diff options
| author | eunhhu <[email protected]> | 2026-09-28 19:41:08 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 19:41:08 +0100 |
| commit | 63cc9554f2cd93e46d0413a21cbce05014e2f2b3 (patch) | |
| tree | 1479bb368b8fa4d2dcac3c43d73c9b34ffca41dc /research/experiments/019-idle-links/quic | |
| parent | a83d663404e030a8734fddc8827fb4afe309a1b9 (diff) | |
| download | pure-63cc9554f2cd93e46d0413a21cbce05014e2f2b3.tar.gz pure-63cc9554f2cd93e46d0413a21cbce05014e2f2b3.zip | |
Measure a link at rest: 132 bytes against a QUIC connection's 38,106 (094)
M1 of design 011. A Pure node allows 1,000 to 1,000,000 senders; each
sends one order and, once told it is stored, rests. At rest a link costs
the node 132 bytes (the sender allowed, 73; its number heard, about 60)
and no transport state; with a million the node runs in 139 MB. A quinn
0.11 service holding the same exchange's clients connected at rest costs
38,106 bytes a connection: 289 times as much, past the design's 1/100.
A link at rest takes its next record in 0.56 ms to stored, one X25519 on
the node included; a new QUIC connection takes 1.06 ms and 2.8 times the
receiver's CPU, without storing.
research/experiments/019-idle-links: links.c, many senders written from
serve/link.h's packet format alone (a second implementation), and the
quinn service and clients (quic/, built offline); run.py makes both
measurements.
Diffstat (limited to 'research/experiments/019-idle-links/quic')
| -rw-r--r-- | research/experiments/019-idle-links/quic/Cargo.lock | 634 | ||||
| -rw-r--r-- | research/experiments/019-idle-links/quic/Cargo.toml | 18 | ||||
| -rw-r--r-- | research/experiments/019-idle-links/quic/src/main.rs | 329 |
3 files changed, 981 insertions, 0 deletions
diff --git a/research/experiments/019-idle-links/quic/Cargo.lock b/research/experiments/019-idle-links/quic/Cargo.lock new file mode 100644 index 0000000..f31be94 --- /dev/null +++ b/research/experiments/019-idle-links/quic/Cargo.lock @@ -0,0 +1,634 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "idle-quic" +version = "0.1.0" +dependencies = [ + "quinn", + "ring", + "rustls", + "tokio", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" diff --git a/research/experiments/019-idle-links/quic/Cargo.toml b/research/experiments/019-idle-links/quic/Cargo.toml new file mode 100644 index 0000000..cc0f527 --- /dev/null +++ b/research/experiments/019-idle-links/quic/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "idle-quic" +version = "0.1.0" +edition = "2021" +publish = false + +# The QUIC side of M1 (../run.py, docs/design/011-pure-transport.md): quinn +# 0.11 with rustls and ring, as a Rust service would use it. +[dependencies] +quinn = { version = "0.11", default-features = false, features = ["rustls-ring", "runtime-tokio"] } +rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "time", "net", "sync", "io-util", "io-std"] } +ring = "0.17" + +[profile.release] +debug = 1 + +[workspace] diff --git a/research/experiments/019-idle-links/quic/src/main.rs b/research/experiments/019-idle-links/quic/src/main.rs new file mode 100644 index 0000000..cdb0376 --- /dev/null +++ b/research/experiments/019-idle-links/quic/src/main.rs @@ -0,0 +1,329 @@ +//! A QUIC service holding many clients at rest, for M1 of +//! docs/design/011-pure-transport.md: the same exchange as a Pure link's +//! (a client sends one order, the service stores it, says so, and the two +//! stay connected, quiet), over quinn 0.11 with rustls (AES-128-GCM). +//! +//! idle-quic serve CERT.der KEY.der [PORT] +//! a service on 127.0.0.1 (the port on stdout as JSON), one thread; +//! a line of JSON on stdout at every 1,000th order stored. Its +//! stateless reset key comes from KEY.der, so one started again on +//! the same port resets the connections of the one before at once +//! idle-quic connect PORT FIRST COUNT WINDOW +//! clients FIRST.. each connect, send order 1 and wait until it is +//! stored, WINDOW at a time over one endpoint; a line of JSON; the +//! connections stay open until stdin closes; then COUNT orders more +//! (number 2) over them, and another line +//! idle-quic steady PORT FIRST COUNT PERIOD_MS SECONDS WINDOW +//! clients FIRST.. connect (WINDOW at a time), then each makes an +//! order every PERIOD_MS for SECONDS and sends it over its connection; +//! one that fails (no answer in a second, or the connection lost) +//! connects again and sends it again. Then a line of JSON: orders made +//! and stored, connections made again, the orders stored in each +//! 100 ms, the time from an order made to it stored +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use quinn::crypto::rustls::QuicClientConfig; +use quinn::{ClientConfig, Endpoint, IdleTimeout, ServerConfig, TransportConfig, VarInt}; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, UnixTime}; +use rustls::{DigitallySignedStruct, SignatureScheme}; +use tokio::io::AsyncReadExt; + +fn transport() -> Arc<TransportConfig> { + let mut t = TransportConfig::default(); + t.max_idle_timeout(Some(IdleTimeout::from(VarInt::from_u32(3_600_000)))); // an hour: connections at rest stay + t.keep_alive_interval(None); + Arc::new(t) +} + +/// The order: the client's number and the order's. +fn order(client: u64, seq: u64) -> [u8; 64] { + let mut bytes = [0u8; 64]; + bytes[..8].copy_from_slice(&client.to_le_bytes()); + bytes[8..16].copy_from_slice(&seq.to_le_bytes()); + bytes[16..19].copy_from_slice(b"pen"); + bytes +} + +fn serve(cert: Vec<u8>, key: Vec<u8>, port: u16) { + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + runtime.block_on(async move { + let reset = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, ring::digest::digest(&ring::digest::SHA256, &key).as_ref()); + let mut config = ServerConfig::with_single_cert( + vec![CertificateDer::from(cert)], + PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)), + ) + .unwrap(); + config.transport_config(transport()); + let socket = std::net::UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], port))).unwrap(); + let endpoint = Endpoint::new(quinn::EndpointConfig::new(Arc::new(reset)), Some(config), socket, + quinn::default_runtime().unwrap()).unwrap(); + println!("{{\"port\":{}}}", endpoint.local_addr().unwrap().port()); + // What the service keeps: each client's last order heard, and its connection. + let heard: Arc<Mutex<HashMap<u64, u64>>> = Arc::default(); + let kept: Arc<Mutex<Vec<quinn::Connection>>> = Arc::default(); + let stored = Arc::new(std::sync::atomic::AtomicU64::new(0)); + while let Some(incoming) = endpoint.accept().await { + let (heard, kept, stored) = (heard.clone(), kept.clone(), stored.clone()); + tokio::spawn(async move { + let Ok(connection) = incoming.await else { return }; + kept.lock().unwrap().push(connection.clone()); + while let Ok((mut send, mut recv)) = connection.accept_bi().await { + let Ok(bytes) = recv.read_to_end(1024).await else { return }; + if bytes.len() < 16 { + return; + } + let client = u64::from_le_bytes(bytes[..8].try_into().unwrap()); + let seq = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); + let fresh = { + let mut heard = heard.lock().unwrap(); + let last = heard.entry(client).or_insert(0); + let fresh = seq > *last; + *last = (*last).max(seq); + fresh + }; + let _ = send.write_all(&[1]).await; + let _ = send.finish(); + if fresh { + let n = stored.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + if n % 1000 == 0 { + println!("{{\"stored\":{n}}}"); + } + } + } + }); + } + }); +} + +#[derive(Debug)] +struct Anyone; // the test's own service: its certificate is not checked + +impl ServerCertVerifier for Anyone { + fn verify_server_cert(&self, _: &CertificateDer<'_>, _: &[CertificateDer<'_>], _: &ServerName<'_>, _: &[u8], + _: UnixTime) -> Result<ServerCertVerified, rustls::Error> { + Ok(ServerCertVerified::assertion()) + } + fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) + -> Result<HandshakeSignatureValid, rustls::Error> { + rustls::crypto::verify_tls12_signature(message, cert, dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms) + } + fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) + -> Result<HandshakeSignatureValid, rustls::Error> { + rustls::crypto::verify_tls13_signature(message, cert, dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms) + } + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + rustls::crypto::ring::default_provider().signature_verification_algorithms.supported_schemes() + } +} + +fn percentile(values: &mut [f64], p: f64) -> f64 { + if values.is_empty() { + return 0.0; + } + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values[((values.len() as f64 * p) as usize).min(values.len() - 1)] +} + +fn client_config() -> ClientConfig { + let crypto = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(Anyone)) + .with_no_client_auth(); + let mut config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(crypto).unwrap())); + config.transport_config(transport()); + config +} + +async fn exchange(connection: &quinn::Connection, client: u64, seq: u64) -> bool { + let Ok((mut send, mut recv)) = connection.open_bi().await else { return false }; + if send.write_all(&order(client, seq)).await.is_err() || send.finish().is_err() { + return false; + } + matches!(recv.read_to_end(16).await, Ok(answer) if answer == [1]) +} + +fn connect(port: u16, first: u64, count: u64, window: usize) { + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + runtime.block_on(async move { + let mut endpoint = Endpoint::client(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap(); + endpoint.set_default_client_config(client_config()); + let server = SocketAddr::from(([127, 0, 0, 1], port)); + let window = Arc::new(tokio::sync::Semaphore::new(window)); + let kept: Arc<Mutex<Vec<quinn::Connection>>> = Arc::default(); + let waited: Arc<Mutex<Vec<f64>>> = Arc::default(); + let began = Instant::now(); + let mut tasks = Vec::new(); + for client in first..first + count { + let permit = window.clone().acquire_owned().await.unwrap(); + let (endpoint, kept, waited) = (endpoint.clone(), kept.clone(), waited.clone()); + tasks.push(tokio::spawn(async move { + let started = Instant::now(); + let Ok(connecting) = endpoint.connect(server, "localhost") else { return false }; + let Ok(connection) = connecting.await else { return false }; + let ok = exchange(&connection, client, 1).await; + waited.lock().unwrap().push(started.elapsed().as_secs_f64() * 1000.0); + kept.lock().unwrap().push(connection); + drop(permit); + ok + })); + } + let mut failed = 0; + for task in tasks { + if !task.await.unwrap_or(false) { + failed += 1; + } + } + let seconds = began.elapsed().as_secs_f64(); + let mut w = waited.lock().unwrap().clone(); + println!("{{\"clients\":{count},\"failed\":{failed},\"seconds\":{seconds:.3},\"per_second\":{:.0},\"stored_ms_p50\":{:.3},\"stored_ms_p99\":{:.3}}}", + count as f64 / seconds, percentile(&mut w, 0.5), percentile(&mut w, 0.99)); + // At rest until stdin closes; then one more order each, over the same connections. + let mut rest = Vec::new(); + let _ = tokio::io::stdin().read_to_end(&mut rest).await; + let connections = kept.lock().unwrap().clone(); + let began = Instant::now(); + let mut again = 0; + for (k, connection) in connections.iter().enumerate() { + if exchange(connection, first + k as u64, 2).await { + again += 1; + } + } + println!("{{\"again\":{again},\"seconds\":{:.3}}}", began.elapsed().as_secs_f64()); + tokio::time::sleep(Duration::from_millis(100)).await; + }); +} + +/// Clients that keep sending, connecting again when their connection fails. +fn steady(port: u16, first: u64, count: u64, period: Duration, seconds: u64, window: usize) { + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + runtime.block_on(async move { + let mut endpoint = Endpoint::client(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap(); + endpoint.set_default_client_config(client_config()); + let server = SocketAddr::from(([127, 0, 0, 1], port)); + let connecting = Arc::new(tokio::sync::Semaphore::new(window)); + let began = Instant::now(); + let end = began + Duration::from_secs(seconds) + Duration::from_secs(2); + let buckets = ((seconds + 70) * 10) as usize; + let timeline: Arc<Vec<std::sync::atomic::AtomicU32>> = + Arc::new((0..buckets).map(|_| std::sync::atomic::AtomicU32::new(0)).collect()); + let waited: Arc<Mutex<Vec<f64>>> = Arc::default(); + let (made, again) = (Arc::new(std::sync::atomic::AtomicU64::new(0)), Arc::new(std::sync::atomic::AtomicU64::new(0))); + let mut tasks = Vec::new(); + for (k, client) in (first..first + count).enumerate() { + let (endpoint, connecting, timeline, waited, made, again) = + (endpoint.clone(), connecting.clone(), timeline.clone(), waited.clone(), made.clone(), again.clone()); + let start = began + Duration::from_secs(2) + period.mul_f64(k as f64 / count as f64); + tasks.push(tokio::spawn(async move { + let mut connection: Option<quinn::Connection> = None; + let mut seq = 0u64; + let mut next = start; + loop { + if connection.is_none() { + let permit = connecting.acquire().await.unwrap(); + if let Ok(c) = endpoint.connect(server, "localhost") { + if let Ok(Ok(c)) = tokio::time::timeout(Duration::from_secs(2), c).await { + connection = Some(c); + } + } + drop(permit); + if connection.is_none() { + tokio::time::sleep(Duration::from_millis(200)).await; + continue; + } + } + if Instant::now() >= end { + return; + } + tokio::time::sleep_until(next.into()).await; + seq += 1; + made.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let order_made = Instant::now(); + next += period; + loop { + let c = connection.clone().unwrap(); + let done = tokio::time::timeout(Duration::from_secs(1), exchange(&c, client, seq)).await; + if matches!(done, Ok(true)) { + let now = Instant::now(); + let bucket = ((now - began).as_millis() / 100) as usize; + if bucket < timeline.len() { + timeline[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + waited.lock().unwrap().push((now - order_made).as_secs_f64() * 1000.0); + break; + } + // Lost: a new connection (a handshake), and the order again. + again.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + connection = None; + while connection.is_none() { + let permit = connecting.acquire().await.unwrap(); + if let Ok(c) = endpoint.connect(server, "localhost") { + if let Ok(Ok(c)) = tokio::time::timeout(Duration::from_secs(2), c).await { + connection = Some(c); + } + } + drop(permit); + if connection.is_none() { + tokio::time::sleep(Duration::from_millis(200)).await; + } + } + } + if Instant::now() >= end { + return; + } + } + })); + } + for task in tasks { + let _ = task.await; + } + let mut w = waited.lock().unwrap().clone(); + let stored = w.len(); + let timeline: Vec<u32> = timeline.iter().map(|b| b.load(std::sync::atomic::Ordering::Relaxed)).collect(); + let last = timeline.iter().rposition(|&b| b > 0).unwrap_or(0); + let (mut longest, mut gap, mut since) = (0usize, 0usize, None::<usize>); + for (b, &n) in timeline.iter().enumerate().take(last + 1) { + if n > 0 { + if let Some(s) = since { + if b - s - 1 > longest { + longest = b - s - 1; + gap = s + 1; + } + } + since = Some(b); + } + } + let series: Vec<String> = timeline[..=last].iter().map(|n| n.to_string()).collect(); + println!("{{\"clients\":{count},\"made\":{},\"stored\":{stored},\"again\":{},\"stored_ms_p50\":{:.3},\"stored_ms_p99\":{:.3},\"stored_ms_max\":{:.3},\"longest_gap_ms\":{},\"gap_from_ms\":{},\"timeline_100ms\":[{}]}}", + made.load(std::sync::atomic::Ordering::Relaxed), again.load(std::sync::atomic::Ordering::Relaxed), + percentile(&mut w, 0.5), percentile(&mut w, 0.99), percentile(&mut w, 1.0), longest * 100, gap * 100, + series.join(",")); + }); +} + +fn main() { + let args: Vec<String> = std::env::args().collect(); + match args.get(1).map(String::as_str) { + Some("serve") if args.len() == 4 || args.len() == 5 => serve(std::fs::read(&args[2]).unwrap(), + std::fs::read(&args[3]).unwrap(), + args.get(4).map_or(0, |p| p.parse().unwrap())), + Some("steady") if args.len() == 8 => steady(args[2].parse().unwrap(), args[3].parse().unwrap(), + args[4].parse().unwrap(), + Duration::from_millis(args[5].parse().unwrap()), + args[6].parse().unwrap(), args[7].parse().unwrap()), + Some("connect") if args.len() == 6 => connect(args[2].parse().unwrap(), args[3].parse().unwrap(), + args[4].parse().unwrap(), args[5].parse().unwrap()), + _ => { + eprintln!("idle-quic serve CERT.der KEY.der [PORT] | idle-quic connect PORT FIRST COUNT WINDOW | \ + idle-quic steady PORT FIRST COUNT PERIOD_MS SECONDS WINDOW"); + std::process::exit(2); + } + } +} |
