diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 01:04:11 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 01:04:11 +0000 |
commit | 148ffe4545e810c9dd0eb8680ed7fe2aa461ede0 (patch) | |
tree | feb506ec0fba24b579c5cf6b32d62a486128e590 | |
parent | 01a4f7de8c17eaa17d1bf2a1a1ccf5dcfd7d87ea (diff) | |
parent | 015bb821e8c49b6c0cbc59437f47735eb17aabdd (diff) | |
download | quiche-aml_cbr_341110000.tar.gz |
Snap for 10447354 from 015bb821e8c49b6c0cbc59437f47735eb17aabdd to mainline-cellbroadcast-releaseaml_cbr_341710000aml_cbr_341610000aml_cbr_341510010aml_cbr_341410010aml_cbr_341311010aml_cbr_341110000aml_cbr_341011000aml_cbr_340914000android14-mainline-cellbroadcast-release
Change-Id: I07d3a4fce0426cdfcd7e3410ea8fe4d1b5202210
55 files changed, 12865 insertions, 2518 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 615faf7..3ee3b17 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "4d411c22413835f2d57f993d1c90c07813f803cd" + "sha1": "05046f9a93253be8116d149617e446df84889180" }, "path_in_vcs": "quiche" }
\ No newline at end of file @@ -63,6 +63,8 @@ rust_defaults { "liblog_rust", "liboctets", "libring", + "libslab", + "libsmallvec", ], prefer_rlib: true, // For DnsResolver (Mainline module introduced in Q). @@ -132,6 +134,8 @@ rust_defaults { "libmio", "liboctets", "libring", + "libslab", + "libsmallvec", "liburl", ], data: [ diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 020aecf..0000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @cloudflare/protocols @@ -16,9 +16,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bindgen" -version = "0.59.2" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ "bitflags", "cexpr", @@ -41,9 +41,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "boring" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6953f3fea6f9f20c15e81b3f4ef2217ea06cc05652a0db49c0abb35626b0fad1" +checksum = "4c713ad6d8d7a681a43870ac37b89efd2a08015ceb4b256d82707509c1f0b6bb" dependencies = [ "bitflags", "boring-sys", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "boring-sys" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e43b1d5c3524929bc64d56e604f7362e357d509ff8b29903605fd72f4f41eae" +checksum = "7663d3069437a5ccdb2b5f4f481c8b80446daea10fa8503844e89ac65fcdc363" dependencies = [ "bindgen", "cmake", @@ -64,15 +64,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cexpr" @@ -91,9 +91,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.3.2" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf6b561dcf059c85bbe388e0a7b0a1469acb3934cc0cfa148613a830629e3049" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] @@ -130,7 +130,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -141,14 +141,14 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "fnv" @@ -168,13 +168,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.11", ] [[package]] @@ -185,15 +185,15 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "ident_case" @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -224,15 +224,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -251,15 +251,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "log" @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" @@ -300,9 +300,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -331,15 +331,15 @@ dependencies = [ [[package]] name = "octets" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa906ec83d76442cc421a362fa8c131caa513ca19af87dc5736b6679b43240d" +checksum = "3a74f2cda724d43a0a63140af89836d4e7db6138ef67c9f96d3a0f0150d05000" [[package]] name = "once_cell" -version = "1.11.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "peeking_take_while" @@ -355,28 +355,29 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] [[package]] name = "qlog" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d439010ca75633cd1a13f0ebc31e8fe982541d1db4ecd84a4d7280adc01d296" +checksum = "321df7a3199d152be256a416096136191e88b7716f1e2e4c8c05b9f77ffb648b" dependencies = [ "serde", "serde_derive", "serde_json", "serde_with", + "smallvec", ] [[package]] name = "quiche" -version = "0.14.0" +version = "0.17.1" dependencies = [ "boring", "cmake", @@ -390,33 +391,35 @@ dependencies = [ "qlog", "ring", "sfv", + "slab", + "smallvec", "url", "winapi", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "ring" @@ -435,13 +438,12 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.23.1" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ "arrayvec", "num-traits", - "serde", ] [[package]] @@ -451,42 +453,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] name = "ryu" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.11", ] [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "indexmap", "itoa", @@ -496,11 +492,10 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b827f2113224f3f19a665136f006709194bdfdcb1fdc1e4b2b5cbac8e0cced54" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "rustversion", "serde", "serde_with_macros", ] @@ -514,14 +509,14 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "sfv" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5082e028484c60920c1b0dc8ab6bd4663f075e15095c5a9009fae779c01f6364" +checksum = "b4f1641177943b4e6faf7622463ae6dfe0f143eb88799e91e2e2e68ede568af5" dependencies = [ "data-encoding", "indexmap", @@ -535,6 +530,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] + +[[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -548,9 +561,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" dependencies = [ "proc-macro2", "quote", @@ -568,27 +592,27 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -618,9 +642,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -628,24 +652,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -653,28 +677,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -704,43 +728,66 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" @@ -11,8 +11,9 @@ [package] edition = "2018" +rust-version = "1.66" name = "quiche" -version = "0.14.0" +version = "0.17.1" authors = ["Alessandro Ghedini <alessandro@ghedini.me>"] build = "src/build.rs" include = [ @@ -80,10 +81,10 @@ version = "0.4" features = ["std"] [dependencies.octets] -version = "0.1" +version = "0.2" [dependencies.qlog] -version = "0.7" +version = "0.9" optional = true [dependencies.ring] @@ -93,6 +94,16 @@ version = "0.16" version = "0.9" optional = true +[dependencies.slab] +version = "0.4" + +[dependencies.smallvec] +version = "1.10" +features = [ + "serde", + "union", +] + [dev-dependencies.mio] version = "0.8" features = [ @@ -123,4 +134,5 @@ features = [ "wincrypt", "ws2def", "ws2ipdef", + "ws2tcpip", ] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index b217d2a..0eb2417 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "quiche" -version = "0.14.0" +version = "0.17.1" authors = ["Alessandro Ghedini <alessandro@ghedini.me>"] edition = "2018" build = "src/build.rs" @@ -10,6 +10,7 @@ readme = "README.md" keywords = ["quic", "http3"] categories = ["network-programming"] license = "BSD-2-Clause" +rust-version = "1.66" include = [ "/*.md", "/*.toml", @@ -57,15 +58,17 @@ log = { version = "0.4", features = ["std"] } libc = "0.2" libm = "0.2" ring = "0.16" +slab = "0.4" lazy_static = "1" -octets = { version = "0.1", path = "../octets" } +octets = { version = "0.2", path = "../octets" } boring = { version = "2.0.0", optional = true } foreign-types-shared = { version = "0.3.0", optional = true } -qlog = { version = "0.7", path = "../qlog", optional = true } +qlog = { version = "0.9", path = "../qlog", optional = true } sfv = { version = "0.9", optional = true } +smallvec = { version = "1.10", features = ["serde", "union"] } [target."cfg(windows)".dependencies] -winapi = { version = "0.3", features = ["wincrypt", "ws2def", "ws2ipdef"] } +winapi = { version = "0.3", features = ["wincrypt", "ws2def", "ws2ipdef", "ws2tcpip"] } [dev-dependencies] mio = { version = "0.8", features = ["net", "os-poll"] } @@ -1,5 +1,9 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update rust/crates/quiche +# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md + name: "quiche" -description: "\ud83e\udd67 Savoury implementation of the QUIC transport protocol and HTTP/3" +description: "\360\237\245\247 Savoury implementation of the QUIC transport protocol and HTTP/3" third_party { url { type: HOMEPAGE @@ -7,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/quiche/quiche-0.14.0.crate" + value: "https://static.crates.io/crates/quiche/quiche-0.17.1.crate" } - version: "0.14.0" + version: "0.17.1" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 9 - day: 20 + year: 2023 + month: 4 + day: 7 } } @@ -3,7 +3,7 @@ [![crates.io](https://img.shields.io/crates/v/quiche.svg)](https://crates.io/crates/quiche) [![docs.rs](https://docs.rs/quiche/badge.svg)](https://docs.rs/quiche) [![license](https://img.shields.io/github/license/cloudflare/quiche.svg)](https://opensource.org/licenses/BSD-2-Clause) -![build](https://img.shields.io/github/workflow/status/cloudflare/quiche/Stable) +![build](https://img.shields.io/github/actions/workflow/status/cloudflare/quiche/stable.yml?branch=master) [quiche] is an implementation of the QUIC transport protocol and HTTP/3 as specified by the [IETF]. It provides a low level API for processing QUIC packets @@ -26,6 +26,10 @@ quiche powers Cloudflare edge network's [HTTP/3 support][cloudflare-http3]. The [cloudflare-quic.com](https://cloudflare-quic.com) website can be used for testing and experimentation. +### Android + +Android's DNS resolver uses quiche to [implement DNS over HTTP/3][android-http3]. + ### curl quiche can be [integrated into curl][curl-http3] to provide support for HTTP/3. @@ -36,6 +40,7 @@ quiche can be [integrated into NGINX](nginx/) using an unofficial patch to provide support for HTTP/3. [cloudflare-http3]: https://blog.cloudflare.com/http3-the-past-present-and-future/ +[android-http3]: https://security.googleblog.com/2022/07/dns-over-http3-in-android.html [curl-http3]: https://github.com/curl/curl/blob/master/docs/HTTP3.md#quiche-version Getting Started @@ -64,27 +69,55 @@ production) Use the `--help` command-line flag to get a more detailed description of each tool's options. -### Connection setup +### Configuring connections The first step in establishing a QUIC connection using quiche is creating a -configuration object: +[`Config`] object: ```rust -let config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; +let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; +config.set_application_protos(&[b"example-proto"]); + +// Additional configuration specific to application and use case... ``` -This is shared among multiple connections and can be used to configure a -QUIC endpoint. +The [`Config`] object controls important aspects of the QUIC connection such +as QUIC version, ALPN IDs, flow control, congestion control, idle timeout +and other properties or features. + +QUIC is a general-purpose transport protocol and there are several +configuration properties where there is no reasonable default value. For +example, the permitted number of concurrent streams of any particular type +is dependent on the application running over QUIC, and other use-case +specific concerns. + +quiche defaults several properties to zero, applications most likely need +to set these to something else to satisfy their needs using the following: + +- [`set_initial_max_streams_bidi()`] +- [`set_initial_max_streams_uni()`] +- [`set_initial_max_data()`] +- [`set_initial_max_stream_data_bidi_local()`] +- [`set_initial_max_stream_data_bidi_remote()`] +- [`set_initial_max_stream_data_uni()`] + +[`Config`] also holds TLS configuration. This can be changed by mutators on +the an existing object, or by constructing a TLS context manually and +creating a configuration using [`with_boring_ssl_ctx()`]. + +A configuration object can be shared among multiple connections. + +### Connection setup On the client-side the [`connect()`] utility function can be used to create a new connection, while [`accept()`] is for servers: ```rust // Client connection. -let conn = quiche::connect(Some(&server_name), &scid, &mut config)?; +let conn = quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?; // Server connection. -let conn = quiche::accept(&scid, None, &mut config)?; +let conn = quiche::accept(&scid, None, local, peer, &mut config)?; ``` ### Handling incoming packets @@ -93,10 +126,12 @@ Using the connection's [`recv()`] method the application can process incoming packets that belong to that connection from the network: ```rust +let to = socket.local_addr().unwrap(); + loop { let (read, from) = socket.recv_from(&mut buf).unwrap(); - let recv_info = quiche::RecvInfo { from }; + let recv_info = quiche::RecvInfo { from, to }; let read = match conn.recv(&mut buf[..read], recv_info) { Ok(v) => v, @@ -228,6 +263,14 @@ if conn.is_established() { The quiche [HTTP/3 module] provides a high level API for sending and receiving HTTP requests and responses on top of the QUIC transport protocol. +[`Config`]: https://docs.quic.tech/quiche/struct.Config.html +[`set_initial_max_streams_bidi()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi +[`set_initial_max_streams_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_uni +[`set_initial_max_data()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data +[`set_initial_max_stream_data_bidi_local()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local +[`set_initial_max_stream_data_bidi_remote()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote +[`set_initial_max_stream_data_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_uni +[`with_boring_ssl_ctx()`]: https://docs.quic.tech/quiche/struct.Config.html#method.with_boring_ssl_ctx [`connect()`]: https://docs.quic.tech/quiche/fn.connect.html [`accept()`]: https://docs.quic.tech/quiche/fn.accept.html [`recv()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.recv @@ -265,7 +308,7 @@ is disabled by default), by passing ``--features ffi`` to ``cargo``. Building -------- -quiche requires Rust 1.54 or later to build. The latest stable Rust release can +quiche requires Rust 1.66 or later to build. The latest stable Rust release can be installed using [rustup](https://rustup.rs/). Once the Rust build environment is setup, the quiche source code can be fetched diff --git a/TEST_MAPPING b/TEST_MAPPING index 1d3032b..d394a8a 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,18 +1,17 @@ // Generated by update_crate_tests.py for tests that depend on this crate. { - "presubmit": [ + "imports": [ { - "name": "doh_unit_test" - }, + "path": "packages/modules/DnsResolver" + } + ], + "presubmit": [ { "name": "quiche_device_test_src_lib" } ], "presubmit-rust": [ { - "name": "doh_unit_test" - }, - { "name": "quiche_device_test_src_lib" } ] diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index b599b53..0000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -cognitive-complexity-threshold = 100 diff --git a/examples/client.c b/examples/client.c index 0df9665..ebe049b 100644 --- a/examples/client.c +++ b/examples/client.c @@ -51,6 +51,9 @@ struct conn_io { int sock; + struct sockaddr_storage local_addr; + socklen_t local_addr_len; + quiche_conn *conn; }; @@ -122,8 +125,10 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { quiche_recv_info recv_info = { (struct sockaddr *) &peer_addr, - peer_addr_len, + + (struct sockaddr *) &conn_io->local_addr, + conn_io->local_addr_len, }; ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info); @@ -206,11 +211,13 @@ static void timeout_cb(EV_P_ ev_timer *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns\n", - stats.recv, stats.sent, stats.lost, stats.rtt); + stats.recv, stats.sent, stats.lost, path_stats.rtt); ev_break(EV_A_ EVBREAK_ONE); return; @@ -282,7 +289,23 @@ int main(int argc, char *argv[]) { return -1; } - quiche_conn *conn = quiche_connect(host, (const uint8_t*) scid, sizeof(scid), + struct conn_io *conn_io = malloc(sizeof(*conn_io)); + if (conn_io == NULL) { + fprintf(stderr, "failed to allocate connection IO\n"); + return -1; + } + + conn_io->local_addr_len = sizeof(conn_io->local_addr); + if (getsockname(sock, (struct sockaddr *)&conn_io->local_addr, + &conn_io->local_addr_len) != 0) + { + perror("failed to get local address of socket"); + return -1; + }; + + quiche_conn *conn = quiche_connect(host, (const uint8_t *) scid, sizeof(scid), + (struct sockaddr *) &conn_io->local_addr, + conn_io->local_addr_len, peer->ai_addr, peer->ai_addrlen, config); if (conn == NULL) { @@ -290,12 +313,6 @@ int main(int argc, char *argv[]) { return -1; } - struct conn_io *conn_io = malloc(sizeof(*conn_io)); - if (conn_io == NULL) { - fprintf(stderr, "failed to allocate connection IO\n"); - return -1; - } - conn_io->sock = sock; conn_io->conn = conn; diff --git a/examples/client.rs b/examples/client.rs index 24966e7..2f576fc 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -44,7 +44,7 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 1 { - println!("Usage: {} URL", cmd); + println!("Usage: {cmd} URL"); println!("\nSee tools/apps/ for more complete implementations."); return; } @@ -81,9 +81,13 @@ fn main() { config.verify_peer(false); config - .set_application_protos( - b"\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", - ) + .set_application_protos(&[ + b"hq-interop", + b"hq-29", + b"hq-28", + b"hq-27", + b"http/0.9", + ]) .unwrap(); config.set_max_idle_timeout(5000); @@ -102,9 +106,13 @@ fn main() { let scid = quiche::ConnectionId::from_ref(&scid); + // Get local address. + let local_addr = socket.local_addr().unwrap(); + // Create a QUIC connection and initiate handshake. let mut conn = - quiche::connect(url.domain(), &scid, peer_addr, &mut config).unwrap(); + quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config) + .unwrap(); info!( "connecting to {:} from {:} with scid {}", @@ -163,7 +171,10 @@ fn main() { debug!("got {} bytes", len); - let recv_info = quiche::RecvInfo { from }; + let recv_info = quiche::RecvInfo { + to: socket.local_addr().unwrap(), + from, + }; // Process potentially coalesced packets. let read = match conn.recv(&mut buf[..len], recv_info) { @@ -266,7 +277,7 @@ fn main() { } fn hex_dump(buf: &[u8]) -> String { - let vec: Vec<String> = buf.iter().map(|b| format!("{:02x}", b)).collect(); + let vec: Vec<String> = buf.iter().map(|b| format!("{b:02x}")).collect(); vec.join("") } diff --git a/examples/http3-client.c b/examples/http3-client.c index 1dad8c8..03b1133 100644 --- a/examples/http3-client.c +++ b/examples/http3-client.c @@ -53,6 +53,9 @@ struct conn_io { int sock; + struct sockaddr_storage local_addr; + socklen_t local_addr_len; + quiche_conn *conn; quiche_h3_conn *http3; @@ -144,8 +147,10 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { quiche_recv_info recv_info = { (struct sockaddr *) &peer_addr, - peer_addr_len, + + (struct sockaddr *) &conn_io->local_addr, + conn_io->local_addr_len, }; ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info); @@ -334,11 +339,13 @@ static void timeout_cb(EV_P_ ev_timer *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns\n", - stats.recv, stats.sent, stats.lost, stats.rtt); + stats.recv, stats.sent, stats.lost, path_stats.rtt); ev_break(EV_A_ EVBREAK_ONE); return; @@ -414,7 +421,23 @@ int main(int argc, char *argv[]) { return -1; } - quiche_conn *conn = quiche_connect(host, (const uint8_t*) scid, sizeof(scid), + struct conn_io *conn_io = malloc(sizeof(*conn_io)); + if (conn_io == NULL) { + fprintf(stderr, "failed to allocate connection IO\n"); + return -1; + } + + conn_io->local_addr_len = sizeof(conn_io->local_addr); + if (getsockname(sock, (struct sockaddr *)&conn_io->local_addr, + &conn_io->local_addr_len) != 0) + { + perror("failed to get local address of socket"); + return -1; + }; + + quiche_conn *conn = quiche_connect(host, (const uint8_t *) scid, sizeof(scid), + (struct sockaddr *) &conn_io->local_addr, + conn_io->local_addr_len, peer->ai_addr, peer->ai_addrlen, config); if (conn == NULL) { @@ -422,12 +445,6 @@ int main(int argc, char *argv[]) { return -1; } - struct conn_io *conn_io = malloc(sizeof(*conn_io)); - if (conn_io == NULL) { - fprintf(stderr, "failed to allocate connection IO\n"); - return -1; - } - conn_io->sock = sock; conn_io->conn = conn; conn_io->host = host; diff --git a/examples/http3-client.rs b/examples/http3-client.rs index d4b8219..6a6bbaa 100644 --- a/examples/http3-client.rs +++ b/examples/http3-client.rs @@ -44,7 +44,7 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 1 { - println!("Usage: {} URL", cmd); + println!("Usage: {cmd} URL"); println!("\nSee tools/apps/ for more complete implementations."); return; } @@ -103,9 +103,13 @@ fn main() { let scid = quiche::ConnectionId::from_ref(&scid); + // Get local address. + let local_addr = socket.local_addr().unwrap(); + // Create a QUIC connection and initiate handshake. let mut conn = - quiche::connect(url.domain(), &scid, peer_addr, &mut config).unwrap(); + quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config) + .unwrap(); info!( "connecting to {:} from {:} with scid {}", @@ -186,7 +190,10 @@ fn main() { debug!("got {} bytes", len); - let recv_info = quiche::RecvInfo { from }; + let recv_info = quiche::RecvInfo { + to: local_addr, + from, + }; // Process potentially coalesced packets. let read = match conn.recv(&mut buf[..len], recv_info) { @@ -212,7 +219,7 @@ fn main() { if conn.is_established() && http3_conn.is_none() { http3_conn = Some( quiche::h3::Connection::with_transport(&mut conn, &h3_config) - .unwrap(), + .expect("Unable to create HTTP/3 connection, check the server's uni stream limit and window size"), ); } @@ -333,18 +340,18 @@ fn main() { } fn hex_dump(buf: &[u8]) -> String { - let vec: Vec<String> = buf.iter().map(|b| format!("{:02x}", b)).collect(); + let vec: Vec<String> = buf.iter().map(|b| format!("{b:02x}")).collect(); vec.join("") } -fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> { +pub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> { hdrs.iter() .map(|h| { - ( - String::from_utf8(h.name().into()).unwrap(), - String::from_utf8(h.value().into()).unwrap(), - ) + let name = String::from_utf8_lossy(h.name()).to_string(); + let value = String::from_utf8_lossy(h.value()).to_string(); + + (name, value) }) .collect() } diff --git a/examples/http3-server.c b/examples/http3-server.c index 058ee6b..7eb2d22 100644 --- a/examples/http3-server.c +++ b/examples/http3-server.c @@ -55,6 +55,9 @@ struct connections { int sock; + struct sockaddr *local_addr; + socklen_t local_addr_len; + struct conn_io *h; }; @@ -177,8 +180,11 @@ static uint8_t *gen_cid(uint8_t *cid, size_t cid_len) { static struct conn_io *create_conn(uint8_t *scid, size_t scid_len, uint8_t *odcid, size_t odcid_len, + struct sockaddr *local_addr, + socklen_t local_addr_len, struct sockaddr_storage *peer_addr, - socklen_t peer_addr_len) { + socklen_t peer_addr_len) +{ struct conn_io *conn_io = calloc(1, sizeof(*conn_io)); if (conn_io == NULL) { fprintf(stderr, "failed to allocate connection IO\n"); @@ -193,6 +199,8 @@ static struct conn_io *create_conn(uint8_t *scid, size_t scid_len, quiche_conn *conn = quiche_accept(conn_io->cid, LOCAL_CONN_ID_LEN, odcid, odcid_len, + local_addr, + local_addr_len, (struct sockaddr *) peer_addr, peer_addr_len, config); @@ -347,6 +355,7 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { } conn_io = create_conn(dcid, dcid_len, odcid, odcid_len, + conns->local_addr, conns->local_addr_len, &peer_addr, peer_addr_len); if (conn_io == NULL) { @@ -355,9 +364,11 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { } quiche_recv_info recv_info = { - (struct sockaddr *) &peer_addr, - + (struct sockaddr *)&peer_addr, peer_addr_len, + + conns->local_addr, + conns->local_addr_len, }; ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info); @@ -467,10 +478,13 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); + fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", - stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); + stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); @@ -492,10 +506,13 @@ static void timeout_cb(EV_P_ ev_timer *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); + fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", - stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); + stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); @@ -575,6 +592,8 @@ int main(int argc, char *argv[]) { struct connections c; c.sock = sock; c.h = NULL; + c.local_addr = local->ai_addr; + c.local_addr_len = local->ai_addrlen; conns = &c; diff --git a/examples/http3-server.rs b/examples/http3-server.rs index c2d9d84..32650cd 100644 --- a/examples/http3-server.rs +++ b/examples/http3-server.rs @@ -64,7 +64,7 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 0 { - println!("Usage: {}", cmd); + println!("Usage: {cmd}"); println!("\nSee tools/apps/ for more complete implementations."); return; } @@ -114,6 +114,8 @@ fn main() { let mut clients = ClientMap::new(); + let local_addr = socket.local_addr().unwrap(); + loop { // Find the shorter timeout from all the active connections. // @@ -261,9 +263,14 @@ fn main() { debug!("New connection: dcid={:?} scid={:?}", hdr.dcid, scid); - let conn = - quiche::accept(&scid, odcid.as_ref(), from, &mut config) - .unwrap(); + let conn = quiche::accept( + &scid, + odcid.as_ref(), + local_addr, + from, + &mut config, + ) + .unwrap(); let client = Client { conn, @@ -282,7 +289,10 @@ fn main() { } }; - let recv_info = quiche::RecvInfo { from }; + let recv_info = quiche::RecvInfo { + to: socket.local_addr().unwrap(), + from, + }; // Process potentially coalesced packets. let read = match client.conn.recv(pkt_buf, recv_info) { @@ -660,13 +670,13 @@ fn handle_writable(client: &mut Client, stream_id: u64) { } } -fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> { +pub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> { hdrs.iter() .map(|h| { - ( - String::from_utf8(h.name().into()).unwrap(), - String::from_utf8(h.value().into()).unwrap(), - ) + let name = String::from_utf8_lossy(h.name()).to_string(); + let value = String::from_utf8_lossy(h.value()).to_string(); + + (name, value) }) .collect() } diff --git a/examples/qpack-decode.rs b/examples/qpack-decode.rs index d2aaaa5..6d1cbf4 100644 --- a/examples/qpack-decode.rs +++ b/examples/qpack-decode.rs @@ -43,11 +43,11 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 1 { - println!("Usage: {} FILE", cmd); + println!("Usage: {cmd} FILE"); return; } - let mut file = File::open(&args.next().unwrap()).unwrap(); + let mut file = File::open(args.next().unwrap()).unwrap(); let mut dec = qpack::Decoder::new(); @@ -61,7 +61,7 @@ fn main() { let _ = file.read(&mut len).unwrap(); let len = u32::from_be_bytes(len) as usize; - let mut data = vec![0; len as usize]; + let mut data = vec![0; len]; let data_len = file.read(&mut data).unwrap(); @@ -76,10 +76,10 @@ fn main() { continue; } - for hdr in dec.decode(&data[..len], std::u64::MAX).unwrap() { + for hdr in dec.decode(&data[..len], u64::MAX).unwrap() { let name = std::str::from_utf8(hdr.name()).unwrap(); let value = std::str::from_utf8(hdr.value()).unwrap(); - println!("{}\t{}", name, value); + println!("{name}\t{value}"); } println!(); diff --git a/examples/qpack-encode.rs b/examples/qpack-encode.rs index 5215fe4..4d44e84 100644 --- a/examples/qpack-encode.rs +++ b/examples/qpack-encode.rs @@ -40,11 +40,11 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 1 { - println!("Usage: {} FILE", cmd); + println!("Usage: {cmd} FILE"); return; } - let file = File::open(&args.next().unwrap()).unwrap(); + let file = File::open(args.next().unwrap()).unwrap(); let file = BufReader::new(&file); let mut enc = h3::qpack::Encoder::new(); diff --git a/examples/server.c b/examples/server.c index b6ac1f2..73447a3 100644 --- a/examples/server.c +++ b/examples/server.c @@ -55,6 +55,9 @@ struct connections { int sock; + struct sockaddr *local_addr; + socklen_t local_addr_len; + struct conn_io *h; }; @@ -175,8 +178,11 @@ static uint8_t *gen_cid(uint8_t *cid, size_t cid_len) { static struct conn_io *create_conn(uint8_t *scid, size_t scid_len, uint8_t *odcid, size_t odcid_len, + struct sockaddr *local_addr, + socklen_t local_addr_len, struct sockaddr_storage *peer_addr, - socklen_t peer_addr_len) { + socklen_t peer_addr_len) +{ struct conn_io *conn_io = calloc(1, sizeof(*conn_io)); if (conn_io == NULL) { fprintf(stderr, "failed to allocate connection IO\n"); @@ -191,6 +197,8 @@ static struct conn_io *create_conn(uint8_t *scid, size_t scid_len, quiche_conn *conn = quiche_accept(conn_io->cid, LOCAL_CONN_ID_LEN, odcid, odcid_len, + local_addr, + local_addr_len, (struct sockaddr *) peer_addr, peer_addr_len, config); @@ -336,6 +344,7 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { } conn_io = create_conn(dcid, dcid_len, odcid, odcid_len, + conns->local_addr, conns->local_addr_len, &peer_addr, peer_addr_len); if (conn_io == NULL) { @@ -344,9 +353,11 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { } quiche_recv_info recv_info = { - (struct sockaddr *) &peer_addr, - + (struct sockaddr *)&peer_addr, peer_addr_len, + + conns->local_addr, + conns->local_addr_len, }; ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info); @@ -390,10 +401,13 @@ static void recv_cb(EV_P_ ev_io *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); + fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", - stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); + stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); @@ -414,10 +428,13 @@ static void timeout_cb(EV_P_ ev_timer *w, int revents) { if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; + quiche_path_stats path_stats; quiche_conn_stats(conn_io->conn, &stats); + quiche_conn_path_stats(conn_io->conn, 0, &path_stats); + fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", - stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); + stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); @@ -487,6 +504,8 @@ int main(int argc, char *argv[]) { struct connections c; c.sock = sock; c.h = NULL; + c.local_addr = local->ai_addr; + c.local_addr_len = local->ai_addrlen; conns = &c; diff --git a/examples/server.rs b/examples/server.rs index 9a846e2..496b51c 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -58,7 +58,7 @@ fn main() { let cmd = &args.next().unwrap(); if args.len() != 0 { - println!("Usage: {}", cmd); + println!("Usage: {cmd}"); println!("\nSee tools/apps/ for more complete implementations."); return; } @@ -85,9 +85,13 @@ fn main() { .unwrap(); config - .set_application_protos( - b"\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", - ) + .set_application_protos(&[ + b"hq-interop", + b"hq-29", + b"hq-28", + b"hq-27", + b"http/0.9", + ]) .unwrap(); config.set_max_idle_timeout(5000); @@ -108,6 +112,8 @@ fn main() { let mut clients = ClientMap::new(); + let local_addr = socket.local_addr().unwrap(); + loop { // Find the shorter timeout from all the active connections. // @@ -255,9 +261,14 @@ fn main() { debug!("New connection: dcid={:?} scid={:?}", hdr.dcid, scid); - let conn = - quiche::accept(&scid, odcid.as_ref(), from, &mut config) - .unwrap(); + let conn = quiche::accept( + &scid, + odcid.as_ref(), + local_addr, + from, + &mut config, + ) + .unwrap(); let client = Client { conn, @@ -275,7 +286,10 @@ fn main() { } }; - let recv_info = quiche::RecvInfo { from }; + let recv_info = quiche::RecvInfo { + to: socket.local_addr().unwrap(), + from, + }; // Process potentially coalesced packets. let read = match client.conn.recv(pkt_buf, recv_info) { diff --git a/include/quiche.h b/include/quiche.h index 25318f9..c8ced63 100644 --- a/include/quiche.h +++ b/include/quiche.h @@ -115,6 +115,15 @@ enum quiche_error { // Error in congestion control. QUICHE_ERR_CONGESTION_CONTROL = -14, + + // Too many identifiers were provided. + QUICHE_ERR_ID_LIMIT = -17, + + // Not enough available identifiers. + QUICHE_ERR_OUT_OF_IDENTIFIERS = -18, + + // Error in key update. + QUICHE_ERR_KEY_UPDATE = -19, }; // Returns a human readable string with the quiche version number. @@ -125,7 +134,7 @@ int quiche_enable_debug_logging(void (*cb)(const char *line, void *argp), void *argp); // Stores configuration shared between multiple connections. -typedef struct Config quiche_config; +typedef struct quiche_config quiche_config; // Creates a config object with the given version. quiche_config *quiche_config_new(uint32_t version); @@ -203,6 +212,7 @@ void quiche_config_set_disable_active_migration(quiche_config *config, bool v); enum quiche_cc_algorithm { QUICHE_CC_RENO = 0, QUICHE_CC_CUBIC = 1, + QUICHE_CC_BBR = 2, }; // Sets the congestion control algorithm used. @@ -211,6 +221,9 @@ void quiche_config_set_cc_algorithm(quiche_config *config, enum quiche_cc_algori // Configures whether to use HyStart++. void quiche_config_enable_hystart(quiche_config *config, bool v); +// Configures whether to enable pacing (enabled by default). +void quiche_config_enable_pacing(quiche_config *config, bool v); + // Configures whether to enable receiving DATAGRAM frames. void quiche_config_enable_dgram(quiche_config *config, bool enabled, size_t recv_queue_len, @@ -222,6 +235,12 @@ void quiche_config_set_max_connection_window(quiche_config *config, uint64_t v); // Sets the maximum stream window. void quiche_config_set_max_stream_window(quiche_config *config, uint64_t v); +// Sets the limit of active connection IDs. +void quiche_config_set_active_connection_id_limit(quiche_config *config, uint64_t v); + +// Sets the initial stateless reset token. |v| must contain 16 bytes, otherwise the behaviour is undefined. +void quiche_config_set_stateless_reset_token(quiche_config *config, const uint8_t *v); + // Frees the config object. void quiche_config_free(quiche_config *config); @@ -234,18 +253,20 @@ int quiche_header_info(const uint8_t *buf, size_t buf_len, size_t dcil, uint8_t *token, size_t *token_len); // A QUIC connection. -typedef struct Connection quiche_conn; +typedef struct quiche_conn quiche_conn; // Creates a new server-side connection. quiche_conn *quiche_accept(const uint8_t *scid, size_t scid_len, const uint8_t *odcid, size_t odcid_len, - const struct sockaddr *from, size_t from_len, + const struct sockaddr *local, size_t local_len, + const struct sockaddr *peer, size_t peer_len, quiche_config *config); // Creates a new client-side connection. quiche_conn *quiche_connect(const char *server_name, const uint8_t *scid, size_t scid_len, - const struct sockaddr *to, size_t to_len, + const struct sockaddr *local, size_t local_len, + const struct sockaddr *peer, size_t peer_len, quiche_config *config); // Writes a version negotiation packet. @@ -265,6 +286,7 @@ bool quiche_version_is_supported(uint32_t version); quiche_conn *quiche_conn_new_with_tls(const uint8_t *scid, size_t scid_len, const uint8_t *odcid, size_t odcid_len, + const struct sockaddr *local, size_t local_len, const struct sockaddr *peer, size_t peer_len, quiche_config *config, void *ssl, bool is_server); @@ -287,8 +309,13 @@ void quiche_conn_set_qlog_fd(quiche_conn *conn, int fd, const char *log_title, int quiche_conn_set_session(quiche_conn *conn, const uint8_t *buf, size_t buf_len); typedef struct { + // The remote address the packet was received from. struct sockaddr *from; socklen_t from_len; + + // The local address the packet was received on. + struct sockaddr *to; + socklen_t to_len; } quiche_recv_info; // Processes QUIC packets received from the peer. @@ -296,7 +323,11 @@ ssize_t quiche_conn_recv(quiche_conn *conn, uint8_t *buf, size_t buf_len, const quiche_recv_info *info); typedef struct { - // The address the packet should be sent to. + // The local address the packet should be sent from. + struct sockaddr_storage from; + socklen_t from_len; + + // The remote address the packet should be sent to. struct sockaddr_storage to; socklen_t to_len; @@ -309,7 +340,7 @@ ssize_t quiche_conn_send(quiche_conn *conn, uint8_t *out, size_t out_len, quiche_send_info *out_info); // Returns the size of the send quantum, in bytes. -size_t quiche_conn_send_quantum(quiche_conn *conn); +size_t quiche_conn_send_quantum(const quiche_conn *conn); // Reads contiguous data from a stream. ssize_t quiche_conn_stream_recv(quiche_conn *conn, uint64_t stream_id, @@ -319,6 +350,7 @@ ssize_t quiche_conn_stream_recv(quiche_conn *conn, uint64_t stream_id, ssize_t quiche_conn_stream_send(quiche_conn *conn, uint64_t stream_id, const uint8_t *buf, size_t buf_len, bool fin); +// The side of the stream to be shut down. enum quiche_shutdown { QUICHE_SHUTDOWN_READ = 0, QUICHE_SHUTDOWN_WRITE = 1, @@ -332,29 +364,44 @@ int quiche_conn_stream_priority(quiche_conn *conn, uint64_t stream_id, int quiche_conn_stream_shutdown(quiche_conn *conn, uint64_t stream_id, enum quiche_shutdown direction, uint64_t err); -ssize_t quiche_conn_stream_capacity(quiche_conn *conn, uint64_t stream_id); +// Returns the stream's send capacity in bytes. +ssize_t quiche_conn_stream_capacity(const quiche_conn *conn, uint64_t stream_id); + +// Returns true if the stream has data that can be read. +bool quiche_conn_stream_readable(const quiche_conn *conn, uint64_t stream_id); + +// Returns the next stream that has data to read, or -1 if no such stream is +// available. +int64_t quiche_conn_stream_readable_next(quiche_conn *conn); + +// Returns true if the stream has enough send capacity. +// +// On error a value lower than 0 is returned. +int quiche_conn_stream_writable(quiche_conn *conn, uint64_t stream_id, size_t len); -bool quiche_conn_stream_readable(quiche_conn *conn, uint64_t stream_id); +// Returns the next stream that can be written to, or -1 if no such stream is +// available. +int64_t quiche_conn_stream_writable_next(quiche_conn *conn); // Returns true if all the data has been read from the specified stream. -bool quiche_conn_stream_finished(quiche_conn *conn, uint64_t stream_id); +bool quiche_conn_stream_finished(const quiche_conn *conn, uint64_t stream_id); -typedef struct StreamIter quiche_stream_iter; +typedef struct quiche_stream_iter quiche_stream_iter; // Returns an iterator over streams that have outstanding data to read. -quiche_stream_iter *quiche_conn_readable(quiche_conn *conn); +quiche_stream_iter *quiche_conn_readable(const quiche_conn *conn); // Returns an iterator over streams that can be written to. -quiche_stream_iter *quiche_conn_writable(quiche_conn *conn); +quiche_stream_iter *quiche_conn_writable(const quiche_conn *conn); // Returns the maximum possible size of egress UDP payloads. -size_t quiche_conn_max_send_udp_payload_size(quiche_conn *conn); +size_t quiche_conn_max_send_udp_payload_size(const quiche_conn *conn); // Returns the amount of time until the next timeout event, in nanoseconds. -uint64_t quiche_conn_timeout_as_nanos(quiche_conn *conn); +uint64_t quiche_conn_timeout_as_nanos(const quiche_conn *conn); // Returns the amount of time until the next timeout event, in milliseconds. -uint64_t quiche_conn_timeout_as_millis(quiche_conn *conn); +uint64_t quiche_conn_timeout_as_millis(const quiche_conn *conn); // Processes a timeout event. void quiche_conn_on_timeout(quiche_conn *conn); @@ -364,54 +411,54 @@ int quiche_conn_close(quiche_conn *conn, bool app, uint64_t err, const uint8_t *reason, size_t reason_len); // Returns a string uniquely representing the connection. -void quiche_conn_trace_id(quiche_conn *conn, const uint8_t **out, size_t *out_len); +void quiche_conn_trace_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns the source connection ID. -void quiche_conn_source_id(quiche_conn *conn, const uint8_t **out, size_t *out_len); +void quiche_conn_source_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns the destination connection ID. -void quiche_conn_destination_id(quiche_conn *conn, const uint8_t **out, size_t *out_len); +void quiche_conn_destination_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns the negotiated ALPN protocol. -void quiche_conn_application_proto(quiche_conn *conn, const uint8_t **out, +void quiche_conn_application_proto(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns the peer's leaf certificate (if any) as a DER-encoded buffer. -void quiche_conn_peer_cert(quiche_conn *conn, const uint8_t **out, size_t *out_len); +void quiche_conn_peer_cert(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns the serialized cryptographic session for the connection. -void quiche_conn_session(quiche_conn *conn, const uint8_t **out, size_t *out_len); +void quiche_conn_session(const quiche_conn *conn, const uint8_t **out, size_t *out_len); // Returns true if the connection handshake is complete. -bool quiche_conn_is_established(quiche_conn *conn); +bool quiche_conn_is_established(const quiche_conn *conn); // Returns true if the connection has a pending handshake that has progressed // enough to send or receive early data. -bool quiche_conn_is_in_early_data(quiche_conn *conn); +bool quiche_conn_is_in_early_data(const quiche_conn *conn); // Returns whether there is stream or DATAGRAM data available to read. -bool quiche_conn_is_readable(quiche_conn *conn); +bool quiche_conn_is_readable(const quiche_conn *conn); // Returns true if the connection is draining. -bool quiche_conn_is_draining(quiche_conn *conn); +bool quiche_conn_is_draining(const quiche_conn *conn); // Returns the number of bidirectional streams that can be created // before the peer's stream count limit is reached. -uint64_t quiche_conn_peer_streams_left_bidi(quiche_conn *conn); +uint64_t quiche_conn_peer_streams_left_bidi(const quiche_conn *conn); // Returns the number of unidirectional streams that can be created // before the peer's stream count limit is reached. -uint64_t quiche_conn_peer_streams_left_uni(quiche_conn *conn); +uint64_t quiche_conn_peer_streams_left_uni(const quiche_conn *conn); // Returns true if the connection is closed. -bool quiche_conn_is_closed(quiche_conn *conn); +bool quiche_conn_is_closed(const quiche_conn *conn); // Returns true if the connection was closed due to the idle timeout. -bool quiche_conn_is_timed_out(quiche_conn *conn); +bool quiche_conn_is_timed_out(const quiche_conn *conn); // Returns true if a connection error was received, and updates the provided // parameters accordingly. -bool quiche_conn_peer_error(quiche_conn *conn, +bool quiche_conn_peer_error(const quiche_conn *conn, bool *is_app, uint64_t *error_code, const uint8_t **reason, @@ -419,11 +466,11 @@ bool quiche_conn_peer_error(quiche_conn *conn, // Returns true if a connection error was queued or sent, and updates the provided // parameters accordingly. -bool quiche_conn_local_error(quiche_conn *conn, - bool *is_app, - uint64_t *error_code, - const uint8_t **reason, - size_t *reason_len); +bool quiche_conn_local_error(const quiche_conn *conn, + bool *is_app, + uint64_t *error_code, + const uint8_t **reason, + size_t *reason_len); // Initializes the stream's application data. // @@ -455,19 +502,13 @@ typedef struct { // The number of QUIC packets that were lost. size_t lost; - // The number of sent QUIC packets with retranmitted data. + // The number of sent QUIC packets with retransmitted data. size_t retrans; - // The estimated round-trip time of the connection (in nanoseconds). - uint64_t rtt; - - // The size of the connection's congestion window in bytes. - size_t cwnd; - // The number of sent bytes. uint64_t sent_bytes; - // The number of recevied bytes. + // The number of received bytes. uint64_t recv_bytes; // The number of bytes lost. @@ -476,11 +517,8 @@ typedef struct { // The number of stream bytes retransmitted. uint64_t stream_retrans_bytes; - // The current PMTU for the connection. - size_t pmtu; - - // The most recent data delivery rate estimate in bytes/s. - uint64_t delivery_rate; + // The number of known paths for the connection. + size_t paths_count; // The maximum idle timeout. uint64_t peer_max_idle_timeout; @@ -523,25 +561,84 @@ typedef struct { } quiche_stats; // Collects and returns statistics about the connection. -void quiche_conn_stats(quiche_conn *conn, quiche_stats *out); +void quiche_conn_stats(const quiche_conn *conn, quiche_stats *out); + +typedef struct { + // The local address used by this path. + struct sockaddr_storage local_addr; + socklen_t local_addr_len; + + // The peer address seen by this path. + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len; + + // The validation state of the path. + ssize_t validation_state; + + // Whether this path is active. + bool active; + + // The number of QUIC packets received on this path. + size_t recv; + + // The number of QUIC packets sent on this path. + size_t sent; + + // The number of QUIC packets that were lost on this path. + size_t lost; + + // The number of sent QUIC packets with retransmitted data on this path. + size_t retrans; + + // The estimated round-trip time of the path (in nanoseconds). + uint64_t rtt; + + // The size of the path's congestion window in bytes. + size_t cwnd; + + // The number of sent bytes on this path. + uint64_t sent_bytes; + + // The number of received bytes on this path. + uint64_t recv_bytes; + + // The number of bytes lost on this path. + uint64_t lost_bytes; + + // The number of stream bytes retransmitted on this path. + uint64_t stream_retrans_bytes; + + // The current PMTU for the path. + size_t pmtu; + + // The most recent data delivery rate estimate in bytes/s. + uint64_t delivery_rate; +} quiche_path_stats; + + +// Collects and returns statistics about the specified path for the connection. +// +// The `idx` argument represent the path's index (also see the `paths_count` +// field of `quiche_stats`). +int quiche_conn_path_stats(const quiche_conn *conn, size_t idx, quiche_path_stats *out); // Returns the maximum DATAGRAM payload that can be sent. -ssize_t quiche_conn_dgram_max_writable_len(quiche_conn *conn); +ssize_t quiche_conn_dgram_max_writable_len(const quiche_conn *conn); // Returns the length of the first stored DATAGRAM. -ssize_t quiche_conn_dgram_recv_front_len(quiche_conn *conn); +ssize_t quiche_conn_dgram_recv_front_len(const quiche_conn *conn); // Returns the number of items in the DATAGRAM receive queue. -ssize_t quiche_conn_dgram_recv_queue_len(quiche_conn *conn); +ssize_t quiche_conn_dgram_recv_queue_len(const quiche_conn *conn); // Returns the total size of all items in the DATAGRAM receive queue. -ssize_t quiche_conn_dgram_recv_queue_byte_size(quiche_conn *conn); +ssize_t quiche_conn_dgram_recv_queue_byte_size(const quiche_conn *conn); // Returns the number of items in the DATAGRAM send queue. -ssize_t quiche_conn_dgram_send_queue_len(quiche_conn *conn); +ssize_t quiche_conn_dgram_send_queue_len(const quiche_conn *conn); // Returns the total size of all items in the DATAGRAM send queue. -ssize_t quiche_conn_dgram_send_queue_byte_size(quiche_conn *conn); +ssize_t quiche_conn_dgram_send_queue_byte_size(const quiche_conn *conn); // Reads the first received DATAGRAM. ssize_t quiche_conn_dgram_recv(quiche_conn *conn, uint8_t *buf, @@ -555,6 +652,14 @@ ssize_t quiche_conn_dgram_send(quiche_conn *conn, const uint8_t *buf, void quiche_conn_dgram_purge_outgoing(quiche_conn *conn, bool (*f)(uint8_t *, size_t)); +// Schedule an ack-eliciting packet on the active path. +ssize_t quiche_conn_send_ack_eliciting(quiche_conn *conn); + +// Schedule an ack-eliciting packet on the specified path. +ssize_t quiche_conn_send_ack_eliciting_on_path(quiche_conn *conn, + const struct sockaddr *local, size_t local_len, + const struct sockaddr *peer, size_t peer_len); + // Frees the connection object. void quiche_conn_free(quiche_conn *conn); @@ -683,10 +788,19 @@ enum quiche_h3_error { // See QUICHE_ERR_CONGESTION_CONTROL. QUICHE_H3_TRANSPORT_ERR_CONGESTION_CONTROL = QUICHE_ERR_CONGESTION_CONTROL - 1000, + + // See QUICHE_ERR_ID_LIMIT. + QUICHE_H3_TRANSPORT_ERR_ID_LIMIT = QUICHE_ERR_ID_LIMIT - 1000, + + // See QUICHE_ERR_OUT_OF_IDENTIFIERS. + QUICHE_H3_TRANSPORT_ERR_OUT_OF_IDENTIFIERS = QUICHE_ERR_OUT_OF_IDENTIFIERS - 1000, + + // See QUICHE_ERR_KEY_UPDATE. + QUICHE_H3_TRANSPORT_ERR_KEY_UPDATE = QUICHE_ERR_KEY_UPDATE - 1000, }; // Stores configuration shared between multiple connections. -typedef struct Http3Config quiche_h3_config; +typedef struct quiche_h3_config quiche_h3_config; // Creates an HTTP/3 config object with default settings values. quiche_h3_config *quiche_h3_config_new(void); @@ -700,11 +814,14 @@ void quiche_h3_config_set_qpack_max_table_capacity(quiche_h3_config *config, uin // Sets the `SETTINGS_QPACK_BLOCKED_STREAMS` setting. void quiche_h3_config_set_qpack_blocked_streams(quiche_h3_config *config, uint64_t v); +// Sets the `SETTINGS_ENABLE_CONNECT_PROTOCOL` setting. +void quiche_h3_config_enable_extended_connect(quiche_h3_config *config, bool enabled); + // Frees the HTTP/3 config object. void quiche_h3_config_free(quiche_h3_config *config); -// A QUIC connection. -typedef struct Http3Connection quiche_h3_conn; +// An HTTP/3 connection. +typedef struct quiche_h3_conn quiche_h3_conn; // Creates a new server-side connection. quiche_h3_conn *quiche_h3_accept(quiche_conn *quiche_conn, @@ -724,7 +841,7 @@ enum quiche_h3_event_type { QUICHE_H3_EVENT_PRIORITY_UPDATE, }; -typedef struct Http3Event quiche_h3_event; +typedef struct quiche_h3_event quiche_h3_event; // Processes HTTP/3 data received from the peer. int64_t quiche_h3_conn_poll(quiche_h3_conn *conn, quiche_conn *quic_conn, @@ -758,6 +875,9 @@ int quiche_h3_for_each_setting(quiche_h3_conn *conn, // Check whether data will follow the headers on the stream. bool quiche_h3_event_headers_has_body(quiche_h3_event *ev); +// Check whether or not extended connection is enabled by the peer +bool quiche_h3_extended_connect_enabled_by_peer(quiche_h3_conn *conn); + // Frees the HTTP/3 event object. void quiche_h3_event_free(quiche_h3_event *ev); @@ -805,6 +925,13 @@ int quiche_h3_parse_extensible_priority(uint8_t *priority, size_t priority_len, quiche_h3_priority *parsed); +/// Sends a PRIORITY_UPDATE frame on the control stream with specified +/// request stream ID and priority. +int quiche_h3_send_priority_update_for_request(quiche_h3_conn *conn, + quiche_conn *quic_conn, + uint64_t stream_id, + quiche_h3_priority *priority); + // Take the last received PRIORITY_UPDATE frame for a stream. // // The `cb` callback will be called once. `cb` should check the validity of diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch index f77d468..a1b4bf2 100644 --- a/patches/Android.bp.patch +++ b/patches/Android.bp.patch @@ -1,5 +1,5 @@ diff --git a/Android.bp b/Android.bp -index 38dd21b..bc3ee19 100644 +index 884af0e..ecbb83f 100644 --- a/Android.bp +++ b/Android.bp @@ -43,8 +43,8 @@ cc_library_headers { @@ -27,9 +27,9 @@ index 38dd21b..bc3ee19 100644 "liblazy_static", "liblibc", "liblibm", -@@ -63,10 +64,8 @@ rust_ffi_shared { - "liboctets", - "libring", +@@ -65,41 +66,19 @@ rust_ffi_shared { + "libslab", + "libsmallvec", ], - static_libs: [ - "libcrypto", @@ -40,7 +40,9 @@ index 38dd21b..bc3ee19 100644 apex_available: [ "//apex_available:platform", "com.android.resolv", -@@ -74,26 +73,10 @@ rust_ffi_shared { + ], +- product_available: true, +- vendor_available: true, min_sdk_version: "29", } @@ -62,6 +64,8 @@ index 38dd21b..bc3ee19 100644 - "liblog_rust", - "liboctets", - "libring", +- "libslab", +- "libsmallvec", - ], - static_libs: [ +rust_ffi { @@ -71,7 +75,12 @@ index 38dd21b..bc3ee19 100644 "libcrypto", "libssl", ], -@@ -104,28 +87,22 @@ rust_library { +@@ -107,57 +86,41 @@ rust_library { + "//apex_available:platform", + "com.android.resolv", + ], +- product_available: true, +- vendor_available: true, min_sdk_version: "29", } @@ -94,6 +103,8 @@ index 38dd21b..bc3ee19 100644 - "liblog_rust", - "liboctets", - "libring", +- "libslab", +- "libsmallvec", +rust_library { + name: "libquiche", + defaults: ["libquiche_defaults"], @@ -114,7 +125,11 @@ index 38dd21b..bc3ee19 100644 "libssl", ], apex_available: [ -@@ -135,17 +112,13 @@ rust_ffi_static { + "//apex_available:platform", + "com.android.resolv", + ], +- product_available: true, +- vendor_available: true, min_sdk_version: "29", } @@ -134,8 +149,8 @@ index 38dd21b..bc3ee19 100644 edition: "2018", features: [ "boringssl-vendored", -@@ -161,10 +134,6 @@ rust_test { - "libring", +@@ -175,10 +138,6 @@ rust_test { + "libsmallvec", "liburl", ], - static_libs: [ @@ -145,7 +160,7 @@ index 38dd21b..bc3ee19 100644 data: [ "examples/cert.crt", "examples/cert.key", -@@ -172,3 +141,26 @@ rust_test { +@@ -186,3 +145,26 @@ rust_test { "examples/rootca.crt", ], } diff --git a/patches/Initial-stateless-reset-detection.patch b/patches/Initial-stateless-reset-detection.patch deleted file mode 100644 index 24461a7..0000000 --- a/patches/Initial-stateless-reset-detection.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 2c1ce8948b8fe1a11ea57ff8d1bcee07d038f49e Mon Sep 17 00:00:00 2001 -From: Mike Yu <yumike@google.com> -Date: Wed, 1 Feb 2023 06:14:18 +0000 -Subject: [PATCH] Initial stateless reset detection - -Backport the stateless reset patch to AOSP (current -version of quiche is 0.14.0) so that we don't need to -wait until the patch is released to a quiche version -newer than 0.16.0 (currently, the latest version is 0.16.0). - -This patch is slightly modified because the type of -stateless_reset_token is different (In 0.14.0 the -type is Option<Vec<u8>>; in 0.16.0 the type is -Option<u128>). - -Source patch: -https://github.com/cloudflare/quiche/commit/c6357db0b5311010e266637eda2f645b7fa91df4. - -Bug: 242832641 -Bug: 245074765 -Bug: 258767218 -Test: cd packages/modules/DnsResolver && atest -Test: cd external/rust/crates/quiche && atest -Test: 1. Applied ag/20124672 to DnsResolver - 2. Ran dnschk every 5 minutes for 1 hour - 3. Checked the log: - a. Confirmed that some stateless reset packets were received - b. Confirmed that DNS queries fallback'ed to DoT immediately - after DnsResolver received stateless reset packets -Change-Id: Ife933f54ac6ec1098a9046673ca200c6b4e2ebbf ---- - src/lib.rs | 36 +++++++++++++++++++++++++++++++++++- - 1 file changed, 35 insertions(+), 1 deletion(-) - -diff --git a/src/lib.rs b/src/lib.rs -index 2e13278..d590979 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -1864,7 +1864,17 @@ impl Connection { - let read = match self.recv_single(&mut buf[len - left..len], &info) { - Ok(v) => v, - -- Err(Error::Done) => left, -+ Err(Error::Done) => { -+ // If the packet can't be processed or decrypted, check if -+ // it's a stateless reset. -+ if self.is_stateless_reset(&buf[len - left..len]) { -+ trace!("{} packet is a stateless reset", self.trace_id); -+ -+ self.closed = true; -+ } -+ -+ left -+ }, - - Err(e) => { - // In case of error processing the incoming packet, close -@@ -1900,6 +1910,30 @@ impl Connection { - Ok(done) - } - -+ /// Returns true if a QUIC packet is a stateless reset. -+ fn is_stateless_reset(&self, buf: &[u8]) -> bool { -+ // If the packet is too small, then we just throw it away. -+ let buf_len = buf.len(); -+ if buf_len < 21 { -+ return false; -+ } -+ -+ // TODO: we should iterate over all active destination connection IDs -+ // and check against their reset token. -+ match &self.peer_transport_params.stateless_reset_token { -+ Some(token) => { -+ let token_len = 16; -+ ring::constant_time::verify_slices_are_equal( -+ &token, -+ &buf[buf_len - token_len..buf_len], -+ ) -+ .is_ok() -+ }, -+ -+ None => false, -+ } -+ } -+ - /// Processes a single QUIC packet received from the peer. - /// - /// On success the number of bytes processed from the input buffer is --- -2.39.1.456.gfc5497dd1b-goog - diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index f0d9d93..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,62 +0,0 @@ -max_width = 82 -hard_tabs = false -tab_spaces = 4 -newline_style = "Auto" -use_small_heuristics = "Default" -indent_style = "Block" -wrap_comments = true -format_code_in_doc_comments = true -comment_width = 80 -normalize_comments = true -normalize_doc_attributes = true -format_strings = false -format_macro_matchers = false -format_macro_bodies = true -empty_item_single_line = true -struct_lit_single_line = true -fn_single_line = false -where_single_line = false -imports_indent = "Block" -imports_layout = "Vertical" -imports_granularity = "Item" -reorder_imports = true -reorder_modules = true -reorder_impl_items = true -type_punctuation_density = "Wide" -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false -binop_separator = "Back" -remove_nested_parens = true -combine_control_expr = true -overflow_delimited_expr = true -struct_field_align_threshold = 0 -enum_discrim_align_threshold = 20 -match_arm_blocks = false -force_multiline_blocks = false -fn_args_layout = "Compressed" -brace_style = "SameLineWhere" -control_brace_style = "AlwaysSameLine" -trailing_semicolon = true -trailing_comma = "Vertical" -match_block_trailing_comma = true -blank_lines_upper_bound = 1 -blank_lines_lower_bound = 0 -edition = "2018" -merge_derives = true -use_try_shorthand = true -use_field_init_shorthand = true -force_explicit_abi = false -condense_wildcard_suffixes = true -color = "Auto" -unstable_features = true -disable_all_formatting = false -skip_children = false -hide_parse_errors = false -error_on_line_overflow = false -error_on_unformatted = false -report_todo = "Never" -report_fixme = "Never" -ignore = [] -emit_mode = "Files" -make_backup = false diff --git a/src/build.rs b/src/build.rs index e675bfe..739422f 100644 --- a/src/build.rs +++ b/src/build.rs @@ -32,7 +32,7 @@ const CMAKE_PARAMS_ARM_LINUX: &[(&str, &[(&str, &str)])] = &[ /// so adjust library location based on platform and build target. /// See issue: https://github.com/alexcrichton/cmake-rs/issues/18 fn get_boringssl_platform_output_path() -> String { - if cfg!(windows) { + if cfg!(target_env = "msvc") { // Code under this branch should match the logic in cmake-rs let debug_env_var = std::env::var("DEBUG").expect("DEBUG variable not defined in env"); @@ -119,7 +119,7 @@ fn get_boringssl_cmake_config() -> cmake::Config { "" }; - let cflag = format!("{} {}", bitcode_cflag, target_cflag); + let cflag = format!("{bitcode_cflag} {target_cflag}"); boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag); boringssl_cmake.cflag(&cflag); @@ -177,27 +177,26 @@ fn write_pkg_config() { let target_dir = target_dir_path(); let out_path = target_dir.as_path().join("quiche.pc"); - let mut out_file = std::fs::File::create(&out_path).unwrap(); + let mut out_file = std::fs::File::create(out_path).unwrap(); + + let include_dir = format!("{manifest_dir}/include"); - let include_dir = format!("{}/include", manifest_dir); let version = std::env::var("CARGO_PKG_VERSION").unwrap(); let output = format!( "# quiche -includedir={} +includedir={include_dir} libdir={} Name: quiche Description: quiche library URL: https://github.com/cloudflare/quiche -Version: {} +Version: {version} Libs: -Wl,-rpath,${{libdir}} -L${{libdir}} -lquiche Cflags: -I${{includedir}} ", - include_dir, target_dir.to_str().unwrap(), - version ); out_file.write_all(output.as_bytes()).unwrap(); @@ -228,20 +227,29 @@ fn main() { .cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE"); } - cfg.build_target("bssl").build().display().to_string() + cfg.build_target("ssl").build(); + cfg.build_target("crypto").build().display().to_string() }); let build_path = get_boringssl_platform_output_path(); - let build_dir = format!("{}/build/{}", bssl_dir, build_path); - println!("cargo:rustc-link-search=native={}", build_dir); + let mut build_dir = format!("{bssl_dir}/build/{build_path}"); - println!("cargo:rustc-link-lib=static=crypto"); - println!("cargo:rustc-link-lib=static=ssl"); + // If build directory doesn't exist, use the specified path as is. + if !std::path::Path::new(&build_dir).is_dir() { + build_dir = bssl_dir; + } + + println!("cargo:rustc-link-search=native={build_dir}"); + + let bssl_link_kind = std::env::var("QUICHE_BSSL_LINK_KIND") + .unwrap_or("static".to_string()); + println!("cargo:rustc-link-lib={bssl_link_kind}=ssl"); + println!("cargo:rustc-link-lib={bssl_link_kind}=crypto"); } if cfg!(feature = "boringssl-boring-crate") { - println!("cargo:rustc-link-lib=static=crypto"); println!("cargo:rustc-link-lib=static=ssl"); + println!("cargo:rustc-link-lib=static=crypto"); } // MacOS: Allow cdylib to link with undefined symbols diff --git a/src/cid.rs b/src/cid.rs new file mode 100644 index 0000000..2584635 --- /dev/null +++ b/src/cid.rs @@ -0,0 +1,964 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::Error; +use crate::Result; + +use crate::frame; +use crate::packet::ConnectionId; + +use std::collections::VecDeque; + +/// A structure holding a `ConnectionId` and all its related metadata. +#[derive(Debug, Default)] +pub struct ConnectionIdEntry { + /// The Connection ID. + pub cid: ConnectionId<'static>, + + /// Its associated sequence number. + pub seq: u64, + + /// Its associated reset token. Initial CIDs may not have any reset token. + pub reset_token: Option<u128>, + + /// The path identifier using this CID, if any. + pub path_id: Option<usize>, +} + +#[derive(Default)] +struct BoundedNonEmptyConnectionIdVecDeque { + /// The inner `VecDeque`. + inner: VecDeque<ConnectionIdEntry>, + + /// The maximum number of elements that the `VecDeque` can have. + capacity: usize, +} + +impl BoundedNonEmptyConnectionIdVecDeque { + /// Creates a `VecDeque` bounded by `capacity` and inserts + /// `initial_entry` in it. + fn new(capacity: usize, initial_entry: ConnectionIdEntry) -> Self { + let mut inner = VecDeque::with_capacity(1); + inner.push_back(initial_entry); + Self { inner, capacity } + } + + /// Updates the maximum capacity of the inner `VecDeque` to `new_capacity`. + /// Does nothing if `new_capacity` is lower or equal to the current + /// `capacity`. + fn resize(&mut self, new_capacity: usize) { + if new_capacity > self.capacity { + self.capacity = new_capacity; + } + } + + /// Returns the oldest inserted entry still present in the `VecDeque`. + fn get_oldest(&self) -> &ConnectionIdEntry { + self.inner.front().expect("vecdeque is empty") + } + + /// Gets a immutable reference to the entry having the provided `seq`. + fn get(&self, seq: u64) -> Option<&ConnectionIdEntry> { + // We need to iterate over the whole map to find the key. + self.inner.iter().find(|e| e.seq == seq) + } + + /// Gets a mutable reference to the entry having the provided `seq`. + fn get_mut(&mut self, seq: u64) -> Option<&mut ConnectionIdEntry> { + // We need to iterate over the whole map to find the key. + self.inner.iter_mut().find(|e| e.seq == seq) + } + + /// Returns an iterator over the entries in the `VecDeque`. + fn iter(&self) -> impl Iterator<Item = &ConnectionIdEntry> { + self.inner.iter() + } + + /// Returns the number of elements in the `VecDeque`. + fn len(&self) -> usize { + self.inner.len() + } + + /// Inserts the provided entry in the `VecDeque`. + /// + /// This method ensures the unicity of the `seq` associated to an entry. If + /// an entry has the same `seq` than `e`, this method updates the entry in + /// the `VecDeque` and the number of stored elements remains unchanged. + /// + /// If inserting a new element would exceed the collection's capacity, this + /// method raises an [`IdLimit`]. + /// + /// [`IdLimit`]: enum.Error.html#IdLimit + fn insert(&mut self, e: ConnectionIdEntry) -> Result<()> { + // Ensure we don't have duplicates. + match self.get_mut(e.seq) { + Some(oe) => *oe = e, + None => { + if self.inner.len() == self.capacity { + return Err(Error::IdLimit); + } + self.inner.push_back(e); + }, + }; + Ok(()) + } + + /// Removes all the elements in the collection and inserts the provided one. + fn clear_and_insert(&mut self, e: ConnectionIdEntry) { + self.inner.clear(); + self.inner.push_back(e); + } + + /// Removes the element in the collection having the provided `seq`. + /// + /// If this method is called when there remains a single element in the + /// collection, this method raises an [`OutOfIdentifiers`]. + /// + /// Returns `Some` if the element was in the collection and removed, or + /// `None` if it was not and nothing was modified. + /// + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + fn remove(&mut self, seq: u64) -> Result<Option<ConnectionIdEntry>> { + if self.inner.len() <= 1 { + return Err(Error::OutOfIdentifiers); + } + + Ok(self + .inner + .iter() + .position(|e| e.seq == seq) + .and_then(|index| self.inner.remove(index))) + } + + /// Removes all the elements in the collection whose `seq` is lower than the + /// provided one, and inserts `e` in the collection. + /// + /// For each removed element `re`, `f(re)` is called. + /// + /// If the inserted element has a `seq` lower than the one used to remove + /// elements, it raises an [`OutOfIdentifiers`]. + /// + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + fn remove_lower_than_and_insert<F>( + &mut self, seq: u64, e: ConnectionIdEntry, mut f: F, + ) -> Result<()> + where + F: FnMut(&ConnectionIdEntry), + { + // The insert entry MUST have a sequence higher or equal to the ones + // being retired. + if e.seq < seq { + return Err(Error::OutOfIdentifiers); + } + + // To avoid exceeding the capacity of the inner `VecDeque`, we first + // remove the elements and then insert the new one. + self.inner.retain(|e| { + if e.seq < seq { + f(e); + false + } else { + true + } + }); + + // Note that if no element has been retired and the `VecDeque` reaches + // its capacity limit, this will raise an `IdLimit`. + self.insert(e) + } +} + +/// An iterator over QUIC Connection IDs. +pub struct ConnectionIdIter { + cids: VecDeque<ConnectionId<'static>>, +} + +impl Iterator for ConnectionIdIter { + type Item = ConnectionId<'static>; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + self.cids.pop_front() + } +} + +impl ExactSizeIterator for ConnectionIdIter { + #[inline] + fn len(&self) -> usize { + self.cids.len() + } +} + +#[derive(Default)] +pub struct ConnectionIdentifiers { + /// All the Destination Connection IDs provided by our peer. + dcids: BoundedNonEmptyConnectionIdVecDeque, + + /// All the Source Connection IDs we provide to our peer. + scids: BoundedNonEmptyConnectionIdVecDeque, + + /// Source Connection IDs that should be announced to the peer. + advertise_new_scid_seqs: VecDeque<u64>, + + /// Retired Destination Connection IDs that should be announced to the peer. + retire_dcid_seqs: VecDeque<u64>, + + /// Retired Source Connection IDs that should be notified to the + /// application. + retired_scids: VecDeque<ConnectionId<'static>>, + + /// Largest "Retire Prior To" we received from the peer. + largest_peer_retire_prior_to: u64, + + /// Largest sequence number we received from the peer. + largest_destination_seq: u64, + + /// Next sequence number to use. + next_scid_seq: u64, + + /// "Retire Prior To" value to advertise to the peer. + retire_prior_to: u64, + + /// The maximum number of source Connection IDs our peer allows us. + source_conn_id_limit: usize, + + /// Does the host use zero-length source Connection ID. + zero_length_scid: bool, + + /// Does the host use zero-length destination Connection ID. + zero_length_dcid: bool, +} + +impl ConnectionIdentifiers { + /// Creates a new `ConnectionIdentifiers` with the specified destination + /// connection ID limit and initial source Connection ID. The destination + /// Connection ID is set to the empty one. + pub fn new( + mut destination_conn_id_limit: usize, initial_scid: &ConnectionId, + initial_path_id: usize, reset_token: Option<u128>, + ) -> ConnectionIdentifiers { + // It must be at least 2. + if destination_conn_id_limit < 2 { + destination_conn_id_limit = 2; + } + + // Initially, the limit of active source connection IDs is 2. + let source_conn_id_limit = 2; + + // Record the zero-length SCID status. + let zero_length_scid = initial_scid.is_empty(); + + let initial_scid = + ConnectionId::from_ref(initial_scid.as_ref()).into_owned(); + + // We need to track up to (2 * source_conn_id_limit - 1) source + // Connection IDs when the host wants to force their renewal. + let scids = BoundedNonEmptyConnectionIdVecDeque::new( + 2 * source_conn_id_limit - 1, + ConnectionIdEntry { + cid: initial_scid, + seq: 0, + reset_token, + path_id: Some(initial_path_id), + }, + ); + + let dcids = BoundedNonEmptyConnectionIdVecDeque::new( + destination_conn_id_limit, + ConnectionIdEntry { + cid: ConnectionId::default(), + seq: 0, + reset_token: None, + path_id: Some(initial_path_id), + }, + ); + + // Because we already inserted the initial SCID. + let next_scid_seq = 1; + ConnectionIdentifiers { + scids, + dcids, + retired_scids: VecDeque::new(), + next_scid_seq, + source_conn_id_limit, + zero_length_scid, + ..Default::default() + } + } + + /// Sets the maximum number of source connection IDs our peer allows us. + pub fn set_source_conn_id_limit(&mut self, v: u64) { + // Bound conn id limit so our scids queue sizing is valid. + let v = std::cmp::min(v, (usize::MAX / 2) as u64) as usize; + + // It must be at least 2. + if v >= 2 { + self.source_conn_id_limit = v; + // We need to track up to (2 * source_conn_id_limit - 1) source + // Connection IDs when the host wants to force their renewal. + self.scids.resize(2 * v - 1); + } + } + + /// Gets the destination Connection ID associated with the provided sequence + /// number. + #[inline] + pub fn get_dcid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> { + self.dcids.get(seq_num).ok_or(Error::InvalidState) + } + + /// Gets the source Connection ID associated with the provided sequence + /// number. + #[inline] + pub fn get_scid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> { + self.scids.get(seq_num).ok_or(Error::InvalidState) + } + + /// Adds a new source identifier, and indicates whether it should be + /// advertised through a `NEW_CONNECTION_ID` frame or not. + /// + /// At any time, the peer cannot have more Destination Connection IDs than + /// the maximum number of active Connection IDs it negotiated. In such case + /// (i.e., when [`active_source_cids()`] - `peer_active_conn_id_limit` = 0, + /// if the caller agrees to request the removal of previous connection IDs, + /// it sets the `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is + /// returned. + /// + /// Note that setting `retire_if_needed` does not prevent this function from + /// returning an [`IdLimit`] in the case the caller wants to retire still + /// unannounced Connection IDs. + /// + /// When setting the initial Source Connection ID, the `reset_token` may be + /// `None`. However, other Source CIDs must have an associated + /// `reset_token`. Providing `None` as the `reset_token` for non-initial + /// SCIDs raises an [`InvalidState`]. + /// + /// In the case the provided `cid` is already present, it does not add it. + /// If the provided `reset_token` differs from the one already registered, + /// returns an `InvalidState`. + /// + /// Returns the sequence number associated to that new source identifier. + /// + /// [`active_source_cids()`]: struct.ConnectionIdentifiers.html#method.active_source_cids + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`IdLimit`]: enum.Error.html#IdLimit + pub fn new_scid( + &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>, + advertise: bool, path_id: Option<usize>, retire_if_needed: bool, + ) -> Result<u64> { + if self.zero_length_scid { + return Err(Error::InvalidState); + } + + // Check whether the number of source Connection IDs does not exceed the + // limit. If the host agrees to retire old CIDs, it can store up to + // (2 * source_active_conn_id - 1) source CIDs. This limit is enforced + // when calling `self.scids.insert()`. + if self.scids.len() >= self.source_conn_id_limit { + if !retire_if_needed { + return Err(Error::IdLimit); + } + + // We need to retire the lowest one. + self.retire_prior_to = self.lowest_usable_scid_seq()? + 1; + } + + let seq = self.next_scid_seq; + + if reset_token.is_none() && seq != 0 { + return Err(Error::InvalidState); + } + + // Check first that the SCID has not been inserted before. + if let Some(e) = self.scids.iter().find(|e| e.cid == cid) { + if e.reset_token != reset_token { + return Err(Error::InvalidState); + } + return Ok(e.seq); + } + + self.scids.insert(ConnectionIdEntry { + cid, + seq, + reset_token, + path_id, + })?; + self.next_scid_seq += 1; + + self.mark_advertise_new_scid_seq(seq, advertise); + + Ok(seq) + } + + /// Sets the initial destination identifier. + pub fn set_initial_dcid( + &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>, + path_id: Option<usize>, + ) { + // Record the zero-length DCID status. + self.zero_length_dcid = cid.is_empty(); + self.dcids.clear_and_insert(ConnectionIdEntry { + cid, + seq: 0, + reset_token, + path_id, + }); + } + + /// Adds a new Destination Connection ID (originating from a + /// NEW_CONNECTION_ID frame) and process all its related metadata. + /// + /// Returns an error if the provided Connection ID or its metadata are + /// invalid. + /// + /// Returns a list of tuples (DCID sequence number, Path ID), containing the + /// sequence number of retired DCIDs that were linked to their respective + /// Path ID. + pub fn new_dcid( + &mut self, cid: ConnectionId<'static>, seq: u64, reset_token: u128, + retire_prior_to: u64, + ) -> Result<Vec<(u64, usize)>> { + if self.zero_length_dcid { + return Err(Error::InvalidState); + } + + let mut retired_path_ids = Vec::new(); + // If an endpoint receives a NEW_CONNECTION_ID frame that repeats a + // previously issued connection ID with a different Stateless Reset + // Token field value or a different Sequence Number field value, or if a + // sequence number is used for different connection IDs, the endpoint + // MAY treat that receipt as a connection error of type + // PROTOCOL_VIOLATION. + if let Some(e) = self.dcids.iter().find(|e| e.cid == cid || e.seq == seq) + { + if e.cid != cid || e.seq != seq || e.reset_token != Some(reset_token) + { + return Err(Error::InvalidFrame); + } + // The identifier is already there, nothing to do. + return Ok(retired_path_ids); + } + + // The value in the Retire Prior To field MUST be less than or equal to + // the value in the Sequence Number field. Receiving a value in the + // Retire Prior To field that is greater than that in the Sequence + // Number field MUST be treated as a connection error of type + // FRAME_ENCODING_ERROR. + if retire_prior_to > seq { + return Err(Error::InvalidFrame); + } + + // An endpoint that receives a NEW_CONNECTION_ID frame with a sequence + // number smaller than the Retire Prior To field of a previously + // received NEW_CONNECTION_ID frame MUST send a corresponding + // RETIRE_CONNECTION_ID frame that retires the newly received connection + // ID, unless it has already done so for that sequence number. + if seq < self.largest_peer_retire_prior_to && + !self.retire_dcid_seqs.contains(&seq) + { + self.retire_dcid_seqs.push_back(seq); + return Ok(retired_path_ids); + } + + if seq > self.largest_destination_seq { + self.largest_destination_seq = seq; + } + + let new_entry = ConnectionIdEntry { + cid: cid.clone(), + seq, + reset_token: Some(reset_token), + path_id: None, + }; + + // A receiver MUST ignore any Retire Prior To fields that do not + // increase the largest received Retire Prior To value. + // + // After processing a NEW_CONNECTION_ID frame and adding and retiring + // active connection IDs, if the number of active connection IDs exceeds + // the value advertised in its active_connection_id_limit transport + // parameter, an endpoint MUST close the connection with an error of type + // CONNECTION_ID_LIMIT_ERROR. + if retire_prior_to > self.largest_peer_retire_prior_to { + let retired = &mut self.retire_dcid_seqs; + self.dcids.remove_lower_than_and_insert( + retire_prior_to, + new_entry, + |e| { + retired.push_back(e.seq); + + if let Some(pid) = e.path_id { + retired_path_ids.push((e.seq, pid)); + } + }, + )?; + self.largest_peer_retire_prior_to = retire_prior_to; + } else { + self.dcids.insert(new_entry)?; + } + + Ok(retired_path_ids) + } + + /// Retires the Source Connection ID having the provided sequence number. + /// + /// In case the retired Connection ID is the same as the one used by the + /// packet requesting the retiring, or if the retired sequence number is + /// greater than any previously advertised sequence numbers, it returns an + /// [`InvalidState`]. + /// + /// Returns the path ID that was associated to the retired CID, if any. + /// + /// [`InvalidState`]: enum.Error.html#InvalidState + pub fn retire_scid( + &mut self, seq: u64, pkt_dcid: &ConnectionId, + ) -> Result<Option<usize>> { + if seq >= self.next_scid_seq { + return Err(Error::InvalidState); + } + + let pid = if let Some(e) = self.scids.remove(seq)? { + if e.cid == *pkt_dcid { + return Err(Error::InvalidState); + } + + // Notifies the application. + self.retired_scids.push_back(e.cid); + + // Retiring this SCID may increase the retire prior to. + let lowest_scid_seq = self.lowest_usable_scid_seq()?; + self.retire_prior_to = lowest_scid_seq; + + e.path_id + } else { + None + }; + + Ok(pid) + } + + /// Retires the Destination Connection ID having the provided sequence + /// number. + /// + /// If the caller tries to retire the last destination Connection ID, this + /// method triggers an [`OutOfIdentifiers`]. + /// + /// If the caller tries to retire a non-existing Destination Connection + /// ID sequence number, this method returns an [`InvalidState`]. + /// + /// Returns the path ID that was associated to the retired CID, if any. + /// + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + /// [`InvalidState`]: enum.Error.html#InvalidState + pub fn retire_dcid(&mut self, seq: u64) -> Result<Option<usize>> { + if self.zero_length_dcid { + return Err(Error::InvalidState); + } + + let e = self.dcids.remove(seq)?.ok_or(Error::InvalidState)?; + + self.retire_dcid_seqs.push_back(seq); + + Ok(e.path_id) + } + + /// Updates the Source Connection ID entry with the provided sequence number + /// to indicate that it is now linked to the provided path ID. + pub fn link_scid_to_path_id( + &mut self, dcid_seq: u64, path_id: usize, + ) -> Result<()> { + let e = self.scids.get_mut(dcid_seq).ok_or(Error::InvalidState)?; + e.path_id = Some(path_id); + Ok(()) + } + + /// Updates the Destination Connection ID entry with the provided sequence + /// number to indicate that it is now linked to the provided path ID. + pub fn link_dcid_to_path_id( + &mut self, dcid_seq: u64, path_id: usize, + ) -> Result<()> { + let e = self.dcids.get_mut(dcid_seq).ok_or(Error::InvalidState)?; + e.path_id = Some(path_id); + Ok(()) + } + + /// Gets the minimum Source Connection ID sequence number whose removal has + /// not been requested yet. + #[inline] + pub fn lowest_usable_scid_seq(&self) -> Result<u64> { + self.scids + .iter() + .filter_map(|e| { + if e.seq >= self.retire_prior_to { + Some(e.seq) + } else { + None + } + }) + .min() + .ok_or(Error::InvalidState) + } + + /// Gets the lowest Destination Connection ID sequence number that is not + /// associated to a path. + #[inline] + pub fn lowest_available_dcid_seq(&self) -> Option<u64> { + self.dcids + .iter() + .filter_map(|e| { + if e.path_id.is_none() { + Some(e.seq) + } else { + None + } + }) + .min() + } + + /// Finds the sequence number of the Source Connection ID having the + /// provided value and the identifier of the path using it, if any. + #[inline] + pub fn find_scid_seq( + &self, scid: &ConnectionId, + ) -> Option<(u64, Option<usize>)> { + self.scids.iter().find_map(|e| { + if e.cid == *scid { + Some((e.seq, e.path_id)) + } else { + None + } + }) + } + + /// Returns the number of Source Connection IDs that have not been + /// assigned to a path yet. + /// + /// Note that this function is only meaningful if the host uses non-zero + /// length Source Connection IDs. + #[inline] + pub fn available_scids(&self) -> usize { + self.scids.iter().filter(|e| e.path_id.is_none()).count() + } + + /// Returns the number of Destination Connection IDs that have not been + /// assigned to a path yet. + /// + /// Note that this function returns 0 if the host uses zero length + /// Destination Connection IDs. + #[inline] + pub fn available_dcids(&self) -> usize { + if self.zero_length_dcid() { + return 0; + } + self.dcids.iter().filter(|e| e.path_id.is_none()).count() + } + + /// Returns the oldest active source Connection ID of this connection. + #[inline] + pub fn oldest_scid(&self) -> &ConnectionIdEntry { + self.scids.get_oldest() + } + + /// Returns the oldest known active destination Connection ID of this + /// connection. + /// + /// Note that due to e.g., reordering at reception side, the oldest known + /// active destination Connection ID is not necessarily the one having the + /// lowest sequence. + #[inline] + pub fn oldest_dcid(&self) -> &ConnectionIdEntry { + self.dcids.get_oldest() + } + + /// Adds or remove the source Connection ID sequence number from the + /// source Connection ID set that need to be advertised to the peer through + /// NEW_CONNECTION_ID frames. + #[inline] + pub fn mark_advertise_new_scid_seq( + &mut self, scid_seq: u64, advertise: bool, + ) { + if advertise { + self.advertise_new_scid_seqs.push_back(scid_seq); + } else if let Some(index) = self + .advertise_new_scid_seqs + .iter() + .position(|s| *s == scid_seq) + { + self.advertise_new_scid_seqs.remove(index); + } + } + + /// Adds or remove the destination Connection ID sequence number from the + /// retired destination Connection ID set that need to be advertised to the + /// peer through RETIRE_CONNECTION_ID frames. + #[inline] + pub fn mark_retire_dcid_seq(&mut self, dcid_seq: u64, retire: bool) { + if retire { + self.retire_dcid_seqs.push_back(dcid_seq); + } else if let Some(index) = + self.retire_dcid_seqs.iter().position(|s| *s == dcid_seq) + { + self.retire_dcid_seqs.remove(index); + } + } + + /// Gets a source Connection ID's sequence number requiring advertising it + /// to the peer through NEW_CONNECTION_ID frame, if any. + /// + /// If `Some`, it always returns the same value until it has been removed + /// using `mark_advertise_new_scid_seq`. + #[inline] + pub fn next_advertise_new_scid_seq(&self) -> Option<u64> { + self.advertise_new_scid_seqs.front().copied() + } + + /// Gets a destination Connection IDs's sequence number that need to send + /// RETIRE_CONNECTION_ID frames. + /// + /// If `Some`, it always returns the same value until it has been removed + /// using `mark_retire_dcid_seq`. + #[inline] + pub fn next_retire_dcid_seq(&self) -> Option<u64> { + self.retire_dcid_seqs.front().copied() + } + + /// Returns true if there are new source Connection IDs to advertise. + #[inline] + pub fn has_new_scids(&self) -> bool { + !self.advertise_new_scid_seqs.is_empty() + } + + /// Returns true if there are retired destination Connection IDs to\ + /// advertise. + #[inline] + pub fn has_retire_dcids(&self) -> bool { + !self.retire_dcid_seqs.is_empty() + } + + /// Returns whether zero-length source CIDs are used. + #[inline] + pub fn zero_length_scid(&self) -> bool { + self.zero_length_scid + } + + /// Returns whether zero-length destination CIDs are used. + #[inline] + pub fn zero_length_dcid(&self) -> bool { + self.zero_length_dcid + } + + /// Gets the NEW_CONNECTION_ID frame related to the source connection ID + /// with sequence `seq_num`. + pub fn get_new_connection_id_frame_for( + &self, seq_num: u64, + ) -> Result<frame::Frame> { + let e = self.scids.get(seq_num).ok_or(Error::InvalidState)?; + Ok(frame::Frame::NewConnectionId { + seq_num, + retire_prior_to: self.retire_prior_to, + conn_id: e.cid.to_vec(), + reset_token: e.reset_token.ok_or(Error::InvalidState)?.to_be_bytes(), + }) + } + + /// Returns the number of source Connection IDs that are active. This is + /// only meaningful if the host uses non-zero length Source Connection IDs. + #[inline] + pub fn active_source_cids(&self) -> usize { + self.scids.len() + } + + pub fn pop_retired_scid(&mut self) -> Option<ConnectionId<'static>> { + self.retired_scids.pop_front() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::testing::create_cid_and_reset_token; + + #[test] + fn ids_new_scids() { + let (scid, _) = create_cid_and_reset_token(16); + let (dcid, _) = create_cid_and_reset_token(16); + + let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None); + ids.set_source_conn_id_limit(3); + ids.set_initial_dcid(dcid, None, Some(0)); + + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 0); + assert_eq!(ids.has_new_scids(), false); + assert_eq!(ids.next_advertise_new_scid_seq(), None); + + let (scid2, rt2) = create_cid_and_reset_token(16); + + assert_eq!(ids.new_scid(scid2, Some(rt2), true, None, false), Ok(1)); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 1); + assert_eq!(ids.has_new_scids(), true); + assert_eq!(ids.next_advertise_new_scid_seq(), Some(1)); + + let (scid3, rt3) = create_cid_and_reset_token(16); + + assert_eq!(ids.new_scid(scid3, Some(rt3), true, None, false), Ok(2)); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 2); + assert_eq!(ids.has_new_scids(), true); + assert_eq!(ids.next_advertise_new_scid_seq(), Some(1)); + + // If now we give another CID, it reports an error since it exceeds the + // limit of active CIDs. + let (scid4, rt4) = create_cid_and_reset_token(16); + + assert_eq!( + ids.new_scid(scid4, Some(rt4), true, None, false), + Err(Error::IdLimit), + ); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 2); + assert_eq!(ids.has_new_scids(), true); + assert_eq!(ids.next_advertise_new_scid_seq(), Some(1)); + + // Assume we sent one of them. + ids.mark_advertise_new_scid_seq(1, false); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 2); + assert_eq!(ids.has_new_scids(), true); + assert_eq!(ids.next_advertise_new_scid_seq(), Some(2)); + + // Send the other. + ids.mark_advertise_new_scid_seq(2, false); + + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.available_scids(), 2); + assert_eq!(ids.has_new_scids(), false); + assert_eq!(ids.next_advertise_new_scid_seq(), None); + } + + #[test] + fn new_dcid_event() { + let (scid, _) = create_cid_and_reset_token(16); + let (dcid, _) = create_cid_and_reset_token(16); + + let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None); + ids.set_initial_dcid(dcid, None, Some(0)); + + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.dcids.len(), 1); + + let (dcid2, rt2) = create_cid_and_reset_token(16); + + assert_eq!( + ids.new_dcid(dcid2.clone(), 1, rt2, 0), + Ok(Vec::<(u64, usize)>::new()), + ); + assert_eq!(ids.available_dcids(), 1); + assert_eq!(ids.dcids.len(), 2); + + // Now we assume that the client wants to advertise more source + // Connection IDs than the advertised limit. This is valid if it + // requests its peer to retire enough Connection IDs to fit within the + // limits. + let (dcid3, rt3) = create_cid_and_reset_token(16); + assert_eq!(ids.new_dcid(dcid3.clone(), 2, rt3, 1), Ok(vec![(0, 0)])); + // The CID module does not handle path replacing. Fake it now. + ids.link_dcid_to_path_id(1, 0).unwrap(); + assert_eq!(ids.available_dcids(), 1); + assert_eq!(ids.dcids.len(), 2); + assert_eq!(ids.has_retire_dcids(), true); + assert_eq!(ids.next_retire_dcid_seq(), Some(0)); + + // Fake RETIRE_CONNECTION_ID sending. + ids.mark_retire_dcid_seq(0, false); + assert_eq!(ids.has_retire_dcids(), false); + assert_eq!(ids.next_retire_dcid_seq(), None); + + // Now tries to experience CID retirement. If the server tries to remove + // non-existing DCIDs, it fails. + assert_eq!(ids.retire_dcid(0), Err(Error::InvalidState)); + assert_eq!(ids.retire_dcid(3), Err(Error::InvalidState)); + assert_eq!(ids.has_retire_dcids(), false); + assert_eq!(ids.dcids.len(), 2); + + // Now it removes DCID with sequence 1. + assert_eq!(ids.retire_dcid(1), Ok(Some(0))); + // The CID module does not handle path replacing. Fake it now. + ids.link_dcid_to_path_id(2, 0).unwrap(); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.has_retire_dcids(), true); + assert_eq!(ids.next_retire_dcid_seq(), Some(1)); + assert_eq!(ids.dcids.len(), 1); + + // Fake RETIRE_CONNECTION_ID sending. + ids.mark_retire_dcid_seq(1, false); + assert_eq!(ids.has_retire_dcids(), false); + assert_eq!(ids.next_retire_dcid_seq(), None); + + // Trying to remove the last DCID triggers an error. + assert_eq!(ids.retire_dcid(2), Err(Error::OutOfIdentifiers)); + assert_eq!(ids.available_dcids(), 0); + assert_eq!(ids.has_retire_dcids(), false); + assert_eq!(ids.dcids.len(), 1); + } + + #[test] + fn retire_scids() { + let (scid, _) = create_cid_and_reset_token(16); + let (dcid, _) = create_cid_and_reset_token(16); + + let mut ids = ConnectionIdentifiers::new(3, &scid, 0, None); + ids.set_initial_dcid(dcid, None, Some(0)); + ids.set_source_conn_id_limit(3); + + let (scid2, rt2) = create_cid_and_reset_token(16); + let (scid3, rt3) = create_cid_and_reset_token(16); + + assert_eq!( + ids.new_scid(scid2.clone(), Some(rt2), true, None, false), + Ok(1), + ); + assert_eq!(ids.scids.len(), 2); + assert_eq!( + ids.new_scid(scid3.clone(), Some(rt3), true, None, false), + Ok(2), + ); + assert_eq!(ids.scids.len(), 3); + + assert_eq!(ids.pop_retired_scid(), None); + + assert_eq!(ids.retire_scid(0, &scid2), Ok(Some(0))); + + assert_eq!(ids.pop_retired_scid(), Some(scid)); + assert_eq!(ids.pop_retired_scid(), None); + + assert_eq!(ids.retire_scid(1, &scid3), Ok(None)); + + assert_eq!(ids.pop_retired_scid(), Some(scid2)); + assert_eq!(ids.pop_retired_scid(), None); + } +} diff --git a/src/crypto.rs b/src/crypto.rs index 079961f..b873757 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -38,7 +38,7 @@ use crate::Result; use crate::packet; #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Level { Initial = 0, ZeroRTT = 1, @@ -49,18 +49,16 @@ pub enum Level { impl Level { pub fn from_epoch(e: packet::Epoch) -> Level { match e { - packet::EPOCH_INITIAL => Level::Initial, + packet::Epoch::Initial => Level::Initial, - packet::EPOCH_HANDSHAKE => Level::Handshake, + packet::Epoch::Handshake => Level::Handshake, - packet::EPOCH_APPLICATION => Level::OneRTT, - - _ => unreachable!(), + packet::Epoch::Application => Level::OneRTT, } } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Algorithm { #[allow(non_camel_case_types)] AES128_GCM, @@ -131,45 +129,38 @@ impl Algorithm { pub struct Open { alg: Algorithm, - ctx: EVP_AEAD_CTX, + secret: Vec<u8>, - hp_key: aead::quic::HeaderProtectionKey, + header: HeaderProtectionKey, - nonce: Vec<u8>, + packet: PacketKey, } impl Open { pub fn new( - alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], + alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], secret: &[u8], ) -> Result<Open> { Ok(Open { alg, - ctx: make_aead_ctx(alg, key)?, + secret: Vec::from(secret), - hp_key: aead::quic::HeaderProtectionKey::new( - alg.get_ring_hp(), - hp_key, - ) - .map_err(|_| Error::CryptoFail)?, + header: HeaderProtectionKey::new(alg, hp_key)?, - nonce: Vec::from(iv), + packet: PacketKey::new(alg, key, iv)?, }) } pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Open> { - let key_len = aead.key_len(); - let nonce_len = aead.nonce_len(); + Ok(Open { + alg: aead, - let mut key = vec![0; key_len]; - let mut iv = vec![0; nonce_len]; - let mut pn_key = vec![0; key_len]; + secret: Vec::from(secret), - derive_pkt_key(aead, secret, &mut key)?; - derive_pkt_iv(aead, secret, &mut iv)?; - derive_hdr_key(aead, secret, &mut pn_key)?; + header: HeaderProtectionKey::from_secret(aead, secret)?, - Open::new(aead, &key, &iv, &pn_key) + packet: PacketKey::from_secret(aead, secret)?, + }) } pub fn open_with_u64_counter( @@ -188,11 +179,11 @@ impl Open { let max_out_len = out_len; - let nonce = make_nonce(&self.nonce, counter); + let nonce = make_nonce(&self.packet.nonce, counter); let rc = unsafe { EVP_AEAD_CTX_open( - &self.ctx, // ctx + &self.packet.ctx, // ctx buf.as_mut_ptr(), // out &mut out_len, // out_len max_out_len, // max_out_len @@ -218,7 +209,8 @@ impl Open { } let mask = self - .hp_key + .header + .hpk .new_mask(sample) .map_err(|_| Error::CryptoFail)?; @@ -228,50 +220,59 @@ impl Open { pub fn alg(&self) -> Algorithm { self.alg } + + pub fn derive_next_packet_key(&self) -> Result<Open> { + let next_secret = derive_next_secret(self.alg, &self.secret)?; + + let next_packet_key = PacketKey::from_secret(self.alg, &next_secret)?; + + Ok(Open { + alg: self.alg, + + secret: next_secret, + + header: HeaderProtectionKey::new(self.alg, &self.header.hp_key)?, + + packet: next_packet_key, + }) + } } pub struct Seal { alg: Algorithm, - ctx: EVP_AEAD_CTX, + secret: Vec<u8>, - hp_key: aead::quic::HeaderProtectionKey, + header: HeaderProtectionKey, - nonce: Vec<u8>, + packet: PacketKey, } impl Seal { pub fn new( - alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], + alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8], secret: &[u8], ) -> Result<Seal> { Ok(Seal { alg, - ctx: make_aead_ctx(alg, key)?, + secret: Vec::from(secret), - hp_key: aead::quic::HeaderProtectionKey::new( - alg.get_ring_hp(), - hp_key, - ) - .map_err(|_| Error::CryptoFail)?, + header: HeaderProtectionKey::new(alg, hp_key)?, - nonce: Vec::from(iv), + packet: PacketKey::new(alg, key, iv)?, }) } pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Seal> { - let key_len = aead.key_len(); - let nonce_len = aead.nonce_len(); + Ok(Seal { + alg: aead, - let mut key = vec![0; key_len]; - let mut iv = vec![0; nonce_len]; - let mut pn_key = vec![0; key_len]; + secret: Vec::from(secret), - derive_pkt_key(aead, secret, &mut key)?; - derive_pkt_iv(aead, secret, &mut iv)?; - derive_hdr_key(aead, secret, &mut pn_key)?; + header: HeaderProtectionKey::from_secret(aead, secret)?, - Seal::new(aead, &key, &iv, &pn_key) + packet: PacketKey::from_secret(aead, secret)?, + }) } pub fn seal_with_u64_counter( @@ -302,11 +303,11 @@ impl Seal { return Err(Error::CryptoFail); } - let nonce = make_nonce(&self.nonce, counter); + let nonce = make_nonce(&self.packet.nonce, counter); let rc = unsafe { EVP_AEAD_CTX_seal_scatter( - &self.ctx, // ctx + &self.packet.ctx, // ctx buf.as_mut_ptr(), // out buf[in_len..].as_mut_ptr(), // out_tag &mut out_tag_len, // out_tag_len @@ -335,7 +336,8 @@ impl Seal { } let mask = self - .hp_key + .header + .hpk .new_mask(sample) .map_err(|_| Error::CryptoFail)?; @@ -345,12 +347,85 @@ impl Seal { pub fn alg(&self) -> Algorithm { self.alg } + + pub fn derive_next_packet_key(&self) -> Result<Seal> { + let next_secret = derive_next_secret(self.alg, &self.secret)?; + + let next_packet_key = PacketKey::from_secret(self.alg, &next_secret)?; + + Ok(Seal { + alg: self.alg, + + secret: next_secret, + + header: HeaderProtectionKey::new(self.alg, &self.header.hp_key)?, + + packet: next_packet_key, + }) + } +} + +pub struct HeaderProtectionKey { + hpk: aead::quic::HeaderProtectionKey, + + hp_key: Vec<u8>, +} + +impl HeaderProtectionKey { + pub fn new(alg: Algorithm, hp_key: &[u8]) -> Result<Self> { + aead::quic::HeaderProtectionKey::new(alg.get_ring_hp(), hp_key) + .map(|hpk| Self { + hpk, + hp_key: Vec::from(hp_key), + }) + .map_err(|_| Error::CryptoFail) + } + + pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Self> { + let key_len = aead.key_len(); + + let mut hp_key = vec![0; key_len]; + + derive_hdr_key(aead, secret, &mut hp_key)?; + + Self::new(aead, &hp_key) + } +} + +pub struct PacketKey { + ctx: EVP_AEAD_CTX, + + nonce: Vec<u8>, +} + +impl PacketKey { + pub fn new(alg: Algorithm, key: &[u8], iv: &[u8]) -> Result<Self> { + Ok(Self { + ctx: make_aead_ctx(alg, key)?, + + nonce: Vec::from(iv), + }) + } + + pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Self> { + let key_len = aead.key_len(); + let nonce_len = aead.nonce_len(); + + let mut key = vec![0; key_len]; + let mut iv = vec![0; nonce_len]; + + derive_pkt_key(aead, secret, &mut key)?; + derive_pkt_iv(aead, secret, &mut iv)?; + + Self::new(aead, &key, &iv) + } } pub fn derive_initial_key_material( cid: &[u8], version: u32, is_server: bool, ) -> Result<(Open, Seal)> { - let mut secret = [0; 32]; + let mut client_secret = [0; 32]; + let mut server_secret = [0; 32]; let aead = Algorithm::AES128_GCM; @@ -364,30 +439,54 @@ pub fn derive_initial_key_material( let mut client_iv = vec![0; nonce_len]; let mut client_hp_key = vec![0; key_len]; - derive_client_initial_secret(&initial_secret, &mut secret)?; - derive_pkt_key(aead, &secret, &mut client_key)?; - derive_pkt_iv(aead, &secret, &mut client_iv)?; - derive_hdr_key(aead, &secret, &mut client_hp_key)?; + derive_client_initial_secret(&initial_secret, &mut client_secret)?; + derive_pkt_key(aead, &client_secret, &mut client_key)?; + derive_pkt_iv(aead, &client_secret, &mut client_iv)?; + derive_hdr_key(aead, &client_secret, &mut client_hp_key)?; // Server. let mut server_key = vec![0; key_len]; let mut server_iv = vec![0; nonce_len]; let mut server_hp_key = vec![0; key_len]; - derive_server_initial_secret(&initial_secret, &mut secret)?; - derive_pkt_key(aead, &secret, &mut server_key)?; - derive_pkt_iv(aead, &secret, &mut server_iv)?; - derive_hdr_key(aead, &secret, &mut server_hp_key)?; + derive_server_initial_secret(&initial_secret, &mut server_secret)?; + derive_pkt_key(aead, &server_secret, &mut server_key)?; + derive_pkt_iv(aead, &server_secret, &mut server_iv)?; + derive_hdr_key(aead, &server_secret, &mut server_hp_key)?; let (open, seal) = if is_server { ( - Open::new(aead, &client_key, &client_iv, &client_hp_key)?, - Seal::new(aead, &server_key, &server_iv, &server_hp_key)?, + Open::new( + aead, + &client_key, + &client_iv, + &client_hp_key, + &client_secret, + )?, + Seal::new( + aead, + &server_key, + &server_iv, + &server_hp_key, + &server_secret, + )?, ) } else { ( - Open::new(aead, &server_key, &server_iv, &server_hp_key)?, - Seal::new(aead, &client_key, &client_iv, &client_hp_key)?, + Open::new( + aead, + &server_key, + &server_iv, + &server_hp_key, + &server_secret, + )?, + Seal::new( + aead, + &client_key, + &client_iv, + &client_hp_key, + &client_secret, + )?, ) }; @@ -433,6 +532,17 @@ fn derive_server_initial_secret(prk: &hkdf::Prk, out: &mut [u8]) -> Result<()> { hkdf_expand_label(prk, LABEL, out) } +fn derive_next_secret(aead: Algorithm, secret: &[u8]) -> Result<Vec<u8>> { + const LABEL: &[u8] = b"quic ku"; + + let mut next_secret = vec![0; secret.len()]; + + let secret_prk = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret); + hkdf_expand_label(&secret_prk, LABEL, &mut next_secret)?; + + Ok(next_secret) +} + pub fn derive_hdr_key( aead: Algorithm, secret: &[u8], out: &mut [u8], ) -> Result<()> { diff --git a/src/dgram.rs b/src/dgram.rs index 5da185e..a6b9b5b 100644 --- a/src/dgram.rs +++ b/src/dgram.rs @@ -32,7 +32,7 @@ use std::collections::VecDeque; /// Keeps track of DATAGRAM frames. #[derive(Default)] pub struct DatagramQueue { - queue: VecDeque<Vec<u8>>, + queue: Option<VecDeque<Vec<u8>>>, queue_max_len: usize, queue_bytes_size: usize, } @@ -40,7 +40,7 @@ pub struct DatagramQueue { impl DatagramQueue { pub fn new(queue_max_len: usize) -> Self { DatagramQueue { - queue: VecDeque::with_capacity(queue_max_len), + queue: None, queue_bytes_size: 0, queue_max_len, } @@ -52,17 +52,19 @@ impl DatagramQueue { } self.queue_bytes_size += data.len(); - self.queue.push_back(data); + self.queue + .get_or_insert_with(Default::default) + .push_back(data); Ok(()) } pub fn peek_front_len(&self) -> Option<usize> { - self.queue.front().map(|d| d.len()) + self.queue.as_ref().and_then(|q| q.front().map(|d| d.len())) } pub fn peek_front_bytes(&self, buf: &mut [u8], len: usize) -> Result<usize> { - match self.queue.front() { + match self.queue.as_ref().and_then(|q| q.front()) { Some(d) => { let len = std::cmp::min(len, d.len()); if buf.len() < len { @@ -78,7 +80,7 @@ impl DatagramQueue { } pub fn pop(&mut self) -> Option<Vec<u8>> { - if let Some(d) = self.queue.pop_front() { + if let Some(d) = self.queue.as_mut().and_then(|q| q.pop_front()) { self.queue_bytes_size = self.queue_bytes_size.saturating_sub(d.len()); return Some(d); } @@ -87,21 +89,22 @@ impl DatagramQueue { } pub fn has_pending(&self) -> bool { - !self.queue.is_empty() + !self.queue.as_ref().map(|q| q.is_empty()).unwrap_or(true) } pub fn purge<F: Fn(&[u8]) -> bool>(&mut self, f: F) { - self.queue.retain(|d| !f(d)); - self.queue_bytes_size = - self.queue.iter().fold(0, |total, d| total + d.len()); + if let Some(q) = self.queue.as_mut() { + q.retain(|d| !f(d)); + self.queue_bytes_size = q.iter().fold(0, |total, d| total + d.len()); + } } pub fn is_full(&self) -> bool { - self.queue.len() == self.queue_max_len + self.len() == self.queue_max_len } pub fn len(&self) -> usize { - self.queue.len() + self.queue.as_ref().map(|q| q.len()).unwrap_or(0) } pub fn byte_size(&self) -> usize { @@ -29,7 +29,11 @@ use std::ptr; use std::slice; use std::sync::atomic; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; use std::net::SocketAddr; +use std::net::SocketAddrV4; +use std::net::SocketAddrV6; #[cfg(unix)] use std::os::unix::io::FromRawFd; @@ -43,6 +47,31 @@ use libc::ssize_t; use libc::timespec; #[cfg(not(windows))] +use libc::AF_INET; +#[cfg(windows)] +use winapi::shared::ws2def::AF_INET; + +#[cfg(not(windows))] +use libc::AF_INET6; +#[cfg(windows)] +use winapi::shared::ws2def::AF_INET6; + +#[cfg(not(windows))] +use libc::in_addr; +#[cfg(windows)] +use winapi::shared::inaddr::IN_ADDR as in_addr; + +#[cfg(not(windows))] +use libc::in6_addr; +#[cfg(windows)] +use winapi::shared::in6addr::IN6_ADDR as in6_addr; + +#[cfg(not(windows))] +use libc::sa_family_t; +#[cfg(windows)] +use winapi::shared::ws2def::ADDRESS_FAMILY as sa_family_t; + +#[cfg(not(windows))] use libc::sockaddr_in; #[cfg(windows)] use winapi::shared::ws2def::SOCKADDR_IN as sockaddr_in; @@ -62,21 +91,18 @@ use libc::c_int as socklen_t; #[cfg(not(windows))] use libc::socklen_t; -#[cfg(not(windows))] -use libc::AF_INET; #[cfg(windows)] -use winapi::shared::ws2def::AF_INET; - -#[cfg(not(windows))] -use libc::AF_INET6; +use winapi::shared::in6addr::in6_addr_u; #[cfg(windows)] -use winapi::shared::ws2def::AF_INET6; +use winapi::shared::inaddr::in_addr_S_un; +#[cfg(windows)] +use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH_u; use crate::*; #[no_mangle] pub extern fn quiche_version() -> *const u8 { - //static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); + //static VERSION: &str = concat!("0.17.1", "\0"); // ANDROID's build system doesn't support environment variables // so we hardcode the package version here. static VERSION: &str = concat!("0.6.0", "\0"); @@ -199,12 +225,14 @@ pub extern fn quiche_config_enable_early_data(config: &mut Config) { } #[no_mangle] +/// Corresponds to the `Config::set_application_protos_wire_format` Rust +/// function. pub extern fn quiche_config_set_application_protos( config: &mut Config, protos: *const u8, protos_len: size_t, ) -> c_int { let protos = unsafe { slice::from_raw_parts(protos, protos_len) }; - match config.set_application_protos(protos) { + match config.set_application_protos_wire_format(protos) { Ok(_) => 0, Err(e) => e.to_c() as c_int, @@ -305,6 +333,11 @@ pub extern fn quiche_config_enable_hystart(config: &mut Config, v: bool) { } #[no_mangle] +pub extern fn quiche_config_enable_pacing(config: &mut Config, v: bool) { + config.enable_pacing(v); +} + +#[no_mangle] pub extern fn quiche_config_enable_dgram( config: &mut Config, enabled: bool, recv_queue_len: size_t, send_queue_len: size_t, @@ -332,6 +365,26 @@ pub extern fn quiche_config_set_max_stream_window(config: &mut Config, v: u64) { } #[no_mangle] +pub extern fn quiche_config_set_active_connection_id_limit( + config: &mut Config, v: u64, +) { + config.set_active_connection_id_limit(v); +} + +#[no_mangle] +pub extern fn quiche_config_set_stateless_reset_token( + config: &mut Config, v: *const u8, +) { + let reset_token = unsafe { slice::from_raw_parts(v, 16) }; + let reset_token = match reset_token.try_into() { + Ok(rt) => rt, + Err(_) => unreachable!(), + }; + let reset_token = u128::from_be_bytes(reset_token); + config.set_stateless_reset_token(Some(reset_token)); +} + +#[no_mangle] pub extern fn quiche_config_free(config: *mut Config) { unsafe { Box::from_raw(config) }; } @@ -404,7 +457,8 @@ pub extern fn quiche_header_info( #[no_mangle] pub extern fn quiche_accept( scid: *const u8, scid_len: size_t, odcid: *const u8, odcid_len: size_t, - from: &sockaddr, from_len: socklen_t, config: &mut Config, + local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t, + config: &mut Config, ) -> *mut Connection { let scid = unsafe { slice::from_raw_parts(scid, scid_len) }; let scid = ConnectionId::from_ref(scid); @@ -417,9 +471,10 @@ pub extern fn quiche_accept( None }; - let from = std_addr_from_c(from, from_len); + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); - match accept(&scid, odcid.as_ref(), from, config) { + match accept(&scid, odcid.as_ref(), local, peer, config) { Ok(c) => Box::into_raw(Box::new(c)), Err(_) => ptr::null_mut(), @@ -428,8 +483,9 @@ pub extern fn quiche_accept( #[no_mangle] pub extern fn quiche_connect( - server_name: *const c_char, scid: *const u8, scid_len: size_t, to: &sockaddr, - to_len: socklen_t, config: &mut Config, + server_name: *const c_char, scid: *const u8, scid_len: size_t, + local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t, + config: &mut Config, ) -> *mut Connection { let server_name = if server_name.is_null() { None @@ -440,9 +496,10 @@ pub extern fn quiche_connect( let scid = unsafe { slice::from_raw_parts(scid, scid_len) }; let scid = ConnectionId::from_ref(scid); - let to = std_addr_from_c(to, to_len); + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); - match connect(server_name, &scid, to, config) { + match connect(server_name, &scid, local, peer, config) { Ok(c) => Box::into_raw(Box::new(c)), Err(_) => ptr::null_mut(), @@ -502,8 +559,8 @@ pub extern fn quiche_retry( #[no_mangle] pub extern fn quiche_conn_new_with_tls( scid: *const u8, scid_len: size_t, odcid: *const u8, odcid_len: size_t, - peer: &sockaddr, peer_len: socklen_t, config: &mut Config, ssl: *mut c_void, - is_server: bool, + local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t, + config: &mut Config, ssl: *mut c_void, is_server: bool, ) -> *mut Connection { let scid = unsafe { slice::from_raw_parts(scid, scid_len) }; let scid = ConnectionId::from_ref(scid); @@ -516,6 +573,7 @@ pub extern fn quiche_conn_new_with_tls( None }; + let local = std_addr_from_c(local, local_len); let peer = std_addr_from_c(peer, peer_len); let tls = unsafe { tls::Handshake::from_ptr(ssl) }; @@ -523,6 +581,7 @@ pub extern fn quiche_conn_new_with_tls( match Connection::with_tls( &scid, odcid.as_ref(), + local, peer, config, tls, @@ -632,12 +691,15 @@ pub extern fn quiche_conn_set_session( pub struct RecvInfo<'a> { from: &'a sockaddr, from_len: socklen_t, + to: &'a sockaddr, + to_len: socklen_t, } impl<'a> From<&RecvInfo<'a>> for crate::RecvInfo { fn from(info: &RecvInfo) -> crate::RecvInfo { crate::RecvInfo { from: std_addr_from_c(info.from, info.from_len), + to: std_addr_from_c(info.to, info.to_len), } } } @@ -661,6 +723,8 @@ pub extern fn quiche_conn_recv( #[repr(C)] pub struct SendInfo { + from: sockaddr_storage, + from_len: socklen_t, to: sockaddr_storage, to_len: socklen_t, @@ -679,6 +743,7 @@ pub extern fn quiche_conn_send( match conn.send(out) { Ok((v, info)) => { + out_info.from_len = std_addr_to_c(&info.from, &mut out_info.from); out_info.to_len = std_addr_to_c(&info.to, &mut out_info.to); std_time_to_c(&info.at, &mut out_info.at); @@ -754,7 +819,7 @@ pub extern fn quiche_conn_stream_shutdown( #[no_mangle] pub extern fn quiche_conn_stream_capacity( - conn: &mut Connection, stream_id: u64, + conn: &Connection, stream_id: u64, ) -> ssize_t { match conn.stream_capacity(stream_id) { Ok(v) => v as ssize_t, @@ -765,14 +830,37 @@ pub extern fn quiche_conn_stream_capacity( #[no_mangle] pub extern fn quiche_conn_stream_readable( - conn: &mut Connection, stream_id: u64, + conn: &Connection, stream_id: u64, ) -> bool { conn.stream_readable(stream_id) } #[no_mangle] +pub extern fn quiche_conn_stream_readable_next(conn: &mut Connection) -> i64 { + conn.stream_readable_next().map(|v| v as i64).unwrap_or(-1) +} + +#[no_mangle] +pub extern fn quiche_conn_stream_writable( + conn: &mut Connection, stream_id: u64, len: usize, +) -> c_int { + match conn.stream_writable(stream_id, len) { + Ok(true) => 1, + + Ok(false) => 0, + + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] +pub extern fn quiche_conn_stream_writable_next(conn: &mut Connection) -> i64 { + conn.stream_writable_next().map(|v| v as i64).unwrap_or(-1) +} + +#[no_mangle] pub extern fn quiche_conn_stream_finished( - conn: &mut Connection, stream_id: u64, + conn: &Connection, stream_id: u64, ) -> bool { conn.stream_finished(stream_id) } @@ -838,20 +926,20 @@ pub extern fn quiche_conn_close( } #[no_mangle] -pub extern fn quiche_conn_timeout_as_nanos(conn: &mut Connection) -> u64 { +pub extern fn quiche_conn_timeout_as_nanos(conn: &Connection) -> u64 { match conn.timeout() { Some(timeout) => timeout.as_nanos() as u64, - None => std::u64::MAX, + None => u64::MAX, } } #[no_mangle] -pub extern fn quiche_conn_timeout_as_millis(conn: &mut Connection) -> u64 { +pub extern fn quiche_conn_timeout_as_millis(conn: &Connection) -> u64 { match conn.timeout() { Some(timeout) => timeout.as_millis() as u64, - None => std::u64::MAX, + None => u64::MAX, } } @@ -862,7 +950,7 @@ pub extern fn quiche_conn_on_timeout(conn: &mut Connection) { #[no_mangle] pub extern fn quiche_conn_trace_id( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { let trace_id = conn.trace_id(); @@ -872,7 +960,7 @@ pub extern fn quiche_conn_trace_id( #[no_mangle] pub extern fn quiche_conn_source_id( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { let conn_id = conn.source_id(); let id = conn_id.as_ref(); @@ -882,7 +970,7 @@ pub extern fn quiche_conn_source_id( #[no_mangle] pub extern fn quiche_conn_destination_id( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { let conn_id = conn.destination_id(); let id = conn_id.as_ref(); @@ -893,7 +981,7 @@ pub extern fn quiche_conn_destination_id( #[no_mangle] pub extern fn quiche_conn_application_proto( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { let proto = conn.application_proto(); @@ -903,7 +991,7 @@ pub extern fn quiche_conn_application_proto( #[no_mangle] pub extern fn quiche_conn_peer_cert( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { match conn.peer_cert() { Some(peer_cert) => { @@ -917,7 +1005,7 @@ pub extern fn quiche_conn_peer_cert( #[no_mangle] pub extern fn quiche_conn_session( - conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t, + conn: &Connection, out: &mut *const u8, out_len: &mut size_t, ) { match conn.session() { Some(session) => { @@ -930,33 +1018,33 @@ pub extern fn quiche_conn_session( } #[no_mangle] -pub extern fn quiche_conn_is_established(conn: &mut Connection) -> bool { +pub extern fn quiche_conn_is_established(conn: &Connection) -> bool { conn.is_established() } #[no_mangle] -pub extern fn quiche_conn_is_in_early_data(conn: &mut Connection) -> bool { +pub extern fn quiche_conn_is_in_early_data(conn: &Connection) -> bool { conn.is_in_early_data() } #[no_mangle] -pub extern fn quiche_conn_is_draining(conn: &mut Connection) -> bool { +pub extern fn quiche_conn_is_draining(conn: &Connection) -> bool { conn.is_draining() } #[no_mangle] -pub extern fn quiche_conn_is_closed(conn: &mut Connection) -> bool { +pub extern fn quiche_conn_is_closed(conn: &Connection) -> bool { conn.is_closed() } #[no_mangle] -pub extern fn quiche_conn_is_timed_out(conn: &mut Connection) -> bool { +pub extern fn quiche_conn_is_timed_out(conn: &Connection) -> bool { conn.is_timed_out() } #[no_mangle] pub extern fn quiche_conn_peer_error( - conn: &mut Connection, is_app: *mut bool, error_code: *mut u64, + conn: &Connection, is_app: *mut bool, error_code: *mut u64, reason: &mut *const u8, reason_len: &mut size_t, ) -> bool { match &conn.peer_error { @@ -975,7 +1063,7 @@ pub extern fn quiche_conn_peer_error( #[no_mangle] pub extern fn quiche_conn_local_error( - conn: &mut Connection, is_app: *mut bool, error_code: *mut u64, + conn: &Connection, is_app: *mut bool, error_code: *mut u64, reason: &mut *const u8, reason_len: &mut size_t, ) -> bool { match &conn.local_error { @@ -1015,14 +1103,11 @@ pub struct Stats { sent: usize, lost: usize, retrans: usize, - rtt: u64, - cwnd: usize, sent_bytes: u64, - lost_bytes: u64, recv_bytes: u64, + lost_bytes: u64, stream_retrans_bytes: u64, - pmtu: usize, - delivery_rate: u64, + paths_count: usize, peer_max_idle_timeout: u64, peer_max_udp_payload_size: u64, peer_initial_max_data: u64, @@ -1036,6 +1121,7 @@ pub struct Stats { peer_disable_active_migration: bool, peer_active_conn_id_limit: u64, peer_max_datagram_frame_size: ssize_t, + paths: [PathStats; 8], } #[no_mangle] @@ -1046,14 +1132,11 @@ pub extern fn quiche_conn_stats(conn: &Connection, out: &mut Stats) { out.sent = stats.sent; out.lost = stats.lost; out.retrans = stats.retrans; - out.rtt = stats.rtt.as_nanos() as u64; - out.cwnd = stats.cwnd; out.sent_bytes = stats.sent_bytes; - out.lost_bytes = stats.lost_bytes; out.recv_bytes = stats.recv_bytes; + out.lost_bytes = stats.lost_bytes; out.stream_retrans_bytes = stats.stream_retrans_bytes; - out.pmtu = stats.pmtu; - out.delivery_rate = stats.delivery_rate; + out.paths_count = stats.paths_count; out.peer_max_idle_timeout = stats.peer_max_idle_timeout; out.peer_max_udp_payload_size = stats.peer_max_udp_payload_size; out.peer_initial_max_data = stats.peer_initial_max_data; @@ -1072,7 +1155,58 @@ pub extern fn quiche_conn_stats(conn: &Connection, out: &mut Stats) { None => Error::Done.to_c(), Some(v) => v as ssize_t, - } + }; +} + +#[repr(C)] +pub struct PathStats { + local_addr: sockaddr_storage, + local_addr_len: socklen_t, + peer_addr: sockaddr_storage, + peer_addr_len: socklen_t, + validation_state: ssize_t, + active: bool, + recv: usize, + sent: usize, + lost: usize, + retrans: usize, + rtt: u64, + cwnd: usize, + sent_bytes: u64, + recv_bytes: u64, + lost_bytes: u64, + stream_retrans_bytes: u64, + pmtu: usize, + delivery_rate: u64, +} + +#[no_mangle] +pub extern fn quiche_conn_path_stats( + conn: &Connection, idx: usize, out: &mut PathStats, +) -> c_int { + let stats = match conn.path_stats().nth(idx) { + Some(p) => p, + None => return Error::Done.to_c() as c_int, + }; + + out.local_addr_len = std_addr_to_c(&stats.local_addr, &mut out.local_addr); + out.peer_addr_len = std_addr_to_c(&stats.peer_addr, &mut out.peer_addr); + out.validation_state = stats.validation_state.to_c(); + out.active = stats.active; + out.recv = stats.recv; + out.sent = stats.sent; + out.lost = stats.lost; + out.retrans = stats.retrans; + out.rtt = stats.rtt.as_nanos() as u64; + out.cwnd = stats.cwnd; + out.sent_bytes = stats.sent_bytes; + out.recv_bytes = stats.recv_bytes; + out.lost_bytes = stats.lost_bytes; + out.stream_retrans_bytes = stats.stream_retrans_bytes; + out.pmtu = stats.pmtu; + out.delivery_rate = stats.delivery_rate; + + 0 } #[no_mangle] @@ -1166,74 +1300,196 @@ pub extern fn quiche_conn_dgram_purge_outgoing( } #[no_mangle] +pub extern fn quiche_conn_send_ack_eliciting(conn: &mut Connection) -> ssize_t { + match conn.send_ack_eliciting() { + Ok(()) => 0, + Err(e) => e.to_c(), + } +} + +#[no_mangle] +pub extern fn quiche_conn_send_ack_eliciting_on_path( + conn: &mut Connection, local: &sockaddr, local_len: socklen_t, + peer: &sockaddr, peer_len: socklen_t, +) -> ssize_t { + let local = std_addr_from_c(local, local_len); + let peer = std_addr_from_c(peer, peer_len); + match conn.send_ack_eliciting_on_path(local, peer) { + Ok(()) => 0, + Err(e) => e.to_c(), + } +} + +#[no_mangle] pub extern fn quiche_conn_free(conn: *mut Connection) { unsafe { Box::from_raw(conn) }; } #[no_mangle] -pub extern fn quiche_conn_peer_streams_left_bidi(conn: &mut Connection) -> u64 { +pub extern fn quiche_conn_peer_streams_left_bidi(conn: &Connection) -> u64 { conn.peer_streams_left_bidi() } #[no_mangle] -pub extern fn quiche_conn_peer_streams_left_uni(conn: &mut Connection) -> u64 { +pub extern fn quiche_conn_peer_streams_left_uni(conn: &Connection) -> u64 { conn.peer_streams_left_uni() } #[no_mangle] -pub extern fn quiche_conn_send_quantum(conn: &mut Connection) -> size_t { +pub extern fn quiche_conn_send_quantum(conn: &Connection) -> size_t { conn.send_quantum() as size_t } fn std_addr_from_c(addr: &sockaddr, addr_len: socklen_t) -> SocketAddr { - unsafe { - match addr.sa_family as i32 { - AF_INET => { - assert!(addr_len as usize == std::mem::size_of::<sockaddr_in>()); + match addr.sa_family as i32 { + AF_INET => { + assert!(addr_len as usize == std::mem::size_of::<sockaddr_in>()); - SocketAddr::V4( - *(addr as *const _ as *const sockaddr_in as *const _), - ) - }, + let in4 = unsafe { *(addr as *const _ as *const sockaddr_in) }; - AF_INET6 => { - assert!(addr_len as usize == std::mem::size_of::<sockaddr_in6>()); + #[cfg(not(windows))] + let ip_addr = Ipv4Addr::from(u32::from_be(in4.sin_addr.s_addr)); + #[cfg(windows)] + let ip_addr = { + let ip_bytes = unsafe { in4.sin_addr.S_un.S_un_b() }; - SocketAddr::V6( - *(addr as *const _ as *const sockaddr_in6 as *const _), - ) - }, + Ipv4Addr::from([ + ip_bytes.s_b1, + ip_bytes.s_b2, + ip_bytes.s_b3, + ip_bytes.s_b4, + ]) + }; - _ => unimplemented!("unsupported address type"), - } - } -} + let port = u16::from_be(in4.sin_port); -fn std_addr_to_c(addr: &SocketAddr, out: &mut sockaddr_storage) -> socklen_t { - unsafe { - match addr { - SocketAddr::V4(addr) => { - let sa_len = std::mem::size_of::<sockaddr_in>(); + let out = SocketAddrV4::new(ip_addr, port); - let src = addr as *const _ as *const u8; - let dst = out as *mut _ as *mut u8; + out.into() + }, - std::ptr::copy_nonoverlapping(src, dst, sa_len); + AF_INET6 => { + assert!(addr_len as usize == std::mem::size_of::<sockaddr_in6>()); - sa_len as socklen_t - }, + let in6 = unsafe { *(addr as *const _ as *const sockaddr_in6) }; - SocketAddr::V6(addr) => { - let sa_len = std::mem::size_of::<sockaddr_in6>(); + let ip_addr = Ipv6Addr::from( + #[cfg(not(windows))] + in6.sin6_addr.s6_addr, + #[cfg(windows)] + *unsafe { in6.sin6_addr.u.Byte() }, + ); - let src = addr as *const _ as *const u8; - let dst = out as *mut _ as *mut u8; + let port = u16::from_be(in6.sin6_port); - std::ptr::copy_nonoverlapping(src, dst, sa_len); + #[cfg(not(windows))] + let scope_id = in6.sin6_scope_id; + #[cfg(windows)] + let scope_id = unsafe { *in6.u.sin6_scope_id() }; - sa_len as socklen_t - }, - } + let out = + SocketAddrV6::new(ip_addr, port, in6.sin6_flowinfo, scope_id); + + out.into() + }, + + _ => unimplemented!("unsupported address type"), + } +} + +fn std_addr_to_c(addr: &SocketAddr, out: &mut sockaddr_storage) -> socklen_t { + let sin_port = addr.port().to_be(); + + match addr { + SocketAddr::V4(addr) => unsafe { + let sa_len = std::mem::size_of::<sockaddr_in>(); + let out_in = out as *mut _ as *mut sockaddr_in; + + let s_addr = u32::from_ne_bytes(addr.ip().octets()); + + #[cfg(not(windows))] + let sin_addr = in_addr { s_addr }; + #[cfg(windows)] + let sin_addr = { + let mut s_un = std::mem::zeroed::<in_addr_S_un>(); + *s_un.S_addr_mut() = s_addr; + in_addr { S_un: s_un } + }; + + *out_in = sockaddr_in { + sin_family: AF_INET as sa_family_t, + + sin_addr, + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" + ))] + sin_len: sa_len as u8, + + sin_port, + + sin_zero: std::mem::zeroed(), + }; + + sa_len as socklen_t + }, + + SocketAddr::V6(addr) => unsafe { + let sa_len = std::mem::size_of::<sockaddr_in6>(); + let out_in6 = out as *mut _ as *mut sockaddr_in6; + + #[cfg(not(windows))] + let sin6_addr = in6_addr { + s6_addr: addr.ip().octets(), + }; + #[cfg(windows)] + let sin6_addr = { + let mut u = std::mem::zeroed::<in6_addr_u>(); + *u.Byte_mut() = addr.ip().octets(); + in6_addr { u } + }; + + #[cfg(windows)] + let u = { + let mut u = std::mem::zeroed::<SOCKADDR_IN6_LH_u>(); + *u.sin6_scope_id_mut() = addr.scope_id(); + u + }; + + *out_in6 = sockaddr_in6 { + sin6_family: AF_INET6 as sa_family_t, + + sin6_addr, + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" + ))] + sin6_len: sa_len as u8, + + sin6_port: sin_port, + + sin6_flowinfo: addr.flowinfo(), + + #[cfg(not(windows))] + sin6_scope_id: addr.scope_id(), + #[cfg(windows)] + u, + }; + + sa_len as socklen_t + }, } } @@ -1250,3 +1506,108 @@ fn std_time_to_c(_time: &std::time::Instant, out: &mut timespec) { out.tv_sec = 0; out.tv_nsec = 0; } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(windows)] + use winapi::um::ws2tcpip::inet_ntop; + + #[test] + fn addr_v4() { + let addr = "127.0.0.1:8080".parse().unwrap(); + + let mut out: sockaddr_storage = unsafe { std::mem::zeroed() }; + + assert_eq!( + std_addr_to_c(&addr, &mut out), + std::mem::size_of::<sockaddr_in>() as socklen_t + ); + + let s = std::ffi::CString::new("ddd.ddd.ddd.ddd").unwrap(); + + let s = unsafe { + let in_addr = &out as *const _ as *const sockaddr_in; + assert_eq!(u16::from_be((*in_addr).sin_port), addr.port()); + + let dst = s.into_raw(); + + inet_ntop( + AF_INET, + &((*in_addr).sin_addr) as *const _ as *const c_void, + dst, + 16, + ); + + std::ffi::CString::from_raw(dst).into_string().unwrap() + }; + + assert_eq!(s, "127.0.0.1"); + + let addr = unsafe { + std_addr_from_c( + &*(&out as *const _ as *const sockaddr), + std::mem::size_of::<sockaddr_in>() as socklen_t, + ) + }; + + assert_eq!(addr, "127.0.0.1:8080".parse().unwrap()); + } + + #[test] + fn addr_v6() { + let addr = "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080" + .parse() + .unwrap(); + + let mut out: sockaddr_storage = unsafe { std::mem::zeroed() }; + + assert_eq!( + std_addr_to_c(&addr, &mut out), + std::mem::size_of::<sockaddr_in6>() as socklen_t + ); + + let s = std::ffi::CString::new("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd") + .unwrap(); + + let s = unsafe { + let in6_addr = &out as *const _ as *const sockaddr_in6; + assert_eq!(u16::from_be((*in6_addr).sin6_port), addr.port()); + + let dst = s.into_raw(); + + inet_ntop( + AF_INET6, + &((*in6_addr).sin6_addr) as *const _ as *const c_void, + dst, + 45, + ); + + std::ffi::CString::from_raw(dst).into_string().unwrap() + }; + + assert_eq!(s, "2001:db8:85a3::8a2e:370:7334"); + + let addr = unsafe { + std_addr_from_c( + &*(&out as *const _ as *const sockaddr), + std::mem::size_of::<sockaddr_in6>() as socklen_t, + ) + }; + + assert_eq!( + addr, + "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080" + .parse() + .unwrap() + ); + } + + #[cfg(not(windows))] + extern { + fn inet_ntop( + af: c_int, src: *const c_void, dst: *mut c_char, size: socklen_t, + ) -> *mut c_char; + } +} diff --git a/src/frame.rs b/src/frame.rs index f568b29..2addb8d 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -47,14 +47,14 @@ pub const MAX_DGRAM_OVERHEAD: usize = 2; pub const MAX_STREAM_OVERHEAD: usize = 12; pub const MAX_STREAM_SIZE: u64 = 1 << 62; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct EcnCounts { ect0_count: u64, ect1_count: u64, ecn_ce_count: u64, } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub enum Frame { Padding { len: usize, @@ -185,8 +185,6 @@ impl Frame { ) -> Result<Frame> { let frame_type = b.get_varint()?; - // println!("GOT FRAME {:x}", frame_type); - let frame = match frame_type { 0x00 => { let mut len = 1; @@ -428,7 +426,7 @@ impl Frame { }, Frame::Crypto { data } => { - encode_crypto_header(data.off() as u64, data.len() as u64, b)?; + encode_crypto_header(data.off(), data.len() as u64, b)?; b.put_bytes(data)?; }, @@ -445,7 +443,7 @@ impl Frame { Frame::Stream { stream_id, data } => { encode_stream_header( *stream_id, - data.off() as u64, + data.off(), data.len() as u64, data.fin(), b, @@ -641,7 +639,7 @@ impl Frame { Frame::Crypto { data } => { 1 + // frame type - octets::varint_len(data.off() as u64) + // offset + octets::varint_len(data.off()) + // offset 2 + // length, always encode as 2-byte varint data.len() // data }, @@ -662,7 +660,7 @@ impl Frame { Frame::Stream { stream_id, data } => { 1 + // frame type octets::varint_len(*stream_id) + // stream_id - octets::varint_len(data.off() as u64) + // offset + octets::varint_len(data.off()) + // offset 2 + // length, always encode as 2-byte varint data.len() // data }, @@ -800,6 +798,16 @@ impl Frame { ) } + pub fn probing(&self) -> bool { + matches!( + self, + Frame::Padding { .. } | + Frame::NewConnectionId { .. } | + Frame::PathChallenge { .. } | + Frame::PathResponse { .. } + ) + } + #[cfg(feature = "qlog")] pub fn to_qlog(&self) -> QuicFrame { match self { @@ -866,18 +874,21 @@ impl Frame { Frame::NewToken { token } => QuicFrame::NewToken { token: qlog::Token { // TODO: pick the token type some how - ty: Some(qlog::TokenType::StatelessReset), - length: None, - data: qlog::HexSlice::maybe_string(Some(token)), + ty: Some(qlog::TokenType::Retry), + raw: Some(qlog::events::RawInfo { + data: qlog::HexSlice::maybe_string(Some(token)), + length: Some(token.len() as u64), + payload_length: None, + }), details: None, }, }, Frame::Stream { stream_id, data } => QuicFrame::Stream { stream_id: *stream_id, - offset: data.off() as u64, + offset: data.off(), length: data.len() as u64, - fin: data.fin().then(|| true), + fin: data.fin().then_some(true), raw: None, }, @@ -940,12 +951,9 @@ impl Frame { retire_prior_to: *retire_prior_to as u32, connection_id_length: Some(conn_id.len() as u8), connection_id: format!("{}", qlog::HexSlice::new(conn_id)), - stateless_reset_token: Some(qlog::Token { - ty: Some(qlog::TokenType::StatelessReset), - length: None, - data: qlog::HexSlice::maybe_string(Some(reset_token)), - details: None, - }), + stateless_reset_token: qlog::HexSlice::maybe_string(Some( + reset_token, + )), }, Frame::RetireConnectionId { seq_num } => @@ -963,8 +971,8 @@ impl Frame { } => QuicFrame::ConnectionClose { error_space: Some(ErrorSpace::TransportError), error_code: Some(*error_code), - raw_error_code: None, // raw error is no different for us - reason: Some(String::from_utf8(reason.clone()).unwrap()), + error_code_value: None, // raw error is no different for us + reason: Some(String::from_utf8_lossy(reason).into_owned()), trigger_frame_type: None, // don't know trigger type }, @@ -972,8 +980,8 @@ impl Frame { QuicFrame::ConnectionClose { error_space: Some(ErrorSpace::ApplicationError), error_code: Some(*error_code), - raw_error_code: None, // raw error is no different for us - reason: Some(String::from_utf8(reason.clone()).unwrap()), + error_code_value: None, // raw error is no different for us + reason: Some(String::from_utf8_lossy(reason).into_owned()), trigger_frame_type: None, // don't know trigger type }, @@ -996,7 +1004,7 @@ impl std::fmt::Debug for Frame { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Frame::Padding { len } => { - write!(f, "PADDING len={}", len)?; + write!(f, "PADDING len={len}")?; }, Frame::Ping => { @@ -1010,8 +1018,7 @@ impl std::fmt::Debug for Frame { } => { write!( f, - "ACK delay={} blocks={:?} ecn_counts={:?}", - ack_delay, ranges, ecn_counts + "ACK delay={ack_delay} blocks={ranges:?} ecn_counts={ecn_counts:?}" )?; }, @@ -1022,8 +1029,7 @@ impl std::fmt::Debug for Frame { } => { write!( f, - "RESET_STREAM stream={} err={:x} size={}", - stream_id, error_code, final_size + "RESET_STREAM stream={stream_id} err={error_code:x} size={final_size}" )?; }, @@ -1031,11 +1037,7 @@ impl std::fmt::Debug for Frame { stream_id, error_code, } => { - write!( - f, - "STOP_SENDING stream={} err={:x}", - stream_id, error_code - )?; + write!(f, "STOP_SENDING stream={stream_id} err={error_code:x}")?; }, Frame::Crypto { data } => { @@ -1043,7 +1045,7 @@ impl std::fmt::Debug for Frame { }, Frame::CryptoHeader { offset, length } => { - write!(f, "CRYPTO off={} len={}", offset, length)?; + write!(f, "CRYPTO off={offset} len={length}")?; }, Frame::NewToken { .. } => { @@ -1069,61 +1071,67 @@ impl std::fmt::Debug for Frame { } => { write!( f, - "STREAM id={} off={} len={} fin={}", - stream_id, offset, length, fin + "STREAM id={stream_id} off={offset} len={length} fin={fin}" )?; }, Frame::MaxData { max } => { - write!(f, "MAX_DATA max={}", max)?; + write!(f, "MAX_DATA max={max}")?; }, Frame::MaxStreamData { stream_id, max } => { - write!(f, "MAX_STREAM_DATA stream={} max={}", stream_id, max)?; + write!(f, "MAX_STREAM_DATA stream={stream_id} max={max}")?; }, Frame::MaxStreamsBidi { max } => { - write!(f, "MAX_STREAMS type=bidi max={}", max)?; + write!(f, "MAX_STREAMS type=bidi max={max}")?; }, Frame::MaxStreamsUni { max } => { - write!(f, "MAX_STREAMS type=uni max={}", max)?; + write!(f, "MAX_STREAMS type=uni max={max}")?; }, Frame::DataBlocked { limit } => { - write!(f, "DATA_BLOCKED limit={}", limit)?; + write!(f, "DATA_BLOCKED limit={limit}")?; }, Frame::StreamDataBlocked { stream_id, limit } => { write!( f, - "STREAM_DATA_BLOCKED stream={} limit={}", - stream_id, limit + "STREAM_DATA_BLOCKED stream={stream_id} limit={limit}" )?; }, Frame::StreamsBlockedBidi { limit } => { - write!(f, "STREAMS_BLOCKED type=bidi limit={}", limit)?; + write!(f, "STREAMS_BLOCKED type=bidi limit={limit}")?; }, Frame::StreamsBlockedUni { limit } => { - write!(f, "STREAMS_BLOCKED type=uni limit={}", limit)?; + write!(f, "STREAMS_BLOCKED type=uni limit={limit}")?; }, - Frame::NewConnectionId { .. } => { - write!(f, "NEW_CONNECTION_ID (TODO)")?; + Frame::NewConnectionId { + seq_num, + retire_prior_to, + conn_id, + reset_token, + } => { + write!( + f, + "NEW_CONNECTION_ID seq_num={seq_num} retire_prior_to={retire_prior_to} conn_id={conn_id:02x?} reset_token={reset_token:02x?}", + )?; }, - Frame::RetireConnectionId { .. } => { - write!(f, "RETIRE_CONNECTION_ID (TODO)")?; + Frame::RetireConnectionId { seq_num } => { + write!(f, "RETIRE_CONNECTION_ID seq_num={seq_num}")?; }, Frame::PathChallenge { data } => { - write!(f, "PATH_CHALLENGE data={:02x?}", data)?; + write!(f, "PATH_CHALLENGE data={data:02x?}")?; }, Frame::PathResponse { data } => { - write!(f, "PATH_RESPONSE data={:02x?}", data)?; + write!(f, "PATH_RESPONSE data={data:02x?}")?; }, Frame::ConnectionClose { @@ -1133,16 +1141,14 @@ impl std::fmt::Debug for Frame { } => { write!( f, - "CONNECTION_CLOSE err={:x} frame={:x} reason={:x?}", - error_code, frame_type, reason + "CONNECTION_CLOSE err={error_code:x} frame={frame_type:x} reason={reason:x?}" )?; }, Frame::ApplicationClose { error_code, reason } => { write!( f, - "APPLICATION_CLOSE err={:x} reason={:x?}", - error_code, reason + "APPLICATION_CLOSE err={error_code:x} reason={reason:x?}" )?; }, @@ -1155,7 +1161,7 @@ impl std::fmt::Debug for Frame { }, Frame::DatagramHeader { length } => { - write!(f, "DATAGRAM len={}", length)?; + write!(f, "DATAGRAM len={length}")?; }, } @@ -1360,7 +1366,7 @@ mod tests { }; assert_eq!(wire_len, 1); - assert_eq!(&d[..wire_len], [0x01 as u8]); + assert_eq!(&d[..wire_len], [0x01_u8]); let mut b = octets::Octets::with_slice(&d); assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame)); diff --git a/src/h3/ffi.rs b/src/h3/ffi.rs index d32b1be..f42e667 100644 --- a/src/h3/ffi.rs +++ b/src/h3/ffi.rs @@ -71,6 +71,13 @@ pub extern fn quiche_h3_config_set_qpack_blocked_streams( } #[no_mangle] +pub extern fn quiche_h3_config_enable_extended_connect( + config: &mut h3::Config, enabled: bool, +) { + config.enable_extended_connect(enabled); +} + +#[no_mangle] pub extern fn quiche_h3_config_free(config: *mut h3::Config) { unsafe { Box::from_raw(config) }; } @@ -192,6 +199,13 @@ pub extern fn quiche_h3_event_headers_has_body(ev: &h3::Event) -> bool { } #[no_mangle] +pub extern fn quiche_h3_extended_connect_enabled_by_peer( + conn: &h3::Connection, +) -> bool { + conn.extended_connect_enabled_by_peer() +} + +#[no_mangle] pub extern fn quiche_h3_event_free(ev: *mut h3::Event) { unsafe { Box::from_raw(ev) }; } @@ -308,6 +322,18 @@ pub extern fn quiche_h3_parse_extensible_priority( } #[no_mangle] +pub extern fn quiche_h3_send_priority_update_for_request( + conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64, + priority: &Priority, +) -> c_int { + match conn.send_priority_update_for_request(quic_conn, stream_id, priority) { + Ok(()) => 0, + + Err(e) => e.to_c() as c_int, + } +} + +#[no_mangle] pub extern fn quiche_h3_take_last_priority_update( conn: &mut h3::Connection, prioritized_element_id: u64, cb: extern fn( diff --git a/src/h3/frame.rs b/src/h3/frame.rs index 46b802d..76160fe 100644 --- a/src/h3/frame.rs +++ b/src/h3/frame.rs @@ -42,12 +42,13 @@ pub const PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID: u64 = 0xF0701; pub const SETTINGS_QPACK_MAX_TABLE_CAPACITY: u64 = 0x1; pub const SETTINGS_MAX_FIELD_SECTION_SIZE: u64 = 0x6; pub const SETTINGS_QPACK_BLOCKED_STREAMS: u64 = 0x7; +pub const SETTINGS_ENABLE_CONNECT_PROTOCOL: u64 = 0x8; pub const SETTINGS_H3_DATAGRAM: u64 = 0x276; // Permit between 16 maximally-encoded and 128 minimally-encoded SETTINGS. const MAX_SETTINGS_PAYLOAD_SIZE: usize = 256; -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub enum Frame { Data { payload: Vec<u8>, @@ -65,6 +66,7 @@ pub enum Frame { max_field_section_size: Option<u64>, qpack_max_table_capacity: Option<u64>, qpack_blocked_streams: Option<u64>, + connect_protocol_enabled: Option<u64>, h3_datagram: Option<u64>, grease: Option<(u64, u64)>, raw: Option<Vec<(u64, u64)>>, @@ -175,6 +177,7 @@ impl Frame { max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, + connect_protocol_enabled, h3_datagram, grease, .. @@ -196,6 +199,11 @@ impl Frame { len += octets::varint_len(*val); } + if let Some(val) = connect_protocol_enabled { + len += octets::varint_len(SETTINGS_ENABLE_CONNECT_PROTOCOL); + len += octets::varint_len(*val); + } + if let Some(val) = h3_datagram { len += octets::varint_len(SETTINGS_H3_DATAGRAM); len += octets::varint_len(*val); @@ -211,22 +219,27 @@ impl Frame { if let Some(val) = max_field_section_size { b.put_varint(SETTINGS_MAX_FIELD_SECTION_SIZE)?; - b.put_varint(*val as u64)?; + b.put_varint(*val)?; } if let Some(val) = qpack_max_table_capacity { b.put_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY)?; - b.put_varint(*val as u64)?; + b.put_varint(*val)?; } if let Some(val) = qpack_blocked_streams { b.put_varint(SETTINGS_QPACK_BLOCKED_STREAMS)?; - b.put_varint(*val as u64)?; + b.put_varint(*val)?; + } + + if let Some(val) = connect_protocol_enabled { + b.put_varint(SETTINGS_ENABLE_CONNECT_PROTOCOL)?; + b.put_varint(*val)?; } if let Some(val) = h3_datagram { b.put_varint(SETTINGS_H3_DATAGRAM)?; - b.put_varint(*val as u64)?; + b.put_varint(*val)?; } if let Some(val) = grease { @@ -271,7 +284,7 @@ impl Frame { b.put_varint(PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID)?; b.put_varint(len as u64)?; - b.put_varint(*prioritized_element_id as u64)?; + b.put_varint(*prioritized_element_id)?; b.put_bytes(priority_field_value)?; }, @@ -285,7 +298,7 @@ impl Frame { b.put_varint(PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID)?; b.put_varint(len as u64)?; - b.put_varint(*prioritized_element_id as u64)?; + b.put_varint(*prioritized_element_id)?; b.put_bytes(priority_field_value)?; }, @@ -297,6 +310,8 @@ impl Frame { #[cfg(feature = "qlog")] pub fn to_qlog(&self) -> Http3Frame { + use qlog::events::RawInfo; + match self { Frame::Data { .. } => Http3Frame::Data { raw: None }, @@ -312,6 +327,7 @@ impl Frame { max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, + connect_protocol_enabled, h3_datagram, grease, .. @@ -339,6 +355,13 @@ impl Frame { }); } + if let Some(v) = connect_protocol_enabled { + settings.push(qlog::events::h3::Setting { + name: "SETTINGS_ENABLE_CONNECT_PROTOCOL".to_string(), + value: *v, + }); + } + if let Some(v) = h3_datagram { settings.push(qlog::events::h3::Setting { name: "H3_DATAGRAM".to_string(), @@ -399,9 +422,12 @@ impl Frame { raw_type, payload_length, } => Http3Frame::Unknown { - raw_frame_type: *raw_type, - raw_length: Some(*payload_length as u32), - raw: None, + frame_type_value: *raw_type, + raw: Some(RawInfo { + data: None, + payload_length: Some(*payload_length), + length: None, + }), }, } } @@ -419,7 +445,7 @@ impl std::fmt::Debug for Frame { }, Frame::CancelPush { push_id } => { - write!(f, "CANCEL_PUSH push_id={}", push_id)?; + write!(f, "CANCEL_PUSH push_id={push_id}")?; }, Frame::Settings { @@ -429,7 +455,7 @@ impl std::fmt::Debug for Frame { raw, .. } => { - write!(f, "SETTINGS max_field_section={:?}, qpack_max_table={:?}, qpack_blocked={:?} raw={:?}", max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, raw)?; + write!(f, "SETTINGS max_field_section={max_field_section_size:?}, qpack_max_table={qpack_max_table_capacity:?}, qpack_blocked={qpack_blocked_streams:?} raw={raw:?}")?; }, Frame::PushPromise { @@ -445,11 +471,11 @@ impl std::fmt::Debug for Frame { }, Frame::GoAway { id } => { - write!(f, "GOAWAY id={}", id)?; + write!(f, "GOAWAY id={id}")?; }, Frame::MaxPushId { push_id } => { - write!(f, "MAX_PUSH_ID push_id={}", push_id)?; + write!(f, "MAX_PUSH_ID push_id={push_id}")?; }, Frame::PriorityUpdateRequest { @@ -477,7 +503,7 @@ impl std::fmt::Debug for Frame { }, Frame::Unknown { raw_type, .. } => { - write!(f, "UNKNOWN raw_type={}", raw_type,)?; + write!(f, "UNKNOWN raw_type={raw_type}",)?; }, } @@ -491,6 +517,7 @@ fn parse_settings_frame( let mut max_field_section_size = None; let mut qpack_max_table_capacity = None; let mut qpack_blocked_streams = None; + let mut connect_protocol_enabled = None; let mut h3_datagram = None; let mut raw = Vec::new(); @@ -520,6 +547,14 @@ fn parse_settings_frame( qpack_blocked_streams = Some(value); }, + SETTINGS_ENABLE_CONNECT_PROTOCOL => { + if value > 1 { + return Err(super::Error::SettingsError); + } + + connect_protocol_enabled = Some(value); + }, + SETTINGS_H3_DATAGRAM => { if value > 1 { return Err(super::Error::SettingsError); @@ -541,6 +576,7 @@ fn parse_settings_frame( max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, + connect_protocol_enabled, h3_datagram, grease: None, raw: Some(raw), @@ -680,6 +716,7 @@ mod tests { (SETTINGS_MAX_FIELD_SECTION_SIZE, 0), (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0), (SETTINGS_QPACK_BLOCKED_STREAMS, 0), + (SETTINGS_ENABLE_CONNECT_PROTOCOL, 0), (SETTINGS_H3_DATAGRAM, 0), ]; @@ -687,12 +724,13 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: Some(0), h3_datagram: Some(0), grease: None, raw: Some(raw_settings), }; - let frame_payload_len = 9; + let frame_payload_len = 11; let frame_header_len = 2; let wire_len = { @@ -721,6 +759,7 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: Some(0), h3_datagram: Some(0), grease: Some((33, 33)), raw: Default::default(), @@ -730,6 +769,7 @@ mod tests { (SETTINGS_MAX_FIELD_SECTION_SIZE, 0), (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0), (SETTINGS_QPACK_BLOCKED_STREAMS, 0), + (SETTINGS_ENABLE_CONNECT_PROTOCOL, 0), (SETTINGS_H3_DATAGRAM, 0), (33, 33), ]; @@ -740,12 +780,13 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: Some(0), h3_datagram: Some(0), grease: None, raw: Some(raw_settings), }; - let frame_payload_len = 11; + let frame_payload_len = 13; let frame_header_len = 2; let wire_len = { @@ -776,6 +817,7 @@ mod tests { max_field_section_size: Some(1024), qpack_max_table_capacity: None, qpack_blocked_streams: None, + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), @@ -803,6 +845,79 @@ mod tests { } #[test] + fn settings_h3_connect_protocol_enabled() { + let mut d = [42; 128]; + + let raw_settings = vec![(SETTINGS_ENABLE_CONNECT_PROTOCOL, 1)]; + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: Some(1), + h3_datagram: None, + grease: None, + raw: Some(raw_settings), + }; + + let frame_payload_len = 2; + let frame_header_len = 2; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(wire_len, frame_header_len + frame_payload_len); + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len as u64, + &d[frame_header_len..] + ) + .unwrap(), + frame + ); + } + + #[test] + fn settings_h3_connect_protocol_enabled_bad() { + let mut d = [42; 128]; + + let raw_settings = vec![(SETTINGS_ENABLE_CONNECT_PROTOCOL, 9)]; + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: Some(9), + h3_datagram: None, + grease: None, + raw: Some(raw_settings), + }; + + let frame_payload_len = 2; + let frame_header_len = 2; + + let wire_len = { + let mut b = octets::OctetsMut::with_slice(&mut d); + frame.to_bytes(&mut b).unwrap() + }; + + assert_eq!(wire_len, frame_header_len + frame_payload_len); + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len as u64, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + } + + #[test] fn settings_h3_dgram_only() { let mut d = [42; 128]; @@ -812,6 +927,7 @@ mod tests { max_field_section_size: None, qpack_max_table_capacity: None, qpack_blocked_streams: None, + connect_protocol_enabled: None, h3_datagram: Some(1), grease: None, raw: Some(raw_settings), @@ -846,6 +962,7 @@ mod tests { max_field_section_size: None, qpack_max_table_capacity: None, qpack_blocked_streams: None, + connect_protocol_enabled: None, h3_datagram: Some(5), grease: None, raw: Default::default(), @@ -884,6 +1001,7 @@ mod tests { max_field_section_size: None, qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), diff --git a/src/h3/mod.rs b/src/h3/mod.rs index f3d9e06..f5203d1 100644 --- a/src/h3/mod.rs +++ b/src/h3/mod.rs @@ -60,8 +60,9 @@ //! ```no_run //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config).unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config).unwrap(); //! # let h3_config = quiche::h3::Config::new()?; //! let h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?; //! # Ok::<(), quiche::h3::Error>(()) @@ -76,8 +77,9 @@ //! ```no_run //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let to = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::connect(None, &scid, to, &mut config).unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap(); //! # let h3_config = quiche::h3::Config::new()?; //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?; //! let req = vec![ @@ -98,8 +100,9 @@ //! ```no_run //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let to = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::connect(None, &scid, to, &mut config).unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap(); //! # let h3_config = quiche::h3::Config::new()?; //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?; //! let req = vec![ @@ -129,8 +132,9 @@ //! //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config).unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:1234".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config).unwrap(); //! # let h3_config = quiche::h3::Config::new()?; //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?; //! loop { @@ -197,8 +201,9 @@ //! //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let to = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::connect(None, &scid, to, &mut config).unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:1234".parse().unwrap(); +//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap(); //! # let h3_config = quiche::h3::Config::new()?; //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?; //! loop { @@ -285,6 +290,8 @@ use std::collections::VecDeque; #[cfg(feature = "sfv")] use std::convert::TryFrom; +use std::fmt; +use std::fmt::Write; #[cfg(feature = "qlog")] use qlog::events::h3::H3FrameCreated; @@ -293,6 +300,8 @@ use qlog::events::h3::H3FrameParsed; #[cfg(feature = "qlog")] use qlog::events::h3::H3Owner; #[cfg(feature = "qlog")] +use qlog::events::h3::H3PriorityTargetStreamType; +#[cfg(feature = "qlog")] use qlog::events::h3::H3StreamType; #[cfg(feature = "qlog")] use qlog::events::h3::H3StreamTypeSet; @@ -314,14 +323,14 @@ use qlog::events::EventType; /// /// [`Config::set_application_protos()`]: /// ../struct.Config.html#method.set_application_protos -pub const APPLICATION_PROTOCOL: &[u8] = b"\x02h3\x05h3-29\x05h3-28\x05h3-27"; +pub const APPLICATION_PROTOCOL: &[&[u8]] = &[b"h3", b"h3-29", b"h3-28", b"h3-27"]; // The offset used when converting HTTP/3 urgency to quiche urgency. const PRIORITY_URGENCY_OFFSET: u8 = 124; // Parameter values as specified in [Extensible Priorities]. // -// [Extensible Priorities]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority-12#section-4. +// [Extensible Priorities]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4. const PRIORITY_URGENCY_LOWER_BOUND: u8 = 0; const PRIORITY_URGENCY_UPPER_BOUND: u8 = 7; const PRIORITY_URGENCY_DEFAULT: u8 = 3; @@ -346,7 +355,7 @@ const QLOG_STREAM_TYPE_SET: EventType = pub type Result<T> = std::result::Result<T, Error>; /// An HTTP/3 error. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// There is no error or no work to do Done, @@ -474,7 +483,7 @@ impl Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -505,6 +514,7 @@ pub struct Config { max_field_section_size: Option<u64>, qpack_max_table_capacity: Option<u64>, qpack_blocked_streams: Option<u64>, + connect_protocol_enabled: Option<u64>, } impl Config { @@ -514,6 +524,7 @@ impl Config { max_field_section_size: None, qpack_max_table_capacity: None, qpack_blocked_streams: None, + connect_protocol_enabled: None, }) } @@ -543,6 +554,17 @@ impl Config { pub fn set_qpack_blocked_streams(&mut self, v: u64) { self.qpack_blocked_streams = Some(v); } + + /// Sets or omits the `SETTINGS_ENABLE_CONNECT_PROTOCOL` setting. + /// + /// The default value is `false`. + pub fn enable_extended_connect(&mut self, enabled: bool) { + if enabled { + self.connect_protocol_enabled = Some(1); + } else { + self.connect_protocol_enabled = None; + } + } } /// A trait for types with associated string name and value. @@ -554,10 +576,37 @@ pub trait NameValue { fn value(&self) -> &[u8]; } +impl NameValue for (&[u8], &[u8]) { + fn name(&self) -> &[u8] { + self.0 + } + + fn value(&self) -> &[u8] { + self.1 + } +} + /// An owned name-value pair representing a raw HTTP header. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct Header(Vec<u8>, Vec<u8>); +fn try_print_as_readable(hdr: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + match std::str::from_utf8(hdr) { + Ok(s) => f.write_str(&s.escape_default().to_string()), + Err(_) => write!(f, "{hdr:?}"), + } +} + +impl fmt::Debug for Header { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_char('"')?; + try_print_as_readable(&self.0, f)?; + f.write_str(": ")?; + try_print_as_readable(&self.1, f)?; + f.write_char('"') + } +} + impl Header { /// Creates a new header. /// @@ -578,7 +627,7 @@ impl NameValue for Header { } /// A non-owned name-value pair representing a raw HTTP header. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct HeaderRef<'a>(&'a [u8], &'a [u8]); impl<'a> HeaderRef<'a> { @@ -599,7 +648,7 @@ impl<'a> NameValue for HeaderRef<'a> { } /// An HTTP/3 connection event. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { /// Request/response headers were received. Headers { @@ -670,7 +719,7 @@ pub enum Event { /// Structured Fields Dictionary field value. I.e, use `TryFrom` to parse the /// value of a Priority header field or a PRIORITY_UPDATE frame. Using this /// trait requires the `sfv` feature to be enabled. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] #[repr(C)] pub struct Priority { urgency: u8, @@ -680,7 +729,7 @@ pub struct Priority { impl Default for Priority { fn default() -> Self { Priority { - urgency: PRIORITY_URGENCY_DEFAULT as u8, + urgency: PRIORITY_URGENCY_DEFAULT, incremental: PRIORITY_INCREMENTAL_DEFAULT, } } @@ -715,7 +764,7 @@ impl TryFrom<&[u8]> for Priority { /// /// Omitted parameters will yield default values. /// - /// [Extensible Priorities]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority-12#section-4. + /// [Extensible Priorities]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4. fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> { let dict = match sfv::Parser::parse_dictionary(value) { Ok(v) => v, @@ -758,7 +807,7 @@ impl TryFrom<&[u8]> for Priority { _ => false, }; - Ok(Priority::new(urgency as u8, incremental)) + Ok(Priority::new(urgency, incremental)) } } @@ -766,6 +815,7 @@ struct ConnectionSettings { pub max_field_section_size: Option<u64>, pub qpack_max_table_capacity: Option<u64>, pub qpack_blocked_streams: Option<u64>, + pub connect_protocol_enabled: Option<u64>, pub h3_datagram: Option<u64>, pub raw: Option<Vec<(u64, u64)>>, } @@ -828,6 +878,7 @@ impl Connection { max_field_section_size: config.max_field_section_size, qpack_max_table_capacity: config.qpack_max_table_capacity, qpack_blocked_streams: config.qpack_blocked_streams, + connect_protocol_enabled: config.connect_protocol_enabled, h3_datagram, raw: Default::default(), }, @@ -837,6 +888,7 @@ impl Connection { qpack_max_table_capacity: None, qpack_blocked_streams: None, h3_datagram: None, + connect_protocol_enabled: None, raw: Default::default(), }, @@ -877,12 +929,23 @@ impl Connection { /// On success the new connection is returned. /// /// The [`StreamLimit`] error is returned when the HTTP/3 control stream - /// cannot be created. + /// cannot be created due to stream limits. /// - /// [`StreamLimit`]: ../enum.Error.html#variant.InvalidState + /// The [`InternalError`] error is returned when either the underlying QUIC + /// connection is not in a suitable state, or the HTTP/3 control stream + /// cannot be created due to flow control limits. + /// + /// [`StreamLimit`]: ../enum.Error.html#variant.StreamLimit + /// [`InternalError`]: ../enum.Error.html#variant.InternalError pub fn with_transport( conn: &mut super::Connection, config: &Config, ) -> Result<Connection> { + let is_client = !conn.is_server; + if is_client && !(conn.is_established() || conn.is_in_early_data()) { + trace!("{} QUIC connection must be established or in early data before creating an HTTP/3 connection", conn.trace_id()); + return Err(Error::InternalError); + } + let mut http3_conn = Connection::new(config, conn.is_server, conn.dgram_enabled())?; @@ -1004,7 +1067,7 @@ impl Connection { /// reported as writable again. /// /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked - /// [Extensible Priority]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority-12#section-4. + /// [Extensible Priority]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4. pub fn send_response_with_priority<T: NameValue>( &mut self, conn: &mut super::Connection, stream_id: u64, headers: &[T], priority: &Priority, fin: bool, @@ -1183,14 +1246,9 @@ impl Connection { }, }; - if stream_cap < overhead + body.len() { - // Ensure the peer is notified that the connection or stream is - // blocked when the stream's capacity is limited by flow control. - let _ = conn.stream_writable(stream_id, overhead + body.len()); - } - // Make sure there is enough capacity to send the DATA frame header. if stream_cap < overhead { + let _ = conn.stream_writable(stream_id, overhead + 1); return Err(Error::Done); } @@ -1203,6 +1261,7 @@ impl Connection { // Again, avoid sending 0-length DATA frames when the fin flag is false. if body_len == 0 && !fin { + let _ = conn.stream_writable(stream_id, overhead + 1); return Err(Error::Done); } @@ -1235,6 +1294,16 @@ impl Connection { q.add_event_data_now(ev_data).ok(); }); + if written < body.len() { + // Ensure the peer is notified that the connection or stream is + // blocked when the stream's capacity is limited by flow control. + // + // We only need enough capacity to send a few bytes, to make sure + // the stream doesn't hang due to congestion window not growing + // enough. + let _ = conn.stream_writable(stream_id, overhead + 1); + } + if fin && written == body.len() && conn.stream_finished(stream_id) { self.streams.remove(&stream_id); } @@ -1254,12 +1323,23 @@ impl Connection { conn.dgram_max_writable_len().is_some() } + /// Returns whether the peer enabled extended CONNECT support. + /// + /// Support is signalled by the peer's SETTINGS, so this method always + /// returns false until they have been processed using the [`poll()`] + /// method. + /// + /// [`poll()`]: struct.Connection.html#method.poll + pub fn extended_connect_enabled_by_peer(&self) -> bool { + self.peer_settings.connect_protocol_enabled == Some(1) + } + /// Sends an HTTP/3 DATAGRAM with the specified flow ID. pub fn send_dgram( &mut self, conn: &mut super::Connection, flow_id: u64, buf: &[u8], ) -> Result<()> { let len = octets::varint_len(flow_id) + buf.len(); - let mut d = vec![0; len as usize]; + let mut d = vec![0; len]; let mut b = octets::OctetsMut::with_slice(&mut d); b.put_varint(flow_id)?; @@ -1311,29 +1391,19 @@ impl Connection { fn process_dgrams( &mut self, conn: &mut super::Connection, ) -> Result<(u64, Event)> { - let mut d = [0; 8]; - - match conn.dgram_recv_peek(&mut d, 8) { - Ok(_) => { - if self.dgram_event_triggered { - return Err(Error::Done); - } - - self.dgram_event_triggered = true; + if conn.dgram_recv_queue_len() > 0 { + if self.dgram_event_triggered { + return Err(Error::Done); + } - Ok((0, Event::Datagram)) - }, + self.dgram_event_triggered = true; - Err(crate::Error::Done) => { - // The dgram recv queue is empty, so re-arm the Datagram event - // so it is issued next time a DATAGRAM is received. - self.dgram_event_triggered = false; + return Ok((0, Event::Datagram)); + } - Err(Error::Done) - }, + self.dgram_event_triggered = false; - Err(e) => Err(Error::TransportError(e)), - } + Err(Error::Done) } /// Reads request or response body data into the provided buffer. @@ -1407,6 +1477,108 @@ impl Connection { Ok(total) } + /// Sends a PRIORITY_UPDATE frame on the control stream with specified + /// request stream ID and priority. + /// + /// The `priority` parameter represents [Extensible Priority] + /// parameters. If the urgency is outside the range 0-7, it will be clamped + /// to 7. + /// + /// The [`StreamBlocked`] error is returned when the underlying QUIC stream + /// doesn't have enough capacity for the operation to complete. When this + /// happens the application should retry the operation once the stream is + /// reported as writable again. + /// + /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked + /// [Extensible Priority]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4. + pub fn send_priority_update_for_request( + &mut self, conn: &mut super::Connection, stream_id: u64, + priority: &Priority, + ) -> Result<()> { + let mut d = [42; 20]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + // Validate that it is sane to send PRIORITY_UPDATE. + if self.is_server { + return Err(Error::FrameUnexpected); + } + + if stream_id % 4 != 0 { + return Err(Error::FrameUnexpected); + } + + let control_stream_id = + self.control_stream_id.ok_or(Error::FrameUnexpected)?; + + let urgency = priority + .urgency + .clamp(PRIORITY_URGENCY_LOWER_BOUND, PRIORITY_URGENCY_UPPER_BOUND); + + let mut field_value = format!("u={urgency}"); + + if priority.incremental { + field_value.push_str(",i"); + } + + let priority_field_value = field_value.as_bytes(); + let frame_payload_len = + octets::varint_len(stream_id) + priority_field_value.len(); + + let overhead = + octets::varint_len(frame::PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID) + + octets::varint_len(stream_id) + + octets::varint_len(frame_payload_len as u64); + + // Make sure the control stream has enough capacity. + match conn.stream_writable( + control_stream_id, + overhead + priority_field_value.len(), + ) { + Ok(true) => (), + + Ok(false) => return Err(Error::StreamBlocked), + + Err(e) => { + return Err(e.into()); + }, + } + + b.put_varint(frame::PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID)?; + b.put_varint(frame_payload_len as u64)?; + b.put_varint(stream_id)?; + let off = b.off(); + conn.stream_send(control_stream_id, &d[..off], false)?; + + // Sending field value separately avoids unnecessary copy. + conn.stream_send(control_stream_id, priority_field_value, false)?; + + trace!( + "{} tx frm PRIORITY_UPDATE request_stream={} priority_field_value={}", + conn.trace_id(), + stream_id, + field_value, + ); + + qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, { + let frame = Http3Frame::PriorityUpdate { + target_stream_type: H3PriorityTargetStreamType::Request, + prioritized_element_id: stream_id, + priority_field_value: field_value.clone(), + }; + + let ev_data = EventData::H3FrameCreated(H3FrameCreated { + stream_id, + length: Some(priority_field_value.len() as u64), + frame, + raw: None, + }); + + q.add_event_data_now(ev_data).ok(); + }); + + Ok(()) + } + /// Take the last PRIORITY_UPDATE for a prioritized element ID. /// /// When the [`poll()`] method returns a [`PriorityUpdate`] event for a @@ -1686,8 +1858,7 @@ impl Connection { let ev_data = EventData::H3StreamTypeSet(H3StreamTypeSet { stream_id, owner: Some(H3Owner::Local), - old: None, - new: H3StreamType::QpackEncode, + stream_type: H3StreamType::QpackEncode, associated_push_id: None, }); @@ -1709,8 +1880,7 @@ impl Connection { let ev_data = EventData::H3StreamTypeSet(H3StreamTypeSet { stream_id, owner: Some(H3Owner::Local), - old: None, - new: H3StreamType::QpackDecode, + stream_type: H3StreamType::QpackDecode, associated_push_id: None, }); @@ -1825,8 +1995,7 @@ impl Connection { let ev_data = EventData::H3StreamTypeSet(H3StreamTypeSet { stream_id, owner: Some(H3Owner::Local), - old: None, - new: H3StreamType::Unknown, + stream_type: H3StreamType::Unknown, associated_push_id: None, }); @@ -1848,8 +2017,21 @@ impl Connection { /// Sends SETTINGS frame based on HTTP/3 configuration. fn send_settings(&mut self, conn: &mut super::Connection) -> Result<()> { - let stream_id = - self.open_uni_stream(conn, stream::HTTP3_CONTROL_STREAM_TYPE_ID)?; + let stream_id = match self + .open_uni_stream(conn, stream::HTTP3_CONTROL_STREAM_TYPE_ID) + { + Ok(v) => v, + + Err(e) => { + trace!("{} Control stream blocked", conn.trace_id(),); + + if e == Error::Done { + return Err(Error::InternalError); + } + + return Err(e); + }, + }; self.control_stream_id = Some(stream_id); @@ -1857,8 +2039,7 @@ impl Connection { let ev_data = EventData::H3StreamTypeSet(H3StreamTypeSet { stream_id, owner: Some(H3Owner::Local), - old: None, - new: H3StreamType::Control, + stream_type: H3StreamType::Control, associated_push_id: None, }); @@ -1877,6 +2058,9 @@ impl Connection { .local_settings .qpack_max_table_capacity, qpack_blocked_streams: self.local_settings.qpack_blocked_streams, + connect_protocol_enabled: self + .local_settings + .connect_protocol_enabled, h3_datagram: self.local_settings.h3_datagram, grease, raw: Default::default(), @@ -1983,8 +2167,7 @@ impl Connection { EventData::H3StreamTypeSet(H3StreamTypeSet { stream_id, owner: Some(H3Owner::Remote), - old: None, - new: ty.to_qlog(), + stream_type: ty.to_qlog(), associated_push_id: None, }); @@ -2095,7 +2278,7 @@ impl Connection { match stream.set_frame_type(varint) { Err(Error::FrameUnexpected) => { - let msg = format!("Unexpected frame type {}", varint); + let msg = format!("Unexpected frame type {varint}"); conn.close( true, @@ -2188,7 +2371,15 @@ impl Connection { { Ok(ev) => return Ok(ev), - Err(Error::Done) => (), + Err(Error::Done) => { + // This might be a frame that is processed internally + // without needing to bubble up to the user as an + // event. Check whether the frame has FIN'd by QUIC + // to prevent trying to read again on a closed stream. + if conn.stream_finished(stream_id) { + break; + } + }, Err(e) => return Err(e), }; @@ -2288,6 +2479,7 @@ impl Connection { max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, + connect_protocol_enabled, h3_datagram, raw, .. @@ -2296,6 +2488,7 @@ impl Connection { max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, + connect_protocol_enabled, h3_datagram, raw, }; @@ -2330,7 +2523,7 @@ impl Connection { let max_size = self .local_settings .max_field_section_size - .unwrap_or(std::u64::MAX); + .unwrap_or(u64::MAX); let headers = match self .qpack_decoder @@ -2657,11 +2850,11 @@ pub mod testing { } impl Session { - pub fn default() -> Result<Session> { + pub fn new() -> Result<Session> { let mut config = crate::Config::new(crate::PROTOCOL_VERSION)?; config.load_cert_chain_from_pem_file("examples/cert.crt")?; config.load_priv_key_from_pem_file("examples/cert.key")?; - config.set_application_protos(b"\x02h3")?; + config.set_application_protos(&[b"h3"])?; config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -2969,9 +3162,87 @@ mod tests { } #[test] + fn h3_handshake_0rtt() { + let mut buf = [0; 65535]; + + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_bidi_local(15); + config.set_initial_max_stream_data_bidi_remote(15); + config.set_initial_max_stream_data_uni(15); + config.set_initial_max_streams_bidi(3); + config.set_initial_max_streams_uni(3); + config.enable_early_data(); + config.verify_peer(false); + + let h3_config = Config::new().unwrap(); + + // Perform initial handshake. + let mut pipe = crate::testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Extract session, + let session = pipe.client.session().unwrap(); + + // Configure session on new connection. + let mut pipe = crate::testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.client.set_session(session), Ok(())); + + // Can't create an H3 connection until the QUIC connection is determined + // to have made sufficient early data progress. + assert!(matches!( + Connection::with_transport(&mut pipe.client, &h3_config), + Err(Error::InternalError) + )); + + // Client sends initial flight. + let (len, _) = pipe.client.send(&mut buf).unwrap(); + + // Now an H3 connection can be created. + assert!(matches!( + Connection::with_transport(&mut pipe.client, &h3_config), + Ok(_) + )); + assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); + + // Client sends 0-RTT packet. + let pkt_type = crate::packet::Type::ZeroRTT; + + let frames = [crate::frame::Frame::Stream { + stream_id: 6, + data: crate::stream::RangeBuf::from(b"aaaaa", 0, true), + }]; + + assert_eq!( + pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), + Ok(1200) + ); + + assert_eq!(pipe.server.undecryptable_pkts.len(), 0); + + // 0-RTT stream data is readable. + let mut r = pipe.server.readable(); + assert_eq!(r.next(), Some(6)); + assert_eq!(r.next(), None); + + let mut b = [0; 15]; + assert_eq!(pipe.server.stream_recv(6, &mut b), Ok((5, true))); + assert_eq!(&b[..5], b"aaaaa"); + } + + #[test] /// Send a request with no body, get a response with no body. fn request_no_body_response_no_body() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(true).unwrap(); @@ -3001,7 +3272,7 @@ mod tests { #[test] /// Send a request with no body, get a response with one DATA frame. fn request_no_body_response_one_chunk() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(true).unwrap(); @@ -3039,7 +3310,7 @@ mod tests { #[test] /// Send a request with no body, get a response with multiple DATA frames. fn request_no_body_response_many_chunks() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(true).unwrap(); @@ -3084,7 +3355,7 @@ mod tests { #[test] /// Send a request with one DATA frame, get a response with no body. fn request_one_chunk_response_no_body() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -3119,7 +3390,7 @@ mod tests { #[test] /// Send a request with multiple DATA frames, get a response with no body. fn request_many_chunks_response_no_body() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -3164,7 +3435,7 @@ mod tests { /// Send a request with multiple DATA frames, get a response with one DATA /// frame. fn many_requests_many_chunks_response_one_chunk() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut reqs = Vec::new(); @@ -3238,7 +3509,7 @@ mod tests { /// Send a request with no body, get a response with one DATA frame and an /// empty FIN after reception from the client. fn request_no_body_response_one_chunk_empty_fin() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(true).unwrap(); @@ -3275,9 +3546,57 @@ mod tests { } #[test] + /// Send a request with no body, get a response with no body followed by + /// GREASE that is STREAM frame with a FIN. + fn request_no_body_response_no_body_with_grease() { + let mut s = Session::new().unwrap(); + s.handshake().unwrap(); + + let (stream, req) = s.send_request(true).unwrap(); + + assert_eq!(stream, 0); + + let ev_headers = Event::Headers { + list: req, + has_body: false, + }; + + assert_eq!(s.poll_server(), Ok((stream, ev_headers))); + assert_eq!(s.poll_server(), Ok((stream, Event::Finished))); + + let resp = s.send_response(stream, false).unwrap(); + + // Note that "has_body" is a misnomer, there will never be a body in + // this test. There's other work that will fix this, once it lands + // remove this comment. + let ev_headers = Event::Headers { + list: resp, + has_body: true, + }; + + // Inject a GREASE frame + let mut d = [42; 10]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let frame_type = b.put_varint(148_764_065_110_560_899).unwrap(); + s.pipe.server.stream_send(0, frame_type, false).unwrap(); + + let frame_len = b.put_varint(10).unwrap(); + s.pipe.server.stream_send(0, frame_len, false).unwrap(); + + s.pipe.server.stream_send(0, &d, true).unwrap(); + + s.advance().ok(); + + assert_eq!(s.poll_client(), Ok((stream, ev_headers))); + assert_eq!(s.poll_client(), Ok((stream, Event::Finished))); + assert_eq!(s.poll_client(), Err(Error::Done)); + } + + #[test] /// Try to send DATA frames before HEADERS. fn body_response_before_headers() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(true).unwrap(); @@ -3304,7 +3623,7 @@ mod tests { /// Try to send DATA frames on wrong streams, ensure the API returns an /// error before anything hits the transport layer. fn send_body_invalid_client_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); assert_eq!(s.send_body_client(0, true), Err(Error::FrameUnexpected)); @@ -3356,7 +3675,7 @@ mod tests { /// Try to send DATA frames on wrong streams, ensure the API returns an /// error before anything hits the transport layer. fn send_body_invalid_server_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); assert_eq!(s.send_body_server(0, true), Err(Error::FrameUnexpected)); @@ -3407,7 +3726,7 @@ mod tests { #[test] /// Send a MAX_PUSH_ID frame from the client on a valid stream. fn max_push_id_from_client_good() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -3423,7 +3742,7 @@ mod tests { #[test] /// Send a MAX_PUSH_ID frame from the client on an invalid stream. fn max_push_id_from_client_bad_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -3448,7 +3767,7 @@ mod tests { /// Send a sequence of MAX_PUSH_ID frames from the client that attempt to /// reduce the limit. fn max_push_id_from_client_limit_reduction() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -3471,7 +3790,7 @@ mod tests { #[test] /// Send a MAX_PUSH_ID frame from the server, which is forbidden. fn max_push_id_from_server() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -3487,7 +3806,7 @@ mod tests { #[test] /// Send a PUSH_PROMISE frame from the client, which is forbidden. fn push_promise_from_client() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -3516,7 +3835,7 @@ mod tests { #[test] /// Send a CANCEL_PUSH frame from the client. fn cancel_push_from_client() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -3532,7 +3851,7 @@ mod tests { #[test] /// Send a CANCEL_PUSH frame from the client on an invalid stream. fn cancel_push_from_client_bad_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -3556,7 +3875,7 @@ mod tests { #[test] /// Send a CANCEL_PUSH frame from the client. fn cancel_push_from_server() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -3572,7 +3891,7 @@ mod tests { #[test] /// Send a GOAWAY frame from the client. fn goaway_from_client_good() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.client.send_goaway(&mut s.pipe.client, 100).unwrap(); @@ -3586,7 +3905,7 @@ mod tests { #[test] /// Send a GOAWAY frame from the server. fn goaway_from_server_good() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.server.send_goaway(&mut s.pipe.server, 4000).unwrap(); @@ -3599,7 +3918,7 @@ mod tests { #[test] /// A client MUST NOT send a request after it receives GOAWAY. fn client_request_after_goaway() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.server.send_goaway(&mut s.pipe.server, 4000).unwrap(); @@ -3614,7 +3933,7 @@ mod tests { #[test] /// Send a GOAWAY frame from the server, using an invalid goaway ID. fn goaway_from_server_invalid_id() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -3631,7 +3950,7 @@ mod tests { /// Send multiple GOAWAY frames from the server, that increase the goaway /// ID. fn goaway_from_server_increase_id() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -3742,18 +4061,16 @@ mod tests { #[test] /// Send a PRIORITY_UPDATE for request stream from the client. fn priority_update_request() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); @@ -3762,33 +4079,27 @@ mod tests { #[test] /// Send a PRIORITY_UPDATE for request stream from the client. fn priority_update_single_stream_rearm() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); - // Once the PriorityUpdate event was fired, subsequent frames will not - // rearm it. - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=5".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 5, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Err(Error::Done)); @@ -3797,15 +4108,13 @@ mod tests { assert_eq!(s.server.take_last_priority_update(0), Ok(b"u=5".to_vec())); assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done)); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=7".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 7, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); @@ -3818,44 +4127,38 @@ mod tests { /// Send multiple PRIORITY_UPDATE frames for different streams from the /// client across multiple flights of exchange. fn priority_update_request_multiple_stream_arm_multiple_flights() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 4, - priority_field_value: b"u=1".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 4, &Priority { + urgency: 1, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((4, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 8, - priority_field_value: b"u=2".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 8, &Priority { + urgency: 2, + incremental: false, + }) + .unwrap(); + s.advance().ok(); assert_eq!(s.poll_server(), Ok((8, Event::PriorityUpdate))); assert_eq!(s.poll_server(), Err(Error::Done)); @@ -3870,7 +4173,7 @@ mod tests { /// Send multiple PRIORITY_UPDATE frames for different streams from the /// client across a single flight. fn priority_update_request_multiple_stream_arm_single_flight() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut d = [42; 65535]; @@ -3920,18 +4223,16 @@ mod tests { /// Send a PRIORITY_UPDATE for a request stream, before and after the stream /// has been completed. fn priority_update_request_collected_completed() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); let (stream, req) = s.send_request(true).unwrap(); let ev_headers = Event::Headers { @@ -3960,15 +4261,13 @@ mod tests { assert_eq!(s.poll_client(), Err(Error::Done)); // Now send a PRIORITY_UPDATE for the completed request stream. - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); // No event generated at server assert_eq!(s.poll_server(), Err(Error::Done)); @@ -3978,18 +4277,16 @@ mod tests { /// Send a PRIORITY_UPDATE for a request stream, before and after the stream /// has been stopped. fn priority_update_request_collected_stopped() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); let (stream, req) = s.send_request(false).unwrap(); let ev_headers = Event::Headers { @@ -4020,15 +4317,13 @@ mod tests { assert_eq!(s.poll_server(), Err(Error::Done)); // Now send a PRIORITY_UPDATE for the closed request stream. - s.send_frame_client( - frame::Frame::PriorityUpdateRequest { - prioritized_element_id: 0, - priority_field_value: b"u=3".to_vec(), - }, - s.client.control_stream_id.unwrap(), - false, - ) - .unwrap(); + s.client + .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority { + urgency: 3, + incremental: false, + }) + .unwrap(); + s.advance().ok(); // No event generated at server assert_eq!(s.poll_server(), Err(Error::Done)); @@ -4037,7 +4332,7 @@ mod tests { #[test] /// Send a PRIORITY_UPDATE for push stream from the client. fn priority_update_push() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -4057,7 +4352,7 @@ mod tests { /// Send a PRIORITY_UPDATE for request stream from the client but for an /// incorrect stream type. fn priority_update_request_bad_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -4077,7 +4372,7 @@ mod tests { /// Send a PRIORITY_UPDATE for push stream from the client but for an /// incorrect stream type. fn priority_update_push_bad_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_client( @@ -4096,7 +4391,7 @@ mod tests { #[test] /// Send a PRIORITY_UPDATE for request stream from the server. fn priority_update_request_from_server() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -4115,7 +4410,7 @@ mod tests { #[test] /// Send a PRIORITY_UPDATE for request stream from the server. fn priority_update_push_from_server() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); s.send_frame_server( @@ -4146,7 +4441,7 @@ mod tests { #[test] /// Client opens multiple control streams, which is forbidden. fn open_multiple_control_streams() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let stream_id = s.client.next_uni_stream_id; @@ -4171,7 +4466,7 @@ mod tests { #[test] /// Client closes the control stream, which is forbidden. fn close_control_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut control_stream_closed = false; @@ -4206,7 +4501,7 @@ mod tests { #[test] /// Client closes QPACK stream, which is forbidden. fn close_qpack_stream() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut qpack_stream_closed = false; @@ -4244,7 +4539,7 @@ mod tests { fn qpack_data() { // TODO: QPACK instructions are ignored until dynamic table support is // added so we just test that the data is safely ignored. - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let e_stream_id = s.client.local_qpack_streams.encoder_stream_id.unwrap(); @@ -4275,7 +4570,7 @@ mod tests { #[test] /// Tests limits for the stream state buffer maximum size. fn max_state_buf_size() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let req = vec![ @@ -4317,7 +4612,7 @@ mod tests { assert_eq!(s.server.poll(&mut s.pipe.server), Ok((0, Event::Data))); // GREASE frames consume the state buffer, so need to be limited. - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut d = [42; 128]; @@ -4342,7 +4637,7 @@ mod tests { fn stream_backpressure() { let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -4404,7 +4699,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4448,7 +4743,7 @@ mod tests { #[test] /// Tests that Error::TransportError contains a transport error. fn transport_error() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let req = vec![ @@ -4484,7 +4779,7 @@ mod tests { #[test] /// Tests that sending DATA before HEADERS causes an error. fn data_before_headers() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut d = [42; 128]; @@ -4507,9 +4802,9 @@ mod tests { } #[test] - /// Tests that calling poll() after an error occured does nothing. + /// Tests that calling poll() after an error occurred does nothing. fn poll_after_error() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let mut d = [42; 128]; @@ -4541,7 +4836,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4570,10 +4865,17 @@ mod tests { Err(Error::StreamBlocked) ); + // Clear the writable stream queue. + assert_eq!(s.pipe.client.stream_writable_next(), Some(10)); + assert_eq!(s.pipe.client.stream_writable_next(), Some(2)); + assert_eq!(s.pipe.client.stream_writable_next(), Some(6)); + assert_eq!(s.pipe.client.stream_writable_next(), None); + s.advance().ok(); // Once the server gives flow control credits back, we can send the // request. + assert_eq!(s.pipe.client.stream_writable_next(), Some(4)); assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(4)); } @@ -4587,7 +4889,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4621,6 +4923,7 @@ mod tests { s.client.send_request(&mut s.pipe.client, &req, true), Err(Error::StreamBlocked) ); + assert_eq!(s.pipe.client.stream_writable_next(), None); // Emit the control stream data and drain it at the server via poll() to // consumes it via poll() and gives back flow control. @@ -4629,13 +4932,301 @@ mod tests { s.advance().ok(); // Now we can send the request. + assert_eq!(s.pipe.client.stream_writable_next(), Some(0)); assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(0)); } #[test] + /// Ensure STREAM_DATA_BLOCKED is not emitted multiple times with the same + /// offset when trying to send large bodies. + fn send_body_truncation_stream_blocked() { + use crate::testing::decode_pkt; + + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); + config.set_initial_max_data(10000); // large connection-level flow control + config.set_initial_max_stream_data_bidi_local(80); + config.set_initial_max_stream_data_bidi_remote(80); + config.set_initial_max_stream_data_uni(150); + config.set_initial_max_streams_bidi(100); + config.set_initial_max_streams_uni(5); + config.verify_peer(false); + + let mut h3_config = Config::new().unwrap(); + + let mut s = Session::with_configs(&mut config, &mut h3_config).unwrap(); + + s.handshake().unwrap(); + + let (stream, req) = s.send_request(true).unwrap(); + + let ev_headers = Event::Headers { + list: req, + has_body: false, + }; + + assert_eq!(s.poll_server(), Ok((stream, ev_headers))); + assert_eq!(s.poll_server(), Ok((stream, Event::Finished))); + + let _ = s.send_response(stream, false).unwrap(); + + assert_eq!(s.pipe.server.streams.blocked().len(), 0); + + // The body must be larger than the stream window would allow + let d = [42; 500]; + let mut off = 0; + + let sent = s + .server + .send_body(&mut s.pipe.server, stream, &d, true) + .unwrap(); + assert_eq!(sent, 25); + off += sent; + + // send_body wrote as much as it could (sent < size of buff). + assert_eq!(s.pipe.server.streams.blocked().len(), 1); + assert_eq!( + s.server + .send_body(&mut s.pipe.server, stream, &d[off..], true), + Err(Error::Done) + ); + assert_eq!(s.pipe.server.streams.blocked().len(), 1); + + // Now read raw frames to see what the QUIC layer did + let mut buf = [0; 65535]; + let (len, _) = s.pipe.server.send(&mut buf).unwrap(); + + let frames = decode_pkt(&mut s.pipe.client, &mut buf, len).unwrap(); + + let mut iter = frames.iter(); + + assert_eq!( + iter.next(), + Some(&crate::frame::Frame::StreamDataBlocked { + stream_id: 0, + limit: 80, + }) + ); + + // At the server, after sending the STREAM_DATA_BLOCKED frame, we clear + // the mark. + assert_eq!(s.pipe.server.streams.blocked().len(), 0); + + // Don't read any data from the client, so stream flow control is never + // given back in the form of changing the stream's max offset. + // Subsequent body send operations will still fail but no more + // STREAM_DATA_BLOCKED frames should be submitted since the limit didn't + // change. No frames means no packet to send. + assert_eq!( + s.server + .send_body(&mut s.pipe.server, stream, &d[off..], true), + Err(Error::Done) + ); + assert_eq!(s.pipe.server.streams.blocked().len(), 0); + assert_eq!(s.pipe.server.send(&mut buf), Err(crate::Error::Done)); + + // Now update the client's max offset manually. + let frames = [crate::frame::Frame::MaxStreamData { + stream_id: 0, + max: 100, + }]; + + let pkt_type = crate::packet::Type::Short; + assert_eq!( + s.pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), + Ok(39), + ); + + let sent = s + .server + .send_body(&mut s.pipe.server, stream, &d[off..], true) + .unwrap(); + assert_eq!(sent, 18); + + // Same thing here... + assert_eq!(s.pipe.server.streams.blocked().len(), 1); + assert_eq!( + s.server + .send_body(&mut s.pipe.server, stream, &d[off..], true), + Err(Error::Done) + ); + assert_eq!(s.pipe.server.streams.blocked().len(), 1); + + let (len, _) = s.pipe.server.send(&mut buf).unwrap(); + + let frames = decode_pkt(&mut s.pipe.client, &mut buf, len).unwrap(); + + let mut iter = frames.iter(); + + assert_eq!( + iter.next(), + Some(&crate::frame::Frame::StreamDataBlocked { + stream_id: 0, + limit: 100, + }) + ); + } + + #[test] + /// Ensure stream doesn't hang due to small cwnd. + fn send_body_stream_blocked_by_small_cwnd() { + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); + config.set_initial_max_data(100000); // large connection-level flow control + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(50000); + config.set_initial_max_stream_data_uni(150); + config.set_initial_max_streams_bidi(100); + config.set_initial_max_streams_uni(5); + config.verify_peer(false); + + let mut h3_config = Config::new().unwrap(); + + let mut s = Session::with_configs(&mut config, &mut h3_config).unwrap(); + + s.handshake().unwrap(); + + let (stream, req) = s.send_request(true).unwrap(); + + let ev_headers = Event::Headers { + list: req, + has_body: false, + }; + + assert_eq!(s.poll_server(), Ok((stream, ev_headers))); + assert_eq!(s.poll_server(), Ok((stream, Event::Finished))); + + let _ = s.send_response(stream, false).unwrap(); + + // Clear the writable stream queue. + assert_eq!(s.pipe.server.stream_writable_next(), Some(stream)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(11)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(3)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(7)); + assert_eq!(s.pipe.server.stream_writable_next(), None); + + // The body must be larger than the cwnd would allow. + let send_buf = [42; 80000]; + + let sent = s + .server + .send_body(&mut s.pipe.server, stream, &send_buf, true) + .unwrap(); + + // send_body wrote as much as it could (sent < size of buff). + assert_eq!(sent, 11995); + + s.advance().ok(); + + // Client reads received headers and body. + let mut recv_buf = [42; 80000]; + assert!(s.poll_client().is_ok()); + assert_eq!(s.poll_client(), Ok((stream, Event::Data))); + assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(11995)); + + s.advance().ok(); + + // Server send cap is smaller than remaining body buffer. + assert!(s.pipe.server.tx_cap < send_buf.len() - sent); + + // Once the server cwnd opens up, we can send more body. + assert_eq!(s.pipe.server.stream_writable_next(), Some(0)); + } + + #[test] + /// Ensure stream doesn't hang due to small cwnd. + fn send_body_stream_blocked_zero_length() { + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); + config.set_initial_max_data(100000); // large connection-level flow control + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(50000); + config.set_initial_max_stream_data_uni(150); + config.set_initial_max_streams_bidi(100); + config.set_initial_max_streams_uni(5); + config.verify_peer(false); + + let mut h3_config = Config::new().unwrap(); + + let mut s = Session::with_configs(&mut config, &mut h3_config).unwrap(); + + s.handshake().unwrap(); + + let (stream, req) = s.send_request(true).unwrap(); + + let ev_headers = Event::Headers { + list: req, + has_body: false, + }; + + assert_eq!(s.poll_server(), Ok((stream, ev_headers))); + assert_eq!(s.poll_server(), Ok((stream, Event::Finished))); + + let _ = s.send_response(stream, false).unwrap(); + + // Clear the writable stream queue. + assert_eq!(s.pipe.server.stream_writable_next(), Some(stream)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(11)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(3)); + assert_eq!(s.pipe.server.stream_writable_next(), Some(7)); + assert_eq!(s.pipe.server.stream_writable_next(), None); + + // The body is large enough to fill the cwnd, except for enough bytes + // for another DATA frame header (but no payload). + let send_buf = [42; 11994]; + + let sent = s + .server + .send_body(&mut s.pipe.server, stream, &send_buf, false) + .unwrap(); + + assert_eq!(sent, 11994); + + // There is only enough capacity left for the DATA frame header, but + // no payload. + assert_eq!(s.pipe.server.stream_capacity(stream).unwrap(), 3); + assert_eq!( + s.server + .send_body(&mut s.pipe.server, stream, &send_buf, false), + Err(Error::Done) + ); + + s.advance().ok(); + + // Client reads received headers and body. + let mut recv_buf = [42; 80000]; + assert!(s.poll_client().is_ok()); + assert_eq!(s.poll_client(), Ok((stream, Event::Data))); + assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(11994)); + + s.advance().ok(); + + // Once the server cwnd opens up, we can send more body. + assert_eq!(s.pipe.server.stream_writable_next(), Some(0)); + } + + #[test] /// Test handling of 0-length DATA writes with and without fin. fn zero_length_data() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -4697,7 +5288,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(69); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4729,13 +5320,50 @@ mod tests { Err(Error::Done) ); + // Clear the writable stream queue. + assert_eq!(s.pipe.client.stream_writable_next(), Some(10)); + assert_eq!(s.pipe.client.stream_writable_next(), Some(2)); + assert_eq!(s.pipe.client.stream_writable_next(), Some(6)); + assert_eq!(s.pipe.client.stream_writable_next(), None); + s.advance().ok(); // Once the server gives flow control credits back, we can send the body. + assert_eq!(s.pipe.client.stream_writable_next(), Some(0)); assert_eq!(s.client.send_body(&mut s.pipe.client, 0, b"", true), Ok(0)); } #[test] + /// Tests that receiving an empty SETTINGS frame is handled and reported. + fn empty_settings() { + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); + config.set_initial_max_data(1500); + config.set_initial_max_stream_data_bidi_local(150); + config.set_initial_max_stream_data_bidi_remote(150); + config.set_initial_max_stream_data_uni(150); + config.set_initial_max_streams_bidi(5); + config.set_initial_max_streams_uni(5); + config.verify_peer(false); + config.set_ack_delay_exponent(8); + config.grease(false); + + let h3_config = Config::new().unwrap(); + let mut s = Session::with_configs(&mut config, &h3_config).unwrap(); + + s.handshake().unwrap(); + + assert!(s.client.peer_settings_raw().is_some()); + assert!(s.server.peer_settings_raw().is_some()); + } + + #[test] /// Tests that receiving a H3_DATAGRAM setting is ok. fn dgram_setting() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); @@ -4745,7 +5373,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4790,7 +5418,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4817,6 +5445,7 @@ mod tests { max_field_section_size: None, qpack_max_table_capacity: None, qpack_blocked_streams: None, + connect_protocol_enabled: None, h3_datagram: Some(1), grease: None, raw: Default::default(), @@ -4840,7 +5469,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -4905,7 +5534,7 @@ mod tests { /// Send a single DATAGRAM. fn single_dgram() { let mut buf = [0; 65535]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // We'll send default data of 10 bytes on flow ID 0. @@ -4926,7 +5555,7 @@ mod tests { /// Send multiple DATAGRAMs. fn multiple_dgram() { let mut buf = [0; 65535]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // We'll send default data of 10 bytes on flow ID 0. @@ -4966,7 +5595,7 @@ mod tests { /// Send more DATAGRAMs than the send queue allows. fn multiple_dgram_overflow() { let mut buf = [0; 65535]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // We'll send default data of 10 bytes on flow ID 0. @@ -5002,7 +5631,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -5050,7 +5679,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -5142,7 +5771,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -5284,7 +5913,7 @@ mod tests { /// Tests that the Finished event is not issued for streams of unknown type /// (e.g. GREASE). fn finished_is_for_requests() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); assert_eq!(s.poll_client(), Err(Error::Done)); @@ -5300,7 +5929,7 @@ mod tests { #[test] /// Tests that streams are marked as finished only once. fn finished_once() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -5328,7 +5957,7 @@ mod tests { fn data_event_rearm() { let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); let (stream, req) = s.send_request(false).unwrap(); @@ -5494,7 +6123,7 @@ mod tests { config .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); - config.set_application_protos(b"\x02h3").unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); config.set_initial_max_stream_data_bidi_remote(150); @@ -5568,7 +6197,7 @@ mod tests { fn reset_stream() { let mut buf = [0; 65535]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // Client sends request. @@ -5622,7 +6251,7 @@ mod tests { #[test] fn reset_finished_at_server() { - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // Client sends HEADERS and doesn't fin @@ -5663,7 +6292,7 @@ mod tests { #[test] fn reset_finished_at_client() { let mut buf = [0; 65535]; - let mut s = Session::default().unwrap(); + let mut s = Session::new().unwrap(); s.handshake().unwrap(); // Client sends HEADERS and doesn't fin diff --git a/src/h3/qpack/encoder.rs b/src/h3/qpack/encoder.rs index ae75f27..85c8637 100644 --- a/src/h3/qpack/encoder.rs +++ b/src/h3/qpack/encoder.rs @@ -73,12 +73,27 @@ impl Encoder { None => { // Encode as fully literal. - let name_len = - super::huffman::encode_output_length(h.name(), true)?; - encode_int(name_len as u64, LITERAL | 0x08, 3, &mut b)?; - - super::huffman::encode(h.name(), &mut b, true)?; + // Huffman-encoding generally saves space but in some cases + // it doesn't, for those just encode the literal string. + match super::huffman::encode_output_length(h.name(), true) { + Ok(len) => { + encode_int(len as u64, LITERAL | 0x08, 3, &mut b)?; + super::huffman::encode(h.name(), &mut b, true)?; + }, + + Err(super::Error::InflatedHuffmanEncoding) => { + encode_int( + h.name().len() as u64, + LITERAL, + 3, + &mut b, + )?; + b.put_bytes(&h.name().to_ascii_lowercase())?; + }, + + Err(e) => return Err(e), + } encode_str(h.value(), 7, &mut b)?; }, @@ -143,11 +158,21 @@ fn encode_int( } fn encode_str(v: &[u8], prefix: usize, b: &mut octets::OctetsMut) -> Result<()> { - let len = super::huffman::encode_output_length(v, false)?; - - encode_int(len as u64, 0x80, prefix, b)?; - - super::huffman::encode(v, b, false)?; + // Huffman-encoding generally saves space but in some cases it doesn't, for + // those just encode the literal string. + match super::huffman::encode_output_length(v, false) { + Ok(len) => { + encode_int(len as u64, 0x80, prefix, b)?; + super::huffman::encode(v, b, false)?; + }, + + Err(super::Error::InflatedHuffmanEncoding) => { + encode_int(v.len() as u64, 0, prefix, b)?; + b.put_bytes(v)?; + }, + + Err(e) => return Err(e), + } Ok(()) } diff --git a/src/h3/qpack/huffman/mod.rs b/src/h3/qpack/huffman/mod.rs index 04391ab..a222c6d 100644 --- a/src/h3/qpack/huffman/mod.rs +++ b/src/h3/qpack/huffman/mod.rs @@ -101,6 +101,10 @@ pub fn encode_output_length(src: &[u8], low: bool) -> Result<usize> { len += 1; } + if len > src.len() { + return Err(Error::InflatedHuffmanEncoding); + } + Ok(len) } diff --git a/src/h3/qpack/mod.rs b/src/h3/qpack/mod.rs index 8a14aa3..4ce54a9 100644 --- a/src/h3/qpack/mod.rs +++ b/src/h3/qpack/mod.rs @@ -40,11 +40,14 @@ const LITERAL_WITH_NAME_REF: u8 = 0b0100_0000; pub type Result<T> = std::result::Result<T, Error>; /// A QPACK error. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// The provided buffer is too short. BufferTooShort, + /// The provided string would be larger after huffman encoding. + InflatedHuffmanEncoding, + /// The QPACK header block's huffman encoding is invalid. InvalidHuffmanEncoding, @@ -60,7 +63,7 @@ pub enum Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -102,7 +105,7 @@ mod tests { assert_eq!(enc.encode(&headers, &mut encoded), Ok(240)); let mut dec = Decoder::new(); - assert_eq!(dec.decode(&mut encoded, std::u64::MAX), Ok(headers)); + assert_eq!(dec.decode(&mut encoded, u64::MAX), Ok(headers)); } #[test] @@ -130,7 +133,7 @@ mod tests { assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35)); let mut dec = Decoder::new(); - let headers_out = dec.decode(&mut encoded, std::u64::MAX).unwrap(); + let headers_out = dec.decode(&mut encoded, u64::MAX).unwrap(); assert_eq!(headers_expected, headers_out); @@ -147,10 +150,48 @@ mod tests { assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35)); let mut dec = Decoder::new(); - let headers_out = dec.decode(&mut encoded, std::u64::MAX).unwrap(); + let headers_out = dec.decode(&mut encoded, u64::MAX).unwrap(); assert_eq!(headers_expected, headers_out); } + + #[test] + fn lower_ascii_range() { + let mut encoded = [0u8; 50]; + let mut enc = Encoder::new(); + + // Indexed name with literal value + let headers1 = vec![crate::h3::Header::new(b"location", b" ")]; + assert_eq!(enc.encode(&headers1, &mut encoded), Ok(19)); + + // Literal name and value + let headers2 = vec![crate::h3::Header::new(b"a", b"")]; + assert_eq!(enc.encode(&headers2, &mut encoded), Ok(20)); + + let headers3 = vec![crate::h3::Header::new(b" ", b"hello")]; + assert_eq!(enc.encode(&headers3, &mut encoded), Ok(24)); + } + + #[test] + fn extended_ascii_range() { + let mut encoded = [0u8; 50]; + let mut enc = Encoder::new(); + + let name = b"location"; + let value = "£££££££££££££££"; + + // Indexed name with literal value + let headers1 = vec![crate::h3::Header::new(name, value.as_bytes())]; + assert_eq!(enc.encode(&headers1, &mut encoded), Ok(34)); + + // Literal name and value + let value = "ððððððððððððððð"; + let headers2 = vec![crate::h3::Header::new(b"a", value.as_bytes())]; + assert_eq!(enc.encode(&headers2, &mut encoded), Ok(35)); + + let headers3 = vec![crate::h3::Header::new(value.as_bytes(), b"hello")]; + assert_eq!(enc.encode(&headers3, &mut encoded), Ok(39)); + } } pub use decoder::Decoder; diff --git a/src/h3/stream.rs b/src/h3/stream.rs index cd2c10e..f23bf34 100644 --- a/src/h3/stream.rs +++ b/src/h3/stream.rs @@ -36,7 +36,7 @@ pub const QPACK_DECODER_STREAM_TYPE_ID: u64 = 0x3; const MAX_STATE_BUF_SIZE: usize = (1 << 24) - 1; -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Type { Control, Request, @@ -51,7 +51,7 @@ impl Type { pub fn to_qlog(self) -> qlog::events::h3::H3StreamType { match self { Type::Control => qlog::events::h3::H3StreamType::Control, - Type::Request => qlog::events::h3::H3StreamType::Data, + Type::Request => qlog::events::h3::H3StreamType::Request, Type::Push => qlog::events::h3::H3StreamType::Push, Type::QpackEncoder => qlog::events::h3::H3StreamType::QpackEncode, Type::QpackDecoder => qlog::events::h3::H3StreamType::QpackDecode, @@ -60,7 +60,7 @@ impl Type { } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum State { /// Reading the stream's type. StreamType, @@ -354,13 +354,25 @@ impl Stream { assert_eq!(self.state, State::FramePayloadLen); // Only expect frames on Control, Request and Push streams. - if self.ty == Some(Type::Control) || - self.ty == Some(Type::Request) || - self.ty == Some(Type::Push) - { + if matches!(self.ty, Some(Type::Control | Type::Request | Type::Push)) { let (state, resize) = match self.frame_type { Some(frame::DATA_FRAME_TYPE_ID) => (State::Data, false), + // These frame types can never have 0 payload length because + // they always have fields that must be populated. + Some( + frame::GOAWAY_FRAME_TYPE_ID | + frame::PUSH_PROMISE_FRAME_TYPE_ID | + frame::CANCEL_PUSH_FRAME_TYPE_ID | + frame::MAX_PUSH_FRAME_TYPE_ID, + ) => { + if len == 0 { + return Err(Error::FrameError); + } + + (State::FramePayload, true) + }, + _ => (State::FramePayload, true), }; @@ -380,6 +392,11 @@ impl Stream { pub fn try_fill_buffer( &mut self, conn: &mut crate::Connection, ) -> Result<()> { + // If no bytes are required to be read, return early. + if self.state_buffer_complete() { + return Ok(()); + } + let buf = &mut self.state_buf[self.state_off..self.state_len]; let read = match conn.stream_recv(self.id, buf) { @@ -431,6 +448,11 @@ impl Stream { fn try_fill_buffer_for_tests( &mut self, stream: &mut std::io::Cursor<Vec<u8>>, ) -> Result<()> { + // If no bytes are required to be read, return early + if self.state_buffer_complete() { + return Ok(()); + } + let buf = &mut self.state_buf[self.state_off..self.state_len]; let read = std::io::Read::read(stream, buf).unwrap(); @@ -613,12 +635,57 @@ mod tests { use super::*; + fn open_uni(b: &mut octets::OctetsMut, ty: u64) -> Result<Stream> { + let stream = Stream::new(2, false); + assert_eq!(stream.state, State::StreamType); + + b.put_varint(ty)?; + + Ok(stream) + } + + fn parse_uni( + stream: &mut Stream, ty: u64, cursor: &mut std::io::Cursor<Vec<u8>>, + ) -> Result<()> { + stream.try_fill_buffer_for_tests(cursor)?; + + let stream_ty = stream.try_consume_varint()?; + assert_eq!(stream_ty, ty); + stream.set_ty(Type::deserialize(stream_ty).unwrap())?; + + Ok(()) + } + + fn parse_skip_frame( + stream: &mut Stream, cursor: &mut std::io::Cursor<Vec<u8>>, + ) -> Result<()> { + // Parse the frame type. + stream.try_fill_buffer_for_tests(cursor)?; + + let frame_ty = stream.try_consume_varint()?; + + stream.set_frame_type(frame_ty)?; + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse the frame payload length. + stream.try_fill_buffer_for_tests(cursor)?; + + let frame_payload_len = stream.try_consume_varint()?; + stream.set_frame_payload_len(frame_payload_len)?; + assert_eq!(stream.state, State::FramePayload); + + // Parse the frame payload. + stream.try_fill_buffer_for_tests(cursor)?; + + stream.try_consume_frame()?; + assert_eq!(stream.state, State::FrameType); + + Ok(()) + } + #[test] /// Process incoming SETTINGS frame on control stream. fn control_good() { - let mut stream = Stream::new(3, false); - assert_eq!(stream.state, State::StreamType); - let mut d = vec![42; 40]; let mut b = octets::OctetsMut::with_slice(&mut d); @@ -632,23 +699,18 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), }; - b.put_varint(HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); frame.to_bytes(&mut b).unwrap(); let mut cursor = std::io::Cursor::new(d); - // Parse stream type. - stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); - - let stream_ty = stream.try_consume_varint().unwrap(); - assert_eq!(stream_ty, HTTP3_CONTROL_STREAM_TYPE_ID); - stream - .set_ty(Type::deserialize(stream_ty).unwrap()) + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) .unwrap(); assert_eq!(stream.state, State::FrameType); @@ -677,11 +739,57 @@ mod tests { } #[test] + /// Process incoming empty SETTINGS frame on control stream. + fn control_empty_settings() { + let mut d = vec![42; 40]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: None, + h3_datagram: None, + grease: None, + raw: Some(vec![]), + }; + + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + frame.to_bytes(&mut b).unwrap(); + + let mut cursor = std::io::Cursor::new(d); + + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) + .unwrap(); + assert_eq!(stream.state, State::FrameType); + + // Parse the SETTINGS frame type. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + + let frame_ty = stream.try_consume_varint().unwrap(); + assert_eq!(frame_ty, frame::SETTINGS_FRAME_TYPE_ID); + + stream.set_frame_type(frame_ty).unwrap(); + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse the SETTINGS frame payload length. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + + let frame_payload_len = stream.try_consume_varint().unwrap(); + assert_eq!(frame_payload_len, 0); + stream.set_frame_payload_len(frame_payload_len).unwrap(); + assert_eq!(stream.state, State::FramePayload); + + // Parse the SETTINGS frame payload. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + + assert_eq!(stream.try_consume_frame(), Ok((frame, 0))); + assert_eq!(stream.state, State::FrameType); + } + + #[test] /// Process duplicate SETTINGS frame on control stream. fn control_bad_multiple_settings() { - let mut stream = Stream::new(3, false); - assert_eq!(stream.state, State::StreamType); - let mut d = vec![42; 40]; let mut b = octets::OctetsMut::with_slice(&mut d); @@ -695,24 +803,19 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), }; - b.put_varint(HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); frame.to_bytes(&mut b).unwrap(); frame.to_bytes(&mut b).unwrap(); let mut cursor = std::io::Cursor::new(d); - // Parse stream type. - stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); - - let stream_ty = stream.try_consume_varint().unwrap(); - assert_eq!(stream_ty, HTTP3_CONTROL_STREAM_TYPE_ID); - stream - .set_ty(Type::deserialize(stream_ty).unwrap()) + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) .unwrap(); assert_eq!(stream.state, State::FrameType); @@ -749,9 +852,6 @@ mod tests { #[test] /// Process other frame before SETTINGS frame on control stream. fn control_bad_late_settings() { - let mut stream = Stream::new(3, false); - assert_eq!(stream.state, State::StreamType); - let mut d = vec![42; 40]; let mut b = octets::OctetsMut::with_slice(&mut d); @@ -767,24 +867,19 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), }; - b.put_varint(HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); goaway.to_bytes(&mut b).unwrap(); settings.to_bytes(&mut b).unwrap(); let mut cursor = std::io::Cursor::new(d); - // Parse stream type. - stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); - - let stream_ty = stream.try_consume_varint().unwrap(); - assert_eq!(stream_ty, HTTP3_CONTROL_STREAM_TYPE_ID); - stream - .set_ty(Type::deserialize(stream_ty).unwrap()) + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) .unwrap(); assert_eq!(stream.state, State::FrameType); @@ -798,9 +893,6 @@ mod tests { #[test] /// Process not-allowed frame on control stream. fn control_bad_frame() { - let mut stream = Stream::new(3, false); - assert_eq!(stream.state, State::StreamType); - let mut d = vec![42; 40]; let mut b = octets::OctetsMut::with_slice(&mut d); @@ -818,24 +910,21 @@ mod tests { max_field_section_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + connect_protocol_enabled: None, h3_datagram: None, grease: None, raw: Some(raw_settings), }; - b.put_varint(HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); settings.to_bytes(&mut b).unwrap(); hdrs.to_bytes(&mut b).unwrap(); let mut cursor = std::io::Cursor::new(d); - // Parse stream type. - stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); - - let stream_ty = stream.try_consume_varint().unwrap(); - stream - .set_ty(Type::deserialize(stream_ty).unwrap()) + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) .unwrap(); + assert_eq!(stream.state, State::FrameType); // Parse first SETTINGS frame. stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); @@ -943,8 +1032,6 @@ mod tests { #[test] fn push_good() { - let mut stream = Stream::new(2, false); - let mut d = vec![42; 128]; let mut b = octets::OctetsMut::with_slice(&mut d); @@ -955,21 +1042,14 @@ mod tests { payload: payload.clone(), }; - b.put_varint(HTTP3_PUSH_STREAM_TYPE_ID).unwrap(); + let mut stream = open_uni(&mut b, HTTP3_PUSH_STREAM_TYPE_ID).unwrap(); b.put_varint(1).unwrap(); hdrs.to_bytes(&mut b).unwrap(); data.to_bytes(&mut b).unwrap(); let mut cursor = std::io::Cursor::new(d); - // Parse stream type. - stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); - - let stream_ty = stream.try_consume_varint().unwrap(); - assert_eq!(stream_ty, HTTP3_PUSH_STREAM_TYPE_ID); - stream - .set_ty(Type::deserialize(stream_ty).unwrap()) - .unwrap(); + parse_uni(&mut stream, HTTP3_PUSH_STREAM_TYPE_ID, &mut cursor).unwrap(); assert_eq!(stream.state, State::PushId); // Parse push ID. @@ -1036,12 +1116,10 @@ mod tests { #[test] fn grease() { - let mut stream = Stream::new(2, false); - let mut d = vec![42; 20]; let mut b = octets::OctetsMut::with_slice(&mut d); - b.put_varint(33).unwrap(); + let mut stream = open_uni(&mut b, 33).unwrap(); let mut cursor = std::io::Cursor::new(d); @@ -1079,4 +1157,178 @@ mod tests { assert_eq!(stream.set_frame_type(frame_ty), Err(Error::FrameUnexpected)); } + + #[test] + fn zero_length_goaway() { + let mut d = vec![42; 128]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: None, + h3_datagram: None, + grease: None, + raw: Some(vec![]), + }; + + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + frame.to_bytes(&mut b).unwrap(); + + // Write a 0-length payload frame. + b.put_varint(frame::GOAWAY_FRAME_TYPE_ID).unwrap(); + b.put_varint(0).unwrap(); + + let mut cursor = std::io::Cursor::new(d); + + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) + .unwrap(); + + // Skip SETTINGS frame type. + parse_skip_frame(&mut stream, &mut cursor).unwrap(); + + // Parse frame type. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_ty = stream.try_consume_varint().unwrap(); + assert_eq!(frame_ty, frame::GOAWAY_FRAME_TYPE_ID); + + stream.set_frame_type(frame_ty).unwrap(); + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse frame payload length. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_payload_len = stream.try_consume_varint().unwrap(); + assert_eq!( + Err(Error::FrameError), + stream.set_frame_payload_len(frame_payload_len) + ); + } + + #[test] + fn zero_length_push_promise() { + let mut d = vec![42; 128]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let mut stream = Stream::new(0, false); + + assert_eq!(stream.ty, Some(Type::Request)); + assert_eq!(stream.state, State::FrameType); + + // Write a 0-length payload frame. + b.put_varint(frame::PUSH_PROMISE_FRAME_TYPE_ID).unwrap(); + b.put_varint(0).unwrap(); + + let mut cursor = std::io::Cursor::new(d); + + // Parse frame type. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_ty = stream.try_consume_varint().unwrap(); + assert_eq!(frame_ty, frame::PUSH_PROMISE_FRAME_TYPE_ID); + + stream.set_frame_type(frame_ty).unwrap(); + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse frame payload length. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_payload_len = stream.try_consume_varint().unwrap(); + assert_eq!( + Err(Error::FrameError), + stream.set_frame_payload_len(frame_payload_len) + ); + } + + #[test] + fn zero_length_cancel_push() { + let mut d = vec![42; 128]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: None, + h3_datagram: None, + grease: None, + raw: Some(vec![]), + }; + + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + frame.to_bytes(&mut b).unwrap(); + + // Write a 0-length payload frame. + b.put_varint(frame::CANCEL_PUSH_FRAME_TYPE_ID).unwrap(); + b.put_varint(0).unwrap(); + + let mut cursor = std::io::Cursor::new(d); + + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) + .unwrap(); + + // Skip SETTINGS frame type. + parse_skip_frame(&mut stream, &mut cursor).unwrap(); + + // Parse frame type. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_ty = stream.try_consume_varint().unwrap(); + assert_eq!(frame_ty, frame::CANCEL_PUSH_FRAME_TYPE_ID); + + stream.set_frame_type(frame_ty).unwrap(); + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse frame payload length. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_payload_len = stream.try_consume_varint().unwrap(); + assert_eq!( + Err(Error::FrameError), + stream.set_frame_payload_len(frame_payload_len) + ); + } + + #[test] + fn zero_length_max_push_id() { + let mut d = vec![42; 128]; + let mut b = octets::OctetsMut::with_slice(&mut d); + + let frame = Frame::Settings { + max_field_section_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + connect_protocol_enabled: None, + h3_datagram: None, + grease: None, + raw: Some(vec![]), + }; + + let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(); + frame.to_bytes(&mut b).unwrap(); + + // Write a 0-length payload frame. + b.put_varint(frame::MAX_PUSH_FRAME_TYPE_ID).unwrap(); + b.put_varint(0).unwrap(); + + let mut cursor = std::io::Cursor::new(d); + + parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor) + .unwrap(); + + // Skip SETTINGS frame type. + parse_skip_frame(&mut stream, &mut cursor).unwrap(); + + // Parse frame type. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_ty = stream.try_consume_varint().unwrap(); + assert_eq!(frame_ty, frame::MAX_PUSH_FRAME_TYPE_ID); + + stream.set_frame_type(frame_ty).unwrap(); + assert_eq!(stream.state, State::FramePayloadLen); + + // Parse frame payload length. + stream.try_fill_buffer_for_tests(&mut cursor).unwrap(); + let frame_payload_len = stream.try_consume_varint().unwrap(); + assert_eq!( + Err(Error::FrameError), + stream.set_frame_payload_len(frame_payload_len) + ); + } } @@ -35,18 +35,46 @@ //! [quiche]: https://github.com/cloudflare/quiche/ //! [ietf]: https://quicwg.org/ //! -//! ## Connection setup +//! ## Configuring connections //! //! The first step in establishing a QUIC connection using quiche is creating a -//! configuration object: +//! [`Config`] object: //! //! ``` -//! let config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; +//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; +//! config.set_application_protos(&[b"example-proto"]); +//! +//! // Additional configuration specific to application and use case... //! # Ok::<(), quiche::Error>(()) //! ``` //! -//! This is shared among multiple connections and can be used to configure a -//! QUIC endpoint. +//! The [`Config`] object controls important aspects of the QUIC connection such +//! as QUIC version, ALPN IDs, flow control, congestion control, idle timeout +//! and other properties or features. +//! +//! QUIC is a general-purpose transport protocol and there are several +//! configuration properties where there is no reasonable default value. For +//! example, the permitted number of concurrent streams of any particular type +//! is dependent on the application running over QUIC, and other use-case +//! specific concerns. +//! +//! quiche defaults several properties to zero, applications most likely need +//! to set these to something else to satisfy their needs using the following: +//! +//! - [`set_initial_max_streams_bidi()`] +//! - [`set_initial_max_streams_uni()`] +//! - [`set_initial_max_data()`] +//! - [`set_initial_max_stream_data_bidi_local()`] +//! - [`set_initial_max_stream_data_bidi_remote()`] +//! - [`set_initial_max_stream_data_uni()`] +//! +//! [`Config`] also holds TLS configuration. This can be changed by mutators on +//! the an existing object, or by constructing a TLS context manually and +//! creating a configuration using [`with_boring_ssl_ctx()`]. +//! +//! A configuration object can be shared among multiple connections. +//! +//! ### Connection setup //! //! On the client-side the [`connect()`] utility function can be used to create //! a new connection, while [`accept()`] is for servers: @@ -55,13 +83,16 @@ //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let server_name = "quic.tech"; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let to = "127.0.0.1:1234".parse().unwrap(); +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); //! // Client connection. -//! let conn = quiche::connect(Some(&server_name), &scid, to, &mut config)?; +//! let conn = +//! quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?; //! //! // Server connection. -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! let conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! let conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! # Ok::<(), quiche::Error>(()) //! ``` //! @@ -83,12 +114,15 @@ //! # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; +//! let to = socket.local_addr().unwrap(); +//! //! loop { //! let (read, from) = socket.recv_from(&mut buf).unwrap(); //! -//! let recv_info = quiche::RecvInfo { from }; +//! let recv_info = quiche::RecvInfo { from, to }; //! //! let read = match conn.recv(&mut buf[..read], recv_info) { //! Ok(v) => v, @@ -121,8 +155,9 @@ //! # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! loop { //! let (write, send_info) = match conn.send(&mut out) { //! Ok(v) => v, @@ -154,8 +189,9 @@ //! ``` //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! let timeout = conn.timeout(); //! # Ok::<(), quiche::Error>(()) //! ``` @@ -170,8 +206,9 @@ //! # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! // Timeout expired, handle it. //! conn.on_timeout(); //! @@ -225,8 +262,9 @@ //! ```no_run //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! if conn.is_established() { //! // Handshake completed, send some data on stream 0. //! conn.stream_send(0, b"hello", true)?; @@ -245,8 +283,9 @@ //! # let mut buf = [0; 512]; //! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; //! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -//! # let from = "127.0.0.1:1234".parse().unwrap(); -//! # let mut conn = quiche::accept(&scid, None, from, &mut config)?; +//! # let peer = "127.0.0.1:1234".parse().unwrap(); +//! # let local = "127.0.0.1:4321".parse().unwrap(); +//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; //! if conn.is_established() { //! // Iterate over readable streams. //! for stream_id in conn.readable() { @@ -264,6 +303,14 @@ //! The quiche [HTTP/3 module] provides a high level API for sending and //! receiving HTTP requests and responses on top of the QUIC transport protocol. //! +//! [`Config`]: https://docs.quic.tech/quiche/struct.Config.html +//! [`set_initial_max_streams_bidi()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi +//! [`set_initial_max_streams_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_uni +//! [`set_initial_max_data()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data +//! [`set_initial_max_stream_data_bidi_local()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local +//! [`set_initial_max_stream_data_bidi_remote()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote +//! [`set_initial_max_stream_data_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_uni +//! [`with_boring_ssl_ctx()`]: https://docs.quic.tech/quiche/struct.Config.html#method.with_boring_ssl_ctx //! [`connect()`]: fn.connect.html //! [`accept()`]: fn.accept.html //! [`recv()`]: struct.Connection.html#method.recv @@ -355,14 +402,18 @@ use qlog::events::EventType; use qlog::events::RawInfo; use std::cmp; +use std::convert::TryInto; use std::time; use std::net::SocketAddr; use std::str::FromStr; +use std::collections::HashSet; use std::collections::VecDeque; +use smallvec::SmallVec; + /// The current QUIC wire version. pub const PROTOCOL_VERSION: u32 = PROTOCOL_VERSION_V1; @@ -389,6 +440,9 @@ const PAYLOAD_MIN_LEN: usize = 4; // account for that. const PAYLOAD_MIN_LEN: usize = 20; +// PATH_CHALLENGE (9 bytes) + AEAD tag (16 bytes). +const MIN_PROBING_SIZE: usize = 25; + const MAX_AMPLIFICATION_FACTOR: usize = 3; // The maximum number of tracked packet number ranges that need to be acked. @@ -427,6 +481,10 @@ const MAX_CONNECTION_WINDOW: u64 = 24 * 1024 * 1024; // the stream flow control window. const CONNECTION_WINDOW_FACTOR: f64 = 1.5; +// How many probing packet timeouts do we tolerate before considering the path +// validation as failed. +const MAX_PROBING_TIMEOUTS: usize = 3; + /// A specialized [`Result`] type for quiche operations. /// /// This type is used throughout quiche's public API for any operation that @@ -436,7 +494,7 @@ const CONNECTION_WINDOW_FACTOR: f64 = 1.5; pub type Result<T> = std::result::Result<T, Error>; /// A QUIC error. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// There is no more work to do. Done, @@ -496,6 +554,15 @@ pub enum Error { /// Error in congestion control. CongestionControl, + + /// Too many identifiers were provided. + IdLimit, + + /// Not enough available identifiers. + OutOfIdentifiers, + + /// Error in key update. + KeyUpdate, } impl Error { @@ -508,6 +575,7 @@ impl Error { Error::FlowControl => 0x3, Error::StreamLimit => 0x4, Error::FinalSize => 0x6, + Error::KeyUpdate => 0xe, _ => 0xa, } } @@ -531,13 +599,16 @@ impl Error { Error::CongestionControl => -14, Error::StreamStopped { .. } => -15, Error::StreamReset { .. } => -16, + Error::IdLimit => -17, + Error::OutOfIdentifiers => -18, + Error::KeyUpdate => -19, } } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -554,16 +625,22 @@ impl std::convert::From<octets::BufferTooShortError> for Error { } /// Ancillary information about incoming packets. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RecvInfo { - /// The address the packet was received from. + /// The remote address the packet was received from. pub from: SocketAddr, + + /// The local address the packet was received on. + pub to: SocketAddr, } /// Ancillary information about outgoing packets. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SendInfo { - /// The address the packet should be sent to. + /// The local address the packet should be sent from. + pub from: SocketAddr, + + /// The remote address the packet should be sent to. pub to: SocketAddr, /// The time to send the packet out. @@ -575,7 +652,7 @@ pub struct SendInfo { } /// Represents information carried by `CONNECTION_CLOSE` frames. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ConnectionError { /// Whether the error came from the application or the transport layer. pub is_app: bool, @@ -587,12 +664,13 @@ pub struct ConnectionError { pub reason: Vec<u8>, } -/// The stream's side to shutdown. +/// The side of the stream to be shut down. /// /// This should be used when calling [`stream_shutdown()`]. /// /// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown #[repr(C)] +#[derive(PartialEq, Eq)] pub enum Shutdown { /// Stop receiving stream data. Read = 0, @@ -632,6 +710,8 @@ pub struct Config { hystart: bool, + pacing: bool, + dgram_recv_max_queue_len: usize, dgram_send_max_queue_len: usize, @@ -639,6 +719,8 @@ pub struct Config { max_connection_window: u64, max_stream_window: u64, + + disable_dcid_reuse: bool, } // See https://quicwg.org/base-drafts/rfc9000.html#section-15 @@ -686,6 +768,7 @@ impl Config { grease: true, cc_algorithm: CongestionControlAlgorithm::CUBIC, hystart: true, + pacing: true, dgram_recv_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN, dgram_send_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN, @@ -694,6 +777,8 @@ impl Config { max_connection_window: MAX_CONNECTION_WINDOW, max_stream_window: stream::MAX_STREAM_WINDOW, + + disable_dcid_reuse: false, }) } @@ -810,9 +895,6 @@ impl Config { /// Configures the list of supported application protocols. /// - /// The list of protocols `protos` must be in wire-format (i.e. a series - /// of non-empty, 8-bit length-prefixed strings). - /// /// On the client this configures the list of protocols to send to the /// server as part of the ALPN extension. /// @@ -825,21 +907,46 @@ impl Config { /// /// ``` /// # let mut config = quiche::Config::new(0xbabababa)?; - /// config.set_application_protos(b"\x08http/1.1\x08http/0.9")?; + /// config.set_application_protos(&[b"http/1.1", b"http/0.9"]); + /// # Ok::<(), quiche::Error>(()) + /// ``` + pub fn set_application_protos( + &mut self, protos_list: &[&[u8]], + ) -> Result<()> { + self.application_protos = + protos_list.iter().map(|s| s.to_vec()).collect(); + + self.tls_ctx.set_alpn(protos_list) + } + + /// Configures the list of supported application protocols using wire + /// format. + /// + /// The list of protocols `protos` must be a series of non-empty, 8-bit + /// length-prefixed strings. + /// + /// See [`set_application_protos`](Self::set_application_protos) for more + /// background about application protocols. + /// + /// ## Examples: + /// + /// ``` + /// # let mut config = quiche::Config::new(0xbabababa)?; + /// config.set_application_protos_wire_format(b"\x08http/1.1\x08http/0.9")?; /// # Ok::<(), quiche::Error>(()) /// ``` - pub fn set_application_protos(&mut self, protos: &[u8]) -> Result<()> { + pub fn set_application_protos_wire_format( + &mut self, protos: &[u8], + ) -> Result<()> { let mut b = octets::Octets::with_slice(protos); let mut protos_list = Vec::new(); while let Ok(proto) = b.get_bytes_with_u8_length() { - protos_list.push(proto.to_vec()); + protos_list.push(proto.buf()); } - self.application_protos = protos_list; - - self.tls_ctx.set_alpn(&self.application_protos) + self.set_application_protos(&protos_list) } /// Sets the `max_idle_timeout` transport parameter, in milliseconds. @@ -865,10 +972,14 @@ impl Config { /// Sets the `initial_max_data` transport parameter. /// - /// When set to a non-zero value quiche will only allow at most `v` bytes - /// of incoming stream data to be buffered for the whole connection (that - /// is, data that is not yet read by the application) and will allow more - /// data to be received as the buffer is consumed by the application. + /// When set to a non-zero value quiche will only allow at most `v` bytes of + /// incoming stream data to be buffered for the whole connection (that is, + /// data that is not yet read by the application) and will allow more data + /// to be received as the buffer is consumed by the application. + /// + /// When set to zero, either explicitly or via the default, quiche will not + /// give any flow control to the peer, preventing it from sending any stream + /// data. /// /// The default value is `0`. pub fn set_initial_max_data(&mut self, v: u64) { @@ -883,6 +994,10 @@ impl Config { /// application) and will allow more data to be received as the buffer is /// consumed by the application. /// + /// When set to zero, either explicitly or via the default, quiche will not + /// give any flow control to the peer, preventing it from sending any stream + /// data. + /// /// The default value is `0`. pub fn set_initial_max_stream_data_bidi_local(&mut self, v: u64) { self.local_transport_params @@ -897,6 +1012,10 @@ impl Config { /// application) and will allow more data to be received as the buffer is /// consumed by the application. /// + /// When set to zero, either explicitly or via the default, quiche will not + /// give any flow control to the peer, preventing it from sending any stream + /// data. + /// /// The default value is `0`. pub fn set_initial_max_stream_data_bidi_remote(&mut self, v: u64) { self.local_transport_params @@ -910,6 +1029,10 @@ impl Config { /// (that is, data that is not yet read by the application) and will allow /// more data to be received as the buffer is consumed by the application. /// + /// When set to zero, either explicitly or via the default, quiche will not + /// give any flow control to the peer, preventing it from sending any stream + /// data. + /// /// The default value is `0`. pub fn set_initial_max_stream_data_uni(&mut self, v: u64) { self.local_transport_params.initial_max_stream_data_uni = v; @@ -922,6 +1045,9 @@ impl Config { /// given time and will increase the limit automatically as streams are /// completed. /// + /// When set to zero, either explicitly or via the default, quiche will not + /// not allow the peer to open any bidirectional streams. + /// /// A bidirectional stream is considered completed when all incoming data /// has been read by the application (up to the `fin` offset) or the /// stream's read direction has been shutdown, and all outgoing data has @@ -940,6 +1066,9 @@ impl Config { /// given time and will increase the limit automatically as streams are /// completed. /// + /// When set to zero, either explicitly or via the default, quiche will not + /// not allow the peer to open any unidirectional streams. + /// /// A unidirectional stream is considered completed when all incoming data /// has been read by the application (up to the `fin` offset) or the /// stream's read direction has been shutdown. @@ -963,6 +1092,15 @@ impl Config { self.local_transport_params.max_ack_delay = v; } + /// Sets the `active_connection_id_limit` transport parameter. + /// + /// The default value is `2`. Lower values will be ignored. + pub fn set_active_connection_id_limit(&mut self, v: u64) { + if v >= 2 { + self.local_transport_params.active_conn_id_limit = v; + } + } + /// Sets the `disable_active_migration` transport parameter. /// /// The default value is `false`. @@ -1002,6 +1140,13 @@ impl Config { self.hystart = v; } + /// Configures whether to enable pacing. + /// + /// The default value is `true`. + pub fn enable_pacing(&mut self, v: bool) { + self.pacing = v; + } + /// Configures whether to enable receiving DATAGRAM frames. /// /// When enabled, the `max_datagram_frame_size` transport parameter is set @@ -1033,6 +1178,30 @@ impl Config { pub fn set_max_stream_window(&mut self, v: u64) { self.max_stream_window = v; } + + /// Sets the initial stateless reset token. + /// + /// This value is only advertised by servers. Setting a stateless retry + /// token as a client has no effect on the connection. + /// + /// The default value is `None`. + pub fn set_stateless_reset_token(&mut self, v: Option<u128>) { + self.local_transport_params.stateless_reset_token = v; + } + + /// Sets whether the QUIC connection should avoid reusing DCIDs over + /// different paths. + /// + /// When set to `true`, it ensures that a destination Connection ID is never + /// reused on different paths. Such behaviour may lead to connection stall + /// if the peer performs a non-voluntary migration (e.g., NAT rebinding) and + /// does not provide additional destination Connection IDs to handle such + /// event. + /// + /// The default value is `false`. + pub fn set_disable_dcid_reuse(&mut self, v: bool) { + self.disable_dcid_reuse = v; + } } /// A QUIC connection. @@ -1040,17 +1209,14 @@ pub struct Connection { /// QUIC wire version used for the connection. version: u32, - /// Peer's connection ID. - dcid: ConnectionId<'static>, - - /// Local connection ID. - scid: ConnectionId<'static>, + /// Connection Identifiers. + ids: cid::ConnectionIdentifiers, /// Unique opaque ID for the connection that can be used for logging. trace_id: String, /// Packet number spaces. - pkt_num_spaces: [packet::PktNumSpace; packet::EPOCH_COUNT], + pkt_num_spaces: [packet::PktNumSpace; packet::Epoch::count()], /// Peer's transport parameters. peer_transport_params: TransportParams, @@ -1067,10 +1233,11 @@ pub struct Connection { /// client. On the server this is empty. session: Option<Vec<u8>>, - /// Loss recovery and congestion control state. - recovery: recovery::Recovery, + /// The configuration for recovery. + recovery_config: recovery::RecoveryConfig, - peer_addr: SocketAddr, + /// The path manager. + paths: path::PathMap, /// List of supported application protocols. application_protos: Vec<Vec<u8>>, @@ -1081,6 +1248,9 @@ pub struct Connection { /// Total number of sent packets. sent_count: usize, + /// Total number of lost packets. + lost_count: usize, + /// Total number of packets sent with data retransmitted. retrans_count: usize, @@ -1096,6 +1266,9 @@ pub struct Connection { /// Number of stream data bytes that can be buffered. tx_cap: usize, + // Number of bytes buffered in the send buffer. + tx_buffered: usize, + /// Total number of bytes sent to the peer. tx_data: u64, @@ -1105,10 +1278,6 @@ pub struct Connection { /// Last tx_data before running a full send() loop. last_tx_data: u64, - /// Total number of bytes the server can send before the peer's address - /// is verified. - max_send_bytes: usize, - /// Total number of bytes retransmitted over the connection. /// This counts only STREAM and CRYPTO data. stream_retrans_bytes: u64, @@ -1119,6 +1288,9 @@ pub struct Connection { /// Total number of bytes received over the connection. recv_bytes: u64, + /// Total number of bytes sent lost over the connection. + lost_bytes: u64, + /// Streams map, indexed by stream ID. streams: stream::StreamMap, @@ -1141,9 +1313,6 @@ pub struct Connection { /// frame. peer_error: Option<ConnectionError>, - /// Received path challenge. - challenge: Option<[u8; 8]>, - /// The connection-level limit at which send blocking occurred. blocked_limit: Option<u64>, @@ -1175,11 +1344,8 @@ pub struct Connection { /// Whether the peer already updated its connection ID. got_peer_conn_id: bool, - /// Whether the peer's address has been verified. - verified_peer_address: bool, - - /// Whether the peer has verified our address. - peer_verified_address: bool, + /// Whether the peer verified our initial address. + peer_verified_initial_address: bool, /// Whether the peer's transport parameters were parsed. parsed_peer_transport_params: bool, @@ -1196,6 +1362,9 @@ pub struct Connection { /// Whether the connection handshake has been confirmed. handshake_confirmed: bool, + /// Key phase bit used for outgoing protected packets. + key_phase: bool, + /// Whether an ack-eliciting packet has been sent since last receiving a /// packet. ack_eliciting_sent: bool, @@ -1221,6 +1390,10 @@ pub struct Connection { /// Whether to emit DATAGRAM frames in the next packet. emit_dgram: bool, + + /// Whether the connection should prevent from reusing destination + /// Connection IDs when the peer migrates. + disable_dcid_reuse: bool, } /// Creates a new server-side connection. @@ -1237,16 +1410,17 @@ pub struct Connection { /// ```no_run /// # let mut config = quiche::Config::new(0xbabababa)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -/// # let from = "127.0.0.1:1234".parse().unwrap(); -/// let conn = quiche::accept(&scid, None, from, &mut config)?; +/// # let local = "127.0.0.1:0".parse().unwrap(); +/// # let peer = "127.0.0.1:1234".parse().unwrap(); +/// let conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// # Ok::<(), quiche::Error>(()) /// ``` #[inline] pub fn accept( - scid: &ConnectionId, odcid: Option<&ConnectionId>, from: SocketAddr, - config: &mut Config, + scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr, + peer: SocketAddr, config: &mut Config, ) -> Result<Connection> { - let conn = Connection::new(scid, odcid, from, config, true)?; + let conn = Connection::new(scid, odcid, local, peer, config, true)?; Ok(conn) } @@ -1263,16 +1437,18 @@ pub fn accept( /// # let mut config = quiche::Config::new(0xbabababa)?; /// # let server_name = "quic.tech"; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); -/// # let to = "127.0.0.1:1234".parse().unwrap(); -/// let conn = quiche::connect(Some(&server_name), &scid, to, &mut config)?; +/// # let local = "127.0.0.1:4321".parse().unwrap(); +/// # let peer = "127.0.0.1:1234".parse().unwrap(); +/// let conn = +/// quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?; /// # Ok::<(), quiche::Error>(()) /// ``` #[inline] pub fn connect( - server_name: Option<&str>, scid: &ConnectionId, to: SocketAddr, - config: &mut Config, + server_name: Option<&str>, scid: &ConnectionId, local: SocketAddr, + peer: SocketAddr, config: &mut Config, ) -> Result<Connection> { - let mut conn = Connection::new(scid, None, to, config, false)?; + let mut conn = Connection::new(scid, None, local, peer, config, false)?; if let Some(server_name) = server_name { conn.handshake.set_host_name(server_name)?; @@ -1334,13 +1510,14 @@ pub fn negotiate_version( /// # let mut out = [0; 512]; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); +/// # let local = socket.local_addr().unwrap(); /// # fn mint_token(hdr: &quiche::Header, src: &std::net::SocketAddr) -> Vec<u8> { /// # vec![] /// # } /// # fn validate_token<'a>(src: &std::net::SocketAddr, token: &'a [u8]) -> Option<quiche::ConnectionId<'a>> { /// # None /// # } -/// let (len, src) = socket.recv_from(&mut buf).unwrap(); +/// let (len, peer) = socket.recv_from(&mut buf).unwrap(); /// /// let hdr = quiche::Header::from_slice(&mut buf[..len], quiche::MAX_CONN_ID_LEN)?; /// @@ -1348,25 +1525,25 @@ pub fn negotiate_version( /// /// // No token sent by client, create a new one. /// if token.is_empty() { -/// let new_token = mint_token(&hdr, &src); +/// let new_token = mint_token(&hdr, &peer); /// /// let len = quiche::retry( /// &hdr.scid, &hdr.dcid, &scid, &new_token, hdr.version, &mut out, /// )?; /// -/// socket.send_to(&out[..len], &src).unwrap(); +/// socket.send_to(&out[..len], &peer).unwrap(); /// return Ok(()); /// } /// /// // Client sent token, validate it. -/// let odcid = validate_token(&src, token); +/// let odcid = validate_token(&peer, token); /// /// if odcid.is_none() { /// // Invalid address validation token. /// return Ok(()); /// } /// -/// let conn = quiche::accept(&scid, odcid.as_ref(), src, &mut config)?; +/// let conn = quiche::accept(&scid, odcid.as_ref(), local, peer, &mut config)?; /// # Ok::<(), quiche::Error>(()) /// ``` #[inline] @@ -1481,27 +1658,57 @@ impl Default for QlogInfo { impl Connection { fn new( - scid: &ConnectionId, odcid: Option<&ConnectionId>, peer: SocketAddr, - config: &mut Config, is_server: bool, + scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr, + peer: SocketAddr, config: &mut Config, is_server: bool, ) -> Result<Connection> { let tls = config.tls_ctx.new_handshake()?; - Connection::with_tls(scid, odcid, peer, config, tls, is_server) + Connection::with_tls(scid, odcid, local, peer, config, tls, is_server) } fn with_tls( - scid: &ConnectionId, odcid: Option<&ConnectionId>, peer: SocketAddr, - config: &mut Config, tls: tls::Handshake, is_server: bool, + scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr, + peer: SocketAddr, config: &mut Config, tls: tls::Handshake, + is_server: bool, ) -> Result<Connection> { let max_rx_data = config.local_transport_params.initial_max_data; let scid_as_hex: Vec<String> = - scid.iter().map(|b| format!("{:02x}", b)).collect(); + scid.iter().map(|b| format!("{b:02x}")).collect(); + + let reset_token = if is_server { + config.local_transport_params.stateless_reset_token + } else { + None + }; + + let recovery_config = recovery::RecoveryConfig::from_config(config); + + let mut path = path::Path::new(local, peer, &recovery_config, true); + // If we did stateless retry assume the peer's address is verified. + path.verified_peer_address = odcid.is_some(); + // Assume clients validate the server's address implicitly. + path.peer_verified_local_address = is_server; + + // Do not allocate more than the number of active CIDs. + let paths = path::PathMap::new( + path, + config.local_transport_params.active_conn_id_limit as usize, + is_server, + ); + + let active_path_id = paths.get_active_path_id()?; + + let ids = cid::ConnectionIdentifiers::new( + config.local_transport_params.active_conn_id_limit as usize, + scid, + active_path_id, + reset_token, + ); let mut conn = Connection { version: config.version, - dcid: ConnectionId::default(), - scid: scid.to_vec().into(), + ids, trace_id: scid_as_hex.join(""), @@ -1519,17 +1726,19 @@ impl Connection { session: None, - recovery: recovery::Recovery::new(config), + recovery_config, - peer_addr: peer, + paths, application_protos: config.application_protos.clone(), recv_count: 0, sent_count: 0, + lost_count: 0, retrans_count: 0, sent_bytes: 0, recv_bytes: 0, + lost_bytes: 0, rx_data: 0, flow_control: flowcontrol::FlowControl::new( @@ -1541,14 +1750,14 @@ impl Connection { tx_cap: 0, + tx_buffered: 0, + tx_data: 0, max_tx_data: 0, last_tx_data: 0, stream_retrans_bytes: 0, - max_send_bytes: 0, - streams: stream::StreamMap::new( config.local_transport_params.initial_max_streams_bidi, config.local_transport_params.initial_max_streams_uni, @@ -1565,8 +1774,6 @@ impl Connection { peer_error: None, - challenge: None, - blocked_limit: None, idle_timer: None, @@ -1587,11 +1794,8 @@ impl Connection { got_peer_conn_id: false, - // If we did stateless retry assume the peer's address is verified. - verified_peer_address: odcid.is_some(), - // Assume clients validate the server's address implicitly. - peer_verified_address: is_server, + peer_verified_initial_address: is_server, parsed_peer_transport_params: false, @@ -1602,6 +1806,8 @@ impl Connection { handshake_confirmed: false, + key_phase: false, + ack_eliciting_sent: false, closed: false, @@ -1624,6 +1830,8 @@ impl Connection { ), emit_dgram: true, + + disable_dcid_reuse: config.disable_dcid_reuse, }; if let Some(odcid) = odcid { @@ -1631,13 +1839,13 @@ impl Connection { .original_destination_connection_id = Some(odcid.to_vec().into()); conn.local_transport_params.retry_source_connection_id = - Some(scid.to_vec().into()); + Some(conn.ids.get_scid(0)?.cid.to_vec().into()); conn.did_retry = true; } conn.local_transport_params.initial_source_connection_id = - Some(scid.to_vec().into()); + Some(conn.ids.get_scid(0)?.cid.to_vec().into()); conn.handshake.init(is_server)?; @@ -1658,17 +1866,22 @@ impl Connection { conn.is_server, )?; - conn.dcid = dcid.to_vec().into(); + let reset_token = conn.peer_transport_params.stateless_reset_token; + conn.set_initial_dcid( + dcid.to_vec().into(), + reset_token, + active_path_id, + )?; - conn.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_open = + conn.pkt_num_spaces[packet::Epoch::Initial].crypto_open = Some(aead_open); - conn.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_seal = + conn.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = Some(aead_seal); conn.derived_initial_secrets = true; } - conn.recovery.on_init(); + conn.paths.get_mut(active_path_id)?.recovery.on_init(); Ok(conn) } @@ -1795,7 +2008,7 @@ impl Connection { let peer_params = TransportParams::decode(raw_params_bytes.as_ref(), self.is_server)?; - self.process_peer_transport_params(peer_params); + self.process_peer_transport_params(peer_params)?; Ok(()) } @@ -1820,12 +2033,16 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// loop { /// let (read, from) = socket.recv_from(&mut buf).unwrap(); /// - /// let recv_info = quiche::RecvInfo { from }; + /// let recv_info = quiche::RecvInfo { + /// from, + /// to: local, + /// }; /// /// let read = match conn.recv(&mut buf[..read], recv_info) { /// Ok(v) => v, @@ -1845,15 +2062,35 @@ impl Connection { return Err(Error::BufferTooShort); } - // Keep track of how many bytes we received from the client, so we - // can limit bytes sent back before address validation, to a multiple - // of this. The limit needs to be increased early on, so that if there - // is an error there is enough credit to send a CONNECTION_CLOSE. - // - // It doesn't matter if the packets received were valid or not, we only - // need to track the total amount of bytes received. - if !self.verified_peer_address { - self.max_send_bytes += len * MAX_AMPLIFICATION_FACTOR; + let recv_pid = self.paths.path_id_from_addrs(&(info.to, info.from)); + + if let Some(recv_pid) = recv_pid { + let recv_path = self.paths.get_mut(recv_pid)?; + + // Keep track of how many bytes we received from the client, so we + // can limit bytes sent back before address validation, to a + // multiple of this. The limit needs to be increased early on, so + // that if there is an error there is enough credit to send a + // CONNECTION_CLOSE. + // + // It doesn't matter if the packets received were valid or not, we + // only need to track the total amount of bytes received. + // + // Note that we also need to limit the number of bytes we sent on a + // path if we are not the host that initiated its usage. + if self.is_server && !recv_path.verified_peer_address { + recv_path.max_send_bytes += len * MAX_AMPLIFICATION_FACTOR; + } + } else if !self.is_server { + // If a client receives packets from an unknown server address, + // the client MUST discard these packets. + trace!( + "{} client received packet from unknown address {:?}, dropping", + self.trace_id, + info, + ); + + return Ok(len); } let mut done = 0; @@ -1861,7 +2098,11 @@ impl Connection { // Process coalesced packets. while left > 0 { - let read = match self.recv_single(&mut buf[len - left..len], &info) { + let read = match self.recv_single( + &mut buf[len - left..len], + &info, + recv_pid, + ) { Ok(v) => v, Err(Error::Done) => { @@ -1888,9 +2129,18 @@ impl Connection { left -= read; } + // Even though the packet was previously "accepted", it + // should be safe to forward the error, as it also comes + // from the `recv()` method. + self.process_undecrypted_0rtt_packets()?; + + Ok(done) + } + + fn process_undecrypted_0rtt_packets(&mut self) -> Result<()> { // Process previously undecryptable 0-RTT packets if the decryption key // is now available. - if self.pkt_num_spaces[packet::EPOCH_APPLICATION] + if self.pkt_num_spaces[packet::Epoch::Application] .crypto_0rtt_open .is_some() { @@ -1899,15 +2149,11 @@ impl Connection { if let Err(e) = self.recv(&mut pkt, info) { self.undecryptable_pkts.clear(); - // Even though the packet was previously "accepted", it - // should be safe to forward the error, as it also comes - // from the `recv()` method. return Err(e); } } } - - Ok(done) + Ok(()) } /// Returns true if a QUIC packet is a stateless reset. @@ -1920,11 +2166,11 @@ impl Connection { // TODO: we should iterate over all active destination connection IDs // and check against their reset token. - match &self.peer_transport_params.stateless_reset_token { + match self.peer_transport_params.stateless_reset_token { Some(token) => { let token_len = 16; ring::constant_time::verify_slices_are_equal( - &token, + &token.to_be_bytes(), &buf[buf_len - token_len..buf_len], ) .is_ok() @@ -1940,10 +2186,17 @@ impl Connection { /// returned. When the [`Done`] error is returned, processing of the /// remainder of the incoming UDP datagram should be interrupted. /// + /// Note that a server might observe a new 4-tuple, preventing to + /// know in advance to which path the incoming packet belongs to (`recv_pid` + /// is `None`). As a client, packets from unknown 4-tuple are dropped + /// beforehand (see `recv()`). + /// /// On error, an error other than [`Done`] is returned. /// /// [`Done`]: enum.Error.html#variant.Done - fn recv_single(&mut self, buf: &mut [u8], info: &RecvInfo) -> Result<usize> { + fn recv_single( + &mut self, buf: &mut [u8], info: &RecvInfo, recv_pid: Option<usize>, + ) -> Result<usize> { let now = time::Instant::now(); if buf.is_empty() { @@ -1960,10 +2213,12 @@ impl Connection { return Err(Error::Done); } + let buf_len = buf.len(); + let mut b = octets::OctetsMut::with_slice(buf); - let mut hdr = - Header::from_bytes(&mut b, self.scid.len()).map_err(|e| { + let mut hdr = Header::from_bytes(&mut b, self.source_id().len()) + .map_err(|e| { drop_pkt_on_err( e, self.recv_count, @@ -1989,11 +2244,11 @@ impl Connection { return Err(Error::Done); } - if hdr.dcid != self.scid { + if hdr.dcid != self.source_id() { return Err(Error::Done); } - if hdr.scid != self.dcid { + if hdr.scid != self.destination_id() { return Err(Error::Done); } @@ -2039,19 +2294,19 @@ impl Connection { // Derive Initial secrets based on the new version. let (aead_open, aead_seal) = crypto::derive_initial_key_material( - &self.dcid, + &self.destination_id(), self.version, self.is_server, )?; // Reset connection state to force sending another Initial packet. - self.drop_epoch_state(packet::EPOCH_INITIAL, now); + self.drop_epoch_state(packet::Epoch::Initial, now); self.got_peer_conn_id = false; self.handshake.clear()?; - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_open = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = Some(aead_open); - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_seal = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = Some(aead_seal); self.handshake @@ -2076,8 +2331,12 @@ impl Connection { } // Check if Retry packet is valid. - if packet::verify_retry_integrity(&b, &self.dcid, self.version) - .is_err() + if packet::verify_retry_integrity( + &b, + &self.destination_id(), + self.version, + ) + .is_err() { return Err(Error::Done); } @@ -2088,11 +2347,15 @@ impl Connection { self.did_retry = true; // Remember peer's new connection ID. - self.odcid = Some(self.dcid.clone()); + self.odcid = Some(self.destination_id().into_owned()); - self.dcid = hdr.scid.clone(); + self.set_initial_dcid( + hdr.scid.clone(), + None, + self.paths.get_active_path_id()?, + )?; - self.rscid = Some(self.dcid.clone()); + self.rscid = Some(self.destination_id().into_owned()); // Derive Initial secrets using the new connection ID. let (aead_open, aead_seal) = crypto::derive_initial_key_material( @@ -2102,13 +2365,13 @@ impl Connection { )?; // Reset connection state to force sending another Initial packet. - self.drop_epoch_state(packet::EPOCH_INITIAL, now); + self.drop_epoch_state(packet::Epoch::Initial, now); self.got_peer_conn_id = false; self.handshake.clear()?; - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_open = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = Some(aead_open); - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_seal = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = Some(aead_seal); return Err(Error::Done); @@ -2170,9 +2433,9 @@ impl Connection { self.is_server, )?; - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_open = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_open = Some(aead_open); - self.pkt_num_spaces[packet::EPOCH_INITIAL].crypto_seal = + self.pkt_num_spaces[packet::Epoch::Initial].crypto_seal = Some(aead_seal); self.derived_initial_secrets = true; @@ -2191,7 +2454,7 @@ impl Connection { }; // Finally, discard packet if no usable key is available. - let aead = match aead { + let mut aead = match aead { Some(v) => v, None => { @@ -2237,16 +2500,55 @@ impl Connection { let pn_len = hdr.pkt_num_len; trace!( - "{} rx pkt {:?} len={} pn={}", + "{} rx pkt {:?} len={} pn={} {}", self.trace_id, hdr, payload_len, - pn + pn, + AddrTupleFmt(info.from, info.to) ); #[cfg(feature = "qlog")] let mut qlog_frames = vec![]; + // Check for key update. + let mut aead_next = None; + + if self.handshake_confirmed && + hdr.ty != Type::ZeroRTT && + hdr.key_phase != self.key_phase + { + // Check if this packet arrived before key update. + if let Some(key_update) = self.pkt_num_spaces[epoch] + .key_update + .as_ref() + .and_then(|key_update| { + (pn < key_update.pn_on_update).then_some(key_update) + }) + { + aead = &key_update.crypto_open; + } else { + trace!("{} peer-initiated key update", self.trace_id); + + aead_next = Some(( + self.pkt_num_spaces[epoch] + .crypto_open + .as_ref() + .unwrap() + .derive_next_packet_key()?, + self.pkt_num_spaces[epoch] + .crypto_seal + .as_ref() + .unwrap() + .derive_next_packet_key()?, + )); + + // `aead_next` is always `Some()` at this point, so the `unwrap()` + // will never fail. + aead = &aead_next.as_ref().unwrap().0; + } + } + let mut payload = packet::decrypt_pkt( &mut b, pn, @@ -2268,20 +2570,97 @@ impl Connection { return Err(Error::InvalidPacket); } + // Now that we decrypted the packet, let's see if we can map it to an + // existing path. + let recv_pid = if hdr.ty == packet::Type::Short && self.got_peer_conn_id { + let pkt_dcid = ConnectionId::from_ref(&hdr.dcid); + self.get_or_create_recv_path_id(recv_pid, &pkt_dcid, buf_len, info)? + } else { + // During handshake, we are on the initial path. + self.paths.get_active_path_id()? + }; + + // The key update is verified once a packet is successfully decrypted + // using the new keys. + if let Some((open_next, seal_next)) = aead_next { + if !self.pkt_num_spaces[epoch] + .key_update + .as_ref() + .map_or(true, |prev| prev.update_acked) + { + // Peer has updated keys twice without awaiting confirmation. + return Err(Error::KeyUpdate); + } + + trace!("{} key update verified", self.trace_id); + + let _ = self.pkt_num_spaces[epoch].crypto_seal.replace(seal_next); + + let open_prev = self.pkt_num_spaces[epoch] + .crypto_open + .replace(open_next) + .unwrap(); + + let recv_path = self.paths.get_mut(recv_pid)?; + + self.pkt_num_spaces[epoch].key_update = Some(packet::KeyUpdate { + crypto_open: open_prev, + pn_on_update: pn, + update_acked: false, + timer: now + (recv_path.recovery.pto() * 3), + }); + + self.key_phase = !self.key_phase; + + qlog_with_type!(QLOG_PACKET_RX, self.qlog, q, { + let trigger = Some( + qlog::events::security::KeyUpdateOrRetiredTrigger::RemoteUpdate, + ); + + let ev_data_client = + EventData::KeyUpdated(qlog::events::security::KeyUpdated { + key_type: + qlog::events::security::KeyType::Client1RttSecret, + old: None, + new: String::new(), + generation: None, + trigger: trigger.clone(), + }); + + q.add_event_data_with_instant(ev_data_client, now).ok(); + + let ev_data_server = + EventData::KeyUpdated(qlog::events::security::KeyUpdated { + key_type: + qlog::events::security::KeyType::Server1RttSecret, + old: None, + new: String::new(), + generation: None, + trigger, + }); + + q.add_event_data_with_instant(ev_data_server, now).ok(); + }); + } + if !self.is_server && !self.got_peer_conn_id { if self.odcid.is_none() { - self.odcid = Some(self.dcid.clone()); + self.odcid = Some(self.destination_id().into_owned()); } // Replace the randomly generated destination connection ID with // the one supplied by the server. - self.dcid = hdr.scid.clone(); + self.set_initial_dcid( + hdr.scid.clone(), + self.peer_transport_params.stateless_reset_token, + recv_pid, + )?; self.got_peer_conn_id = true; } if self.is_server && !self.got_peer_conn_id { - self.dcid = hdr.scid.clone(); + self.set_initial_dcid(hdr.scid.clone(), None, recv_pid)?; if !self.did_retry && (self.version >= PROTOCOL_VERSION_DRAFT28 || @@ -2306,6 +2685,11 @@ impl Connection { // error and stop further packet processing. let mut frame_processing_err = None; + // To know if the peer migrated the connection, we need to keep track + // whether this is a non-probing packet. + let mut probing = true; + + // Process packet payload. while payload.cap() > 0 { let frame = frame::Frame::from_bytes(&mut payload, hdr.ty)?; @@ -2317,7 +2701,12 @@ impl Connection { ack_elicited = true; } - if let Err(e) = self.process_frame(frame, epoch, now) { + if !frame.probing() { + probing = false; + } + + if let Err(e) = self.process_frame(frame, &hdr, recv_pid, epoch, now) + { frame_processing_err = Some(e); break; } @@ -2357,7 +2746,8 @@ impl Connection { }); qlog_with_type!(QLOG_PACKET_RX, self.qlog, q, { - if let Some(ev_data) = self.recovery.maybe_qlog() { + let recv_path = self.paths.get_mut(recv_pid)?; + if let Some(ev_data) = recv_path.recovery.maybe_qlog() { q.add_event_data_with_instant(ev_data, now).ok(); } }); @@ -2384,76 +2774,120 @@ impl Connection { }); } - // Process acked frames. - for acked in self.recovery.acked[epoch].drain(..) { - match acked { - frame::Frame::ACK { ranges, .. } => { - // Stop acknowledging packets less than or equal to the - // largest acknowledged in the sent ACK frame that, in - // turn, got acked. - if let Some(largest_acked) = ranges.last() { + // Process acked frames. Note that several packets from several paths + // might have been acked by the received packet. + for (_, p) in self.paths.iter_mut() { + for acked in p.recovery.acked[epoch].drain(..) { + match acked { + frame::Frame::ACK { ranges, .. } => { + // Stop acknowledging packets less than or equal to the + // largest acknowledged in the sent ACK frame that, in + // turn, got acked. + if let Some(largest_acked) = ranges.last() { + self.pkt_num_spaces[epoch] + .recv_pkt_need_ack + .remove_until(largest_acked); + } + }, + + frame::Frame::CryptoHeader { offset, length } => { self.pkt_num_spaces[epoch] - .recv_pkt_need_ack - .remove_until(largest_acked); - } - }, + .crypto_stream + .send + .ack_and_drop(offset, length); + }, - frame::Frame::CryptoHeader { offset, length } => { - self.pkt_num_spaces[epoch] - .crypto_stream - .send - .ack_and_drop(offset, length); - }, + frame::Frame::StreamHeader { + stream_id, + offset, + length, + .. + } => { + let stream = match self.streams.get_mut(stream_id) { + Some(v) => v, - frame::Frame::StreamHeader { - stream_id, - offset, - length, - .. - } => { - let stream = match self.streams.get_mut(stream_id) { - Some(v) => v, - - None => continue, - }; + None => continue, + }; - stream.send.ack_and_drop(offset, length); + stream.send.ack_and_drop(offset, length); + + self.tx_buffered = + self.tx_buffered.saturating_sub(length); + + qlog_with_type!(QLOG_DATA_MV, self.qlog, q, { + let ev_data = EventData::DataMoved( + qlog::events::quic::DataMoved { + stream_id: Some(stream_id), + offset: Some(offset), + length: Some(length as u64), + from: Some(DataRecipient::Transport), + to: Some(DataRecipient::Dropped), + raw: None, + }, + ); + + q.add_event_data_with_instant(ev_data, now).ok(); + }); + + // Only collect the stream if it is complete and not + // readable. If it is readable, it will get collected when + // stream_recv() is used. + if stream.is_complete() && !stream.is_readable() { + let local = stream.local; + self.streams.collect(stream_id, local); + } + }, - // Only collect the stream if it is complete and not - // readable. If it is readable, it will get collected when - // stream_recv() is used. - if stream.is_complete() && !stream.is_readable() { - let local = stream.local; - self.streams.collect(stream_id, local); - } - }, + frame::Frame::HandshakeDone => { + // Explicitly set this to true, so that if the frame was + // already scheduled for retransmission, it is aborted. + self.handshake_done_sent = true; - frame::Frame::HandshakeDone => { - // Explicitly set this to true, so that if the frame was - // already scheduled for retransmission, it is aborted. - self.handshake_done_sent = true; + self.handshake_done_acked = true; + }, - self.handshake_done_acked = true; - }, + frame::Frame::ResetStream { stream_id, .. } => { + let stream = match self.streams.get_mut(stream_id) { + Some(v) => v, + + None => continue, + }; - frame::Frame::ResetStream { stream_id, .. } => { - let stream = match self.streams.get_mut(stream_id) { - Some(v) => v, + // Only collect the stream if it is complete and not + // readable. If it is readable, it will get collected when + // stream_recv() is used. + if stream.is_complete() && !stream.is_readable() { + let local = stream.local; + self.streams.collect(stream_id, local); + } + }, - None => continue, - }; + _ => (), + } + } + } - // Only collect the stream if it is complete and not - // readable. If it is readable, it will get collected when - // stream_recv() is used. - if stream.is_complete() && !stream.is_readable() { - let local = stream.local; - self.streams.collect(stream_id, local); - } - }, + // Now that we processed all the frames, if there is a path that has no + // Destination CID, try to allocate one. + let no_dcid = self + .paths + .iter_mut() + .filter(|(_, p)| p.active_dcid_seq.is_none()); - _ => (), + for (pid, p) in no_dcid { + if self.ids.zero_length_dcid() { + p.active_dcid_seq = Some(0); + continue; } + + let dcid_seq = match self.ids.lowest_available_dcid_seq() { + Some(seq) => seq, + None => break, + }; + + self.ids.link_dcid_to_path_id(dcid_seq, pid)?; + + p.active_dcid_seq = Some(dcid_seq); } // We only record the time of arrival of the largest packet number @@ -2472,6 +2906,24 @@ impl Connection { self.pkt_num_spaces[epoch].largest_rx_pkt_num = cmp::max(self.pkt_num_spaces[epoch].largest_rx_pkt_num, pn); + if !probing { + self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num = cmp::max( + self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num, + pn, + ); + + // Did the peer migrated to another path? + let active_path_id = self.paths.get_active_path_id()?; + + if self.is_server && + recv_pid != active_path_id && + self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num == pn + { + self.paths + .on_peer_migrated(recv_pid, self.disable_dcid_reuse)?; + } + } + if let Some(idle_timeout) = self.idle_timeout() { self.idle_timer = Some(now + idle_timeout); } @@ -2480,22 +2932,28 @@ impl Connection { self.update_tx_cap(); self.recv_count += 1; + self.paths.get_mut(recv_pid)?.recv_count += 1; let read = b.off() + aead_tag_len; self.recv_bytes += read as u64; + self.paths.get_mut(recv_pid)?.recv_bytes += read as u64; // An Handshake packet has been received from the client and has been // successfully processed, so we can drop the initial state and consider // the client's address to be verified. if self.is_server && hdr.ty == packet::Type::Handshake { - self.drop_epoch_state(packet::EPOCH_INITIAL, now); + self.drop_epoch_state(packet::Epoch::Initial, now); - self.verified_peer_address = true; + self.paths.get_mut(recv_pid)?.verified_peer_address = true; } self.ack_eliciting_sent = false; + // Reset pacer and start a new burst when a valid + // packet is received. + self.paths.get_mut(recv_pid)?.recovery.pacer.reset(now); + Ok(read) } @@ -2520,12 +2978,16 @@ impl Connection { /// * When the application receives data from the peer (for example any /// time [`stream_recv()`] is called). /// + /// Once [`is_draining()`] returns `true`, it is no longer necessary to call + /// `send()` and all calls will return [`Done`]. + /// /// [`Done`]: enum.Error.html#variant.Done /// [`recv()`]: struct.Connection.html#method.recv /// [`on_timeout()`]: struct.Connection.html#method.on_timeout /// [`stream_send()`]: struct.Connection.html#method.stream_send /// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown /// [`stream_recv()`]: struct.Connection.html#method.stream_recv + /// [`is_draining()`]: struct.Connection.html#method.is_draining /// /// ## Examples: /// @@ -2534,8 +2996,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// loop { /// let (write, send_info) = match conn.send(&mut out) { /// Ok(v) => v, @@ -2556,6 +3019,96 @@ impl Connection { /// # Ok::<(), quiche::Error>(()) /// ``` pub fn send(&mut self, out: &mut [u8]) -> Result<(usize, SendInfo)> { + self.send_on_path(out, None, None) + } + + /// Writes a single QUIC packet to be sent to the peer from the specified + /// local address `from` to the destination address `to`. + /// + /// The behavior of this method differs depending on the value of the `from` + /// and `to` parameters: + /// + /// * If both are `Some`, then the method only consider the 4-tuple + /// (`from`, `to`). Application can monitor the 4-tuple availability, + /// either by monitoring [`path_event_next()`] events or by relying on + /// the [`paths_iter()`] method. If the provided 4-tuple does not exist + /// on the connection (anymore), it returns an [`InvalidState`]. + /// + /// * If `from` is `Some` and `to` is `None`, then the method only + /// considers sending packets on paths having `from` as local address. + /// + /// * If `to` is `Some` and `from` is `None`, then the method only + /// considers sending packets on paths having `to` as peer address. + /// + /// * If both are `None`, all available paths are considered. + /// + /// On success the number of bytes written to the output buffer is + /// returned, or [`Done`] if there was nothing to write. + /// + /// The application should call `send_on_path()` multiple times until + /// [`Done`] is returned, indicating that there are no more packets to + /// send. It is recommended that `send_on_path()` be called in the + /// following cases: + /// + /// * When the application receives QUIC packets from the peer (that is, + /// any time [`recv()`] is also called). + /// + /// * When the connection timer expires (that is, any time [`on_timeout()`] + /// is also called). + /// + /// * When the application sends data to the peer (for examples, any time + /// [`stream_send()`] or [`stream_shutdown()`] are called). + /// + /// * When the application receives data from the peer (for example any + /// time [`stream_recv()`] is called). + /// + /// Once [`is_draining()`] returns `true`, it is no longer necessary to call + /// `send_on_path()` and all calls will return [`Done`]. + /// + /// [`Done`]: enum.Error.html#variant.Done + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`recv()`]: struct.Connection.html#method.recv + /// [`on_timeout()`]: struct.Connection.html#method.on_timeout + /// [`stream_send()`]: struct.Connection.html#method.stream_send + /// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown + /// [`stream_recv()`]: struct.Connection.html#method.stream_recv + /// [`path_event_next()`]: struct.Connection.html#method.path_event_next + /// [`paths_iter()`]: struct.Connection.html#method.paths_iter + /// [`is_draining()`]: struct.Connection.html#method.is_draining + /// + /// ## Examples: + /// + /// ```no_run + /// # let mut out = [0; 512]; + /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; + /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; + /// loop { + /// let (write, send_info) = match conn.send_on_path(&mut out, Some(local), Some(peer)) { + /// Ok(v) => v, + /// + /// Err(quiche::Error::Done) => { + /// // Done writing. + /// break; + /// }, + /// + /// Err(e) => { + /// // An error occurred, handle it. + /// break; + /// }, + /// }; + /// + /// socket.send_to(&out[..write], &send_info.to).unwrap(); + /// } + /// # Ok::<(), quiche::Error>(()) + /// ``` + pub fn send_on_path( + &mut self, out: &mut [u8], from: Option<SocketAddr>, + to: Option<SocketAddr>, + ) -> Result<(usize, SendInfo)> { if out.is_empty() { return Err(Error::BufferTooShort); } @@ -2565,30 +3118,16 @@ impl Connection { } if self.local_error.is_none() { - self.do_handshake()?; + self.do_handshake(time::Instant::now())?; } - // Process previously undecryptable 0-RTT packets if the decryption key - // is now available. - if self.pkt_num_spaces[packet::EPOCH_APPLICATION] - .crypto_0rtt_open - .is_some() - { - while let Some((mut pkt, info)) = self.undecryptable_pkts.pop_front() - { - if self.recv(&mut pkt, info).is_err() { - self.undecryptable_pkts.clear(); - - // Forwarding the error value here could confuse - // applications, as they may not expect getting a `recv()` - // error when calling `send()`. - // - // We simply fall-through to sending packets, which should - // take care of terminating the connection as needed. - break; - } - } - } + // Forwarding the error value here could confuse + // applications, as they may not expect getting a `recv()` + // error when calling `send()`. + // + // We simply fall-through to sending packets, which should + // take care of terminating the connection as needed. + let _ = self.process_undecrypted_0rtt_packets(); // There's no point in trying to send a packet if the Initial secrets // have not been derived yet, so return early. @@ -2604,17 +3143,30 @@ impl Connection { // maximum UDP payload size limit. let mut left = cmp::min(out.len(), self.max_send_udp_payload_size()); + let send_pid = match (from, to) { + (Some(f), Some(t)) => self + .paths + .path_id_from_addrs(&(f, t)) + .ok_or(Error::InvalidState)?, + + _ => self.get_send_path_id(from, to)?, + }; + + let send_path = self.paths.get_mut(send_pid)?; + // Limit data sent by the server based on the amount of data received // from the client before its address is validated. - if !self.verified_peer_address && self.is_server { - left = cmp::min(left, self.max_send_bytes); + if !send_path.verified_peer_address && self.is_server { + left = cmp::min(left, send_path.max_send_bytes); } // Generate coalesced packets. while left > 0 { - let (ty, written) = match self - .send_single(&mut out[done..done + left], has_initial) - { + let (ty, written) = match self.send_single( + &mut out[done..done + left], + send_pid, + has_initial, + ) { Ok(v) => v, Err(Error::BufferTooShort) | Err(Error::Done) => break, @@ -2637,10 +3189,17 @@ impl Connection { // When sending multiple PTO probes, don't coalesce them together, // so they are sent on separate UDP datagrams. if let Ok(epoch) = ty.to_epoch() { - if self.recovery.loss_probes[epoch] > 0 { + if self.paths.get_mut(send_pid)?.recovery.loss_probes[epoch] > 0 { break; } } + + // Don't coalesce packets that must go on different paths. + if !(from.is_some() && to.is_some()) && + self.get_send_path_id(from, to)? != send_pid + { + break; + } } if done == 0 { @@ -2660,17 +3219,20 @@ impl Connection { done += pad_len; } + let send_path = self.paths.get(send_pid)?; + let info = SendInfo { - to: self.peer_addr, + from: send_path.local_addr(), + to: send_path.peer_addr(), - at: self.recovery.get_packet_send_time(), + at: send_path.recovery.get_packet_send_time(), }; Ok((done, info)) } fn send_single( - &mut self, out: &mut [u8], has_initial: bool, + &mut self, out: &mut [u8], send_pid: usize, has_initial: bool, ) -> Result<(packet::Type, usize)> { let now = time::Instant::now(); @@ -2686,116 +3248,148 @@ impl Connection { let mut b = octets::OctetsMut::with_slice(out); - let pkt_type = self.write_pkt_type()?; - - let epoch = pkt_type.to_epoch()?; + let pkt_type = self.write_pkt_type(send_pid)?; - // Process lost frames. - for lost in self.recovery.lost[epoch].drain(..) { - match lost { - frame::Frame::CryptoHeader { offset, length } => { - self.pkt_num_spaces[epoch] - .crypto_stream - .send - .retransmit(offset, length); + let max_dgram_len = self.dgram_max_writable_len(); - self.stream_retrans_bytes += length as u64; - - self.retrans_count += 1; - }, - - frame::Frame::StreamHeader { - stream_id, - offset, - length, - fin, - } => { - let stream = match self.streams.get_mut(stream_id) { - Some(v) => v, + let epoch = pkt_type.to_epoch()?; + let pkt_space = &mut self.pkt_num_spaces[epoch]; - None => continue, - }; + // Process lost frames. There might be several paths having lost frames. + for (_, p) in self.paths.iter_mut() { + for lost in p.recovery.lost[epoch].drain(..) { + match lost { + frame::Frame::CryptoHeader { offset, length } => { + pkt_space.crypto_stream.send.retransmit(offset, length); - let was_flushable = stream.is_flushable(); + self.stream_retrans_bytes += length as u64; + p.stream_retrans_bytes += length as u64; - let empty_fin = length == 0 && fin; + self.retrans_count += 1; + p.retrans_count += 1; + }, - stream.send.retransmit(offset, length); + frame::Frame::StreamHeader { + stream_id, + offset, + length, + fin, + } => { + let stream = match self.streams.get_mut(stream_id) { + Some(v) => v, - // If the stream is now flushable push it to the flushable - // queue, but only if it wasn't already queued. - // - // Consider the stream flushable also when we are sending a - // zero-length frame that has the fin flag set. - if (stream.is_flushable() || empty_fin) && !was_flushable { - let urgency = stream.urgency; - let incremental = stream.incremental; - self.streams.push_flushable( - stream_id, - urgency, - incremental, - ); - } + None => continue, + }; - self.stream_retrans_bytes += length as u64; + let was_flushable = stream.is_flushable(); + + let empty_fin = length == 0 && fin; + + stream.send.retransmit(offset, length); + + // If the stream is now flushable push it to the + // flushable queue, but only if it wasn't already + // queued. + // + // Consider the stream flushable also when we are + // sending a zero-length frame that has the fin flag + // set. + if (stream.is_flushable() || empty_fin) && !was_flushable + { + let urgency = stream.urgency; + let incremental = stream.incremental; + self.streams.push_flushable( + stream_id, + urgency, + incremental, + ); + } + + self.stream_retrans_bytes += length as u64; + p.stream_retrans_bytes += length as u64; + + self.retrans_count += 1; + p.retrans_count += 1; + }, - self.retrans_count += 1; - }, + frame::Frame::ACK { .. } => { + pkt_space.ack_elicited = true; + }, - frame::Frame::ACK { .. } => { - self.pkt_num_spaces[epoch].ack_elicited = true; - }, + frame::Frame::ResetStream { + stream_id, + error_code, + final_size, + } => + if self.streams.get(stream_id).is_some() { + self.streams.mark_reset( + stream_id, true, error_code, final_size, + ); + }, + + // Retransmit HANDSHAKE_DONE only if it hasn't been acked at + // least once already. + frame::Frame::HandshakeDone if !self.handshake_done_acked => { + self.handshake_done_sent = false; + }, - frame::Frame::ResetStream { - stream_id, - error_code, - final_size, - } => - if self.streams.get(stream_id).is_some() { - self.streams - .mark_reset(stream_id, true, error_code, final_size); + frame::Frame::MaxStreamData { stream_id, .. } => { + if self.streams.get(stream_id).is_some() { + self.streams.mark_almost_full(stream_id, true); + } }, - // Retransmit HANDSHAKE_DONE only if it hasn't been acked at - // least once already. - frame::Frame::HandshakeDone if !self.handshake_done_acked => { - self.handshake_done_sent = false; - }, + frame::Frame::MaxData { .. } => { + self.almost_full = true; + }, - frame::Frame::MaxStreamData { stream_id, .. } => { - if self.streams.get(stream_id).is_some() { - self.streams.mark_almost_full(stream_id, true); - } - }, + frame::Frame::NewConnectionId { seq_num, .. } => { + self.ids.mark_advertise_new_scid_seq(seq_num, true); + }, - frame::Frame::MaxData { .. } => { - self.almost_full = true; - }, + frame::Frame::RetireConnectionId { seq_num } => { + self.ids.mark_retire_dcid_seq(seq_num, true); + }, - _ => (), + _ => (), + } } } - let mut left = b.cap(); + let is_app_limited = self.delivery_rate_check_if_app_limited(); + let n_paths = self.paths.len(); + let path = self.paths.get_mut(send_pid)?; + let flow_control = &mut self.flow_control; + let pkt_space = &mut self.pkt_num_spaces[epoch]; - // Limit output packet size by congestion window size. - left = cmp::min(left, self.recovery.cwnd_available()); + let mut left = b.cap(); - let pn = self.pkt_num_spaces[epoch].next_pkt_num; + let pn = pkt_space.next_pkt_num; let pn_len = packet::pkt_num_len(pn)?; // The AEAD overhead at the current encryption level. - let crypto_overhead = self.pkt_num_spaces[epoch] - .crypto_overhead() - .ok_or(Error::Done)?; + let crypto_overhead = pkt_space.crypto_overhead().ok_or(Error::Done)?; + + let dcid_seq = path.active_dcid_seq.ok_or(Error::OutOfIdentifiers)?; + + let dcid = + ConnectionId::from_ref(self.ids.get_dcid(dcid_seq)?.cid.as_ref()); + + let scid = if let Some(scid_seq) = path.active_scid_seq { + ConnectionId::from_ref(self.ids.get_scid(scid_seq)?.cid.as_ref()) + } else if pkt_type == packet::Type::Short { + ConnectionId::default() + } else { + return Err(Error::InvalidState); + }; let hdr = Header { ty: pkt_type, version: self.version, - dcid: ConnectionId::from_ref(&self.dcid), - scid: ConnectionId::from_ref(&self.scid), + dcid, + scid, pkt_num: 0, pkt_num_len: pn_len, @@ -2810,11 +3404,30 @@ impl Connection { }, versions: None, - key_phase: false, + key_phase: self.key_phase, }; hdr.to_bytes(&mut b)?; + let hdr_trace = if log::max_level() == log::LevelFilter::Trace { + Some(format!("{hdr:?}")) + } else { + None + }; + + let hdr_ty = hdr.ty; + + #[cfg(feature = "qlog")] + let qlog_pkt_hdr = self.qlog.streamer.as_ref().map(|_q| { + qlog::events::quic::PacketHeader::with_type( + hdr.ty.to_qlog(), + pn, + Some(hdr.version), + Some(&hdr.scid), + Some(&hdr.dcid), + ) + }); + // Calculate the space required for the packet, including the header // the payload length, the packet number and the AEAD overhead. let mut overhead = b.off() + pn_len + crypto_overhead; @@ -2836,23 +3449,27 @@ impl Connection { // This usually happens when we try to send a new packet but // failed because cwnd is almost full. In such case app_limited // is set to false here to make cwnd grow when ACK is received. - self.recovery.update_app_limited(false); + path.recovery.update_app_limited(false); return Err(Error::Done); }, } // Make sure there is enough space for the minimum payload length. if left < PAYLOAD_MIN_LEN { - self.recovery.update_app_limited(false); + path.recovery.update_app_limited(false); return Err(Error::Done); } - let mut frames: Vec<frame::Frame> = Vec::new(); + let mut frames: SmallVec<[frame::Frame; 1]> = SmallVec::new(); let mut ack_eliciting = false; let mut in_flight = false; let mut has_data = false; + // Whether or not we should explicitly elicit an ACK via PING frame if we + // implicitly elicit one otherwise. + let ack_elicit_required = path.recovery.should_elicit_ack(epoch); + let header_offset = b.off(); // Reserve space for payload length in advance. Since we don't yet know @@ -2867,14 +3484,27 @@ impl Connection { let payload_offset = b.off(); + let cwnd_available = + path.recovery.cwnd_available().saturating_sub(overhead); + + let left_before_packing_ack_frame = left; + // Create ACK frame. - if self.pkt_num_spaces[epoch].recv_pkt_need_ack.len() > 0 && - (self.pkt_num_spaces[epoch].ack_elicited || - self.recovery.loss_probes[epoch] > 0) && - !is_closing + // + // When we need to explicitly elicit an ACK via PING later, go ahead and + // generate an ACK (if there's anything to ACK) since we're going to + // send a packet with PING anyways, even if we haven't received anything + // ACK eliciting. + if pkt_space.recv_pkt_need_ack.len() > 0 && + (pkt_space.ack_elicited || ack_elicit_required) && + (!is_closing || + (pkt_type == Type::Handshake && + self.local_error + .as_ref() + .map_or(false, |le| le.is_app))) && + path.active() { - let ack_delay = - self.pkt_num_spaces[epoch].largest_rx_pkt_time.elapsed(); + let ack_delay = pkt_space.largest_rx_pkt_time.elapsed(); let ack_delay = ack_delay.as_micros() as u64 / 2_u64 @@ -2882,18 +3512,93 @@ impl Connection { let frame = frame::Frame::ACK { ack_delay, - ranges: self.pkt_num_spaces[epoch].recv_pkt_need_ack.clone(), + ranges: pkt_space.recv_pkt_need_ack.clone(), ecn_counts: None, // sending ECN is not supported at this time }; - if push_frame_to_pkt!(b, frames, frame, left) { - self.pkt_num_spaces[epoch].ack_elicited = false; + // When a PING frame needs to be sent, avoid sending the ACK if + // there is not enough cwnd available for both (note that PING + // frames are always 1 byte, so we just need to check that the + // ACK's length is lower than cwnd). + if pkt_space.ack_elicited || frame.wire_len() < cwnd_available { + // ACK-only packets are not congestion controlled so ACKs must + // be bundled considering the buffer capacity only, and not the + // available cwnd. + if push_frame_to_pkt!(b, frames, frame, left) { + pkt_space.ack_elicited = false; + } + } + } + + // Limit output packet size by congestion window size. + left = cmp::min( + left, + // Bytes consumed by ACK frames. + cwnd_available.saturating_sub(left_before_packing_ack_frame - left), + ); + + let mut challenge_data = None; + + if pkt_type == packet::Type::Short { + // Create PATH_RESPONSE frame if needed. + // We do not try to ensure that these are really sent. + while let Some(challenge) = path.pop_received_challenge() { + let frame = frame::Frame::PathResponse { data: challenge }; + + if push_frame_to_pkt!(b, frames, frame, left) { + ack_eliciting = true; + in_flight = true; + } else { + // If there are other pending PATH_RESPONSE, don't lose them + // now. + break; + } + } + + // Create PATH_CHALLENGE frame if needed. + if path.validation_requested() { + // TODO: ensure that data is unique over paths. + let data = rand::rand_u64().to_be_bytes(); + + let frame = frame::Frame::PathChallenge { data }; + + if push_frame_to_pkt!(b, frames, frame, left) { + // Let's notify the path once we know the packet size. + challenge_data = Some(data); + + ack_eliciting = true; + in_flight = true; + } + } + + if let Some(key_update) = pkt_space.key_update.as_mut() { + key_update.update_acked = true; } } if pkt_type == packet::Type::Short && !is_closing { + // Create NEW_CONNECTION_ID frames as needed. + while let Some(seq_num) = self.ids.next_advertise_new_scid_seq() { + let frame = self.ids.get_new_connection_id_frame_for(seq_num)?; + + if push_frame_to_pkt!(b, frames, frame, left) { + self.ids.mark_advertise_new_scid_seq(seq_num, false); + + ack_eliciting = true; + in_flight = true; + } else { + break; + } + } + } + + if pkt_type == packet::Type::Short && !is_closing && path.active() { // Create HANDSHAKE_DONE frame. - if self.should_send_handshake_done() { + // self.should_send_handshake_done() but without the need to borrow + if self.handshake_completed && + !self.handshake_done_sent && + self.is_server + { let frame = frame::Frame::HandshakeDone; if push_frame_to_pkt!(b, frames, frame, left) { @@ -2958,7 +3663,7 @@ impl Connection { }; // Autotune the stream window size. - stream.recv.autotune_window(now, self.recovery.rtt()); + stream.recv.autotune_window(now, path.recovery.rtt()); let frame = frame::Frame::MaxStreamData { stream_id, @@ -2977,7 +3682,7 @@ impl Connection { // Make sure the connection window always has some // room compared to the stream window. - self.flow_control.ensure_window_lower_bound( + flow_control.ensure_window_lower_bound( (recv_win as f64 * CONNECTION_WINDOW_FACTOR) as u64, ); @@ -2988,19 +3693,21 @@ impl Connection { } // Create MAX_DATA frame as needed. - if self.almost_full && self.max_rx_data() < self.max_rx_data_next() { + if self.almost_full && + flow_control.max_data() < flow_control.max_data_next() + { // Autotune the connection window size. - self.flow_control.autotune_window(now, self.recovery.rtt()); + flow_control.autotune_window(now, path.recovery.rtt()); let frame = frame::Frame::MaxData { - max: self.max_rx_data_next(), + max: flow_control.max_data_next(), }; if push_frame_to_pkt!(b, frames, frame, left) { self.almost_full = false; // Commits the new max_rx_data limit. - self.flow_control.update_max_data(now); + flow_control.update_max_data(now); ack_eliciting = true; in_flight = true; @@ -3064,62 +3771,77 @@ impl Connection { in_flight = true; } } - } - // Create CONNECTION_CLOSE frame. - if let Some(conn_err) = self.local_error.as_ref() { - if conn_err.is_app { - // Create ApplicationClose frame. - if pkt_type == packet::Type::Short { - let frame = frame::Frame::ApplicationClose { - error_code: conn_err.error_code, - reason: conn_err.reason.clone(), - }; + // Create RETIRE_CONNECTION_ID frames as needed. + while let Some(seq_num) = self.ids.next_retire_dcid_seq() { + // The sequence number specified in a RETIRE_CONNECTION_ID frame + // MUST NOT refer to the Destination Connection ID field of the + // packet in which the frame is contained. + let dcid_seq = path.active_dcid_seq.ok_or(Error::InvalidState)?; - if push_frame_to_pkt!(b, frames, frame, left) { - self.draining_timer = - Some(now + (self.recovery.pto() * 3)); - - ack_eliciting = true; - in_flight = true; - } + if seq_num == dcid_seq { + continue; } - } else { - // Create ConnectionClose frame. - let frame = frame::Frame::ConnectionClose { - error_code: conn_err.error_code, - frame_type: 0, - reason: conn_err.reason.clone(), - }; + + let frame = frame::Frame::RetireConnectionId { seq_num }; if push_frame_to_pkt!(b, frames, frame, left) { - self.draining_timer = Some(now + (self.recovery.pto() * 3)); + self.ids.mark_retire_dcid_seq(seq_num, false); ack_eliciting = true; in_flight = true; + } else { + break; } } } - // Create PATH_RESPONSE frame. - if let Some(challenge) = self.challenge { - let frame = frame::Frame::PathResponse { data: challenge }; + // Create CONNECTION_CLOSE frame. Try to send this only on the active + // path, unless it is the last one available. + if path.active() || n_paths == 1 { + if let Some(conn_err) = self.local_error.as_ref() { + if conn_err.is_app { + // Create ApplicationClose frame. + if pkt_type == packet::Type::Short { + let frame = frame::Frame::ApplicationClose { + error_code: conn_err.error_code, + reason: conn_err.reason.clone(), + }; - if push_frame_to_pkt!(b, frames, frame, left) { - self.challenge = None; + if push_frame_to_pkt!(b, frames, frame, left) { + let pto = path.recovery.pto(); + self.draining_timer = Some(now + (pto * 3)); - ack_eliciting = true; - in_flight = true; + ack_eliciting = true; + in_flight = true; + } + } + } else { + // Create ConnectionClose frame. + let frame = frame::Frame::ConnectionClose { + error_code: conn_err.error_code, + frame_type: 0, + reason: conn_err.reason.clone(), + }; + + if push_frame_to_pkt!(b, frames, frame, left) { + let pto = path.recovery.pto(); + self.draining_timer = Some(now + (pto * 3)); + + ack_eliciting = true; + in_flight = true; + } + } } } // Create CRYPTO frame. - if self.pkt_num_spaces[epoch].crypto_stream.is_flushable() && + if pkt_space.crypto_stream.is_flushable() && left > frame::MAX_CRYPTO_OVERHEAD && - !is_closing + !is_closing && + path.active() { - let crypto_off = - self.pkt_num_spaces[epoch].crypto_stream.send.off_front(); + let crypto_off = pkt_space.crypto_stream.send.off_front(); // Encode the frame. // @@ -3144,7 +3866,7 @@ impl Connection { b.split_at(hdr_off + hdr_len)?; // Write stream data into the packet buffer. - let (len, _) = self.pkt_num_spaces[epoch] + let (len, _) = pkt_space .crypto_stream .send .emit(&mut crypto_payload.as_mut()[..max_len])?; @@ -3185,7 +3907,7 @@ impl Connection { // where one type is preferred but its buffer is empty, fall back // to the other type in order not to waste this function call. let mut dgram_emitted = false; - let dgrams_to_emit = self.dgram_max_writable_len().is_some(); + let dgrams_to_emit = max_dgram_len.is_some(); let stream_to_emit = self.streams.has_flushable(); let mut do_dgram = self.emit_dgram && dgrams_to_emit; @@ -3199,9 +3921,10 @@ impl Connection { if (pkt_type == packet::Type::Short || pkt_type == packet::Type::ZeroRTT) && left > frame::MAX_DGRAM_OVERHEAD && !is_closing && + path.active() && do_dgram { - if let Some(max_dgram_payload) = self.dgram_max_writable_len() { + if let Some(max_dgram_payload) = max_dgram_len { while let Some(len) = self.dgram_send_queue.peek_front_len() { let hdr_off = b.off(); let hdr_len = 1 + // frame type @@ -3276,23 +3999,22 @@ impl Connection { if (pkt_type == packet::Type::Short || pkt_type == packet::Type::ZeroRTT) && left > frame::MAX_STREAM_OVERHEAD && !is_closing && + path.active() && !dgram_emitted { - while let Some(stream_id) = self.streams.pop_flushable() { + while let Some(stream_id) = self.streams.peek_flushable() { let stream = match self.streams.get_mut(stream_id) { - Some(v) => v, - - None => continue, + // Avoid sending frames for streams that were already stopped. + // + // This might happen if stream data was buffered but not yet + // flushed on the wire when a STOP_SENDING frame is received. + Some(v) if !v.send.is_stopped() => v, + _ => { + self.streams.remove_flushable(); + continue; + }, }; - // Avoid sending frames for streams that were already stopped. - // - // This might happen if stream data was buffered but not yet - // flushed on the wire when a STOP_SENDING frame is received. - if stream.send.is_stopped() { - continue; - } - let stream_off = stream.send.off_front(); // Encode the frame. @@ -3316,8 +4038,10 @@ impl Connection { let max_len = match left.checked_sub(hdr_len) { Some(v) => v, - - None => continue, + None => { + self.streams.remove_flushable(); + continue; + }, }; let (mut stream_hdr, mut stream_payload) = @@ -3359,19 +4083,9 @@ impl Connection { has_data = true; } - // If the stream is still flushable, push it to the back of the - // queue again. - if stream.is_flushable() { - let urgency = stream.urgency; - let incremental = stream.incremental; - self.streams.push_flushable(stream_id, urgency, incremental); - } - - // When fuzzing, try to coalesce multiple STREAM frames in the - // same packet, so it's easier to generate fuzz corpora. - if cfg!(feature = "fuzzing") && left > frame::MAX_STREAM_OVERHEAD - { - continue; + // If the stream is no longer flushable, remove it from the queue + if !stream.is_flushable() { + self.streams.remove_flushable(); } break; @@ -3381,8 +4095,12 @@ impl Connection { // Alternate trying to send DATAGRAMs next time. self.emit_dgram = !dgram_emitted; - // Create PING for PTO probe if no other ack-eliciting frame is sent. - if self.recovery.loss_probes[epoch] > 0 && + // If no other ack-eliciting frame is sent, include a PING frame + // - if PTO probe needed; OR + // - if we've sent too many non ack-eliciting packets without having + // sent an ACK eliciting one; OR + // - the application requested an ack-eliciting frame be sent. + if (ack_elicit_required || path.needs_ack_eliciting) && !ack_eliciting && left >= 1 && !is_closing @@ -3396,23 +4114,30 @@ impl Connection { } if ack_eliciting { - self.recovery.loss_probes[epoch] = - self.recovery.loss_probes[epoch].saturating_sub(1); + path.needs_ack_eliciting = false; + path.recovery.loss_probes[epoch] = + path.recovery.loss_probes[epoch].saturating_sub(1); } if frames.is_empty() { // When we reach this point we are not able to write more, so set // app_limited to false. - self.recovery.update_app_limited(false); + path.recovery.update_app_limited(false); return Err(Error::Done); } // When coalescing a 1-RTT packet, we can't add padding in the UDP // datagram, so use PADDING frames instead. // - // This is only needed if an Initial packet has already been written to - // the UDP datagram, as Initial always requires padding. - if has_initial && pkt_type == packet::Type::Short && left >= 1 { + // This is only needed if + // 1) an Initial packet has already been written to the UDP datagram, + // as Initial always requires padding. + // + // 2) this is a probing packet towards an unvalidated peer address. + if (has_initial || !path.validated()) && + pkt_type == packet::Type::Short && + left >= 1 + { let frame = frame::Frame::Padding { len: left }; if push_frame_to_pkt!(b, frames, frame, left) { @@ -3446,15 +4171,18 @@ impl Connection { } trace!( - "{} tx pkt {:?} len={} pn={}", + "{} tx pkt {} len={} pn={} {}", self.trace_id, - hdr, + hdr_trace.unwrap_or_default(), payload_len, - pn + pn, + AddrTupleFmt(path.local_addr(), path.peer_addr()) ); #[cfg(feature = "qlog")] - let mut qlog_frames = Vec::with_capacity(frames.len()); + let mut qlog_frames: SmallVec< + [qlog::events::quic::QuicFrame; 1], + > = SmallVec::with_capacity(frames.len()); for frame in &mut frames { trace!("{} tx frm {:?}", self.trace_id, frame); @@ -3465,41 +4193,40 @@ impl Connection { } qlog_with_type!(QLOG_PACKET_TX, self.qlog, q, { - let qlog_pkt_hdr = qlog::events::quic::PacketHeader::with_type( - hdr.ty.to_qlog(), - pn, - Some(hdr.version), - Some(&hdr.scid), - Some(&hdr.dcid), - ); - - // Qlog packet raw info described at - // https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema-00#section-5.1 - // - // `length` includes packet headers and trailers (AEAD tag). - let length = payload_len + payload_offset + crypto_overhead; - let qlog_raw_info = RawInfo { - length: Some(length as u64), - payload_length: Some(payload_len as u64), - data: None, - }; + if let Some(header) = qlog_pkt_hdr { + // Qlog packet raw info described at + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema-00#section-5.1 + // + // `length` includes packet headers and trailers (AEAD tag). + let length = payload_len + payload_offset + crypto_overhead; + let qlog_raw_info = RawInfo { + length: Some(length as u64), + payload_length: Some(payload_len as u64), + data: None, + }; - let ev_data = EventData::PacketSent(qlog::events::quic::PacketSent { - header: qlog_pkt_hdr, - frames: Some(qlog_frames), - is_coalesced: None, - retry_token: None, - stateless_reset_token: None, - supported_versions: None, - raw: Some(qlog_raw_info), - datagram_id: None, - trigger: None, - }); + let send_at_time = + now.duration_since(q.start_time()).as_secs_f32() * 1000.0; + + let ev_data = + EventData::PacketSent(qlog::events::quic::PacketSent { + header, + frames: Some(qlog_frames), + is_coalesced: None, + retry_token: None, + stateless_reset_token: None, + supported_versions: None, + raw: Some(qlog_raw_info), + datagram_id: None, + send_at_time: Some(send_at_time), + trigger: None, + }); - q.add_event_data_with_instant(ev_data, now).ok(); + q.add_event_data_with_instant(ev_data, now).ok(); + } }); - let aead = match self.pkt_num_spaces[epoch].crypto_seal { + let aead = match pkt_space.crypto_seal { Some(ref v) => v, None => return Err(Error::InvalidState), }; @@ -3530,40 +4257,54 @@ impl Connection { has_data, }; - if in_flight && self.delivery_rate_check_if_app_limited() { - self.recovery.delivery_rate_update_app_limited(true); + if in_flight && is_app_limited { + path.recovery.delivery_rate_update_app_limited(true); } - self.recovery.on_packet_sent( + pkt_space.next_pkt_num += 1; + + let handshake_status = recovery::HandshakeStatus { + has_handshake_keys: self.pkt_num_spaces[packet::Epoch::Handshake] + .has_keys(), + peer_verified_address: self.peer_verified_initial_address, + completed: self.handshake_completed, + }; + + path.recovery.on_packet_sent( sent_pkt, epoch, - self.handshake_status(), + handshake_status, now, &self.trace_id, ); qlog_with_type!(QLOG_METRICS, self.qlog, q, { - if let Some(ev_data) = self.recovery.maybe_qlog() { + if let Some(ev_data) = path.recovery.maybe_qlog() { q.add_event_data_with_instant(ev_data, now).ok(); } }); - self.pkt_num_spaces[epoch].next_pkt_num += 1; + // Record sent packet size if we probe the path. + if let Some(data) = challenge_data { + path.add_challenge_sent(data, written, now); + } self.sent_count += 1; self.sent_bytes += written as u64; + path.sent_count += 1; + path.sent_bytes += written as u64; - if self.dgram_send_queue.byte_size() > self.recovery.cwnd_available() { - self.recovery.update_app_limited(false); + if self.dgram_send_queue.byte_size() > path.recovery.cwnd_available() { + path.recovery.update_app_limited(false); } + path.max_send_bytes = path.max_send_bytes.saturating_sub(written); + // On the client, drop initial state after sending an Handshake packet. - if !self.is_server && hdr.ty == packet::Type::Handshake { - self.drop_epoch_state(packet::EPOCH_INITIAL, now); + if !self.is_server && hdr_ty == packet::Type::Handshake { + self.drop_epoch_state(packet::Epoch::Initial, now); } - self.max_send_bytes = self.max_send_bytes.saturating_sub(written); - // (Re)start the idle timer if we are sending the first ack-eliciting // packet since last receiving a packet. if ack_eliciting && !self.ack_eliciting_sent { @@ -3584,12 +4325,36 @@ impl Connection { /// This represents the maximum size of a packet burst as determined by the /// congestion control algorithm in use. /// - /// Applications can, for example, use it in conjuction with segmentatation + /// Applications can, for example, use it in conjunction with segmentation /// offloading mechanisms as the maximum limit for outgoing aggregates of /// multiple packets. #[inline] - pub fn send_quantum(&mut self) -> usize { - self.recovery.send_quantum() + pub fn send_quantum(&self) -> usize { + match self.paths.get_active() { + Ok(p) => p.recovery.send_quantum(), + _ => 0, + } + } + + /// Returns the size of the send quantum over the given 4-tuple, in bytes. + /// + /// This represents the maximum size of a packet burst as determined by the + /// congestion control algorithm in use. + /// + /// Applications can, for example, use it in conjunction with segmentation + /// offloading mechanisms as the maximum limit for outgoing aggregates of + /// multiple packets. + /// + /// If the (`local_addr`, peer_addr`) 4-tuple relates to a non-existing + /// path, this method returns 0. + pub fn send_quantum_on_path( + &self, local_addr: SocketAddr, peer_addr: SocketAddr, + ) -> usize { + self.paths + .path_id_from_addrs(&(local_addr, peer_addr)) + .and_then(|pid| self.paths.get(pid).ok()) + .map(|path| path.recovery.send_quantum()) + .unwrap_or(0) } /// Reads contiguous data from a stream into the provided slice. @@ -3613,8 +4378,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// # let stream_id = 0; /// while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) { /// println!("Got {} bytes on stream {}", read, stream_id); @@ -3687,7 +4453,7 @@ impl Connection { length: Some(read as u64), from: Some(DataRecipient::Transport), to: Some(DataRecipient::Application), - data: None, + raw: None, }); let now = time::Instant::now(); @@ -3741,8 +4507,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = "127.0.0.1:4321".parse().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// # let stream_id = 0; /// conn.stream_send(stream_id, b"hello", true)?; /// # Ok::<(), quiche::Error>(()) @@ -3766,30 +4533,43 @@ impl Connection { self.blocked_limit = Some(self.max_tx_data); } + let cap = self.tx_cap; + + // Get existing stream or create a new one. + let stream = self.get_or_create_stream(stream_id, true)?; + + #[cfg(feature = "qlog")] + let offset = stream.send.off_back(); + + let was_writable = stream.is_writable(); + + let was_flushable = stream.is_flushable(); + // Truncate the input buffer based on the connection's send capacity if // necessary. // // When the cap is zero, the method returns Ok(0) *only* when the passed // buffer is empty. We return Error::Done otherwise. - let cap = self.tx_cap; - if cap == 0 && !(fin && buf.is_empty()) { + if cap == 0 && !buf.is_empty() { + if was_writable { + // When `stream_writable_next()` returns a stream, the writable + // mark is removed, but because the stream is blocked by the + // connection-level send capacity it won't be marked as writable + // again once the capacity increases. + // + // Since the stream is writable already, mark it here instead. + self.streams.mark_writable(stream_id, true); + } + return Err(Error::Done); } - let (buf, fin) = if cap < buf.len() { - (&buf[..cap], false) + let (buf, fin, blocked_by_cap) = if cap < buf.len() { + (&buf[..cap], false, true) } else { - (buf, fin) + (buf, fin, false) }; - // Get existing stream or create a new one. - let stream = self.get_or_create_stream(stream_id, true)?; - - #[cfg(feature = "qlog")] - let offset = stream.send.off_back(); - - let was_flushable = stream.is_flushable(); - let sent = match stream.send.write(buf, fin) { Ok(v) => v, @@ -3831,12 +4611,22 @@ impl Connection { if !writable { self.streams.mark_writable(stream_id, false); + } else if was_writable && blocked_by_cap { + // When `stream_writable_next()` returns a stream, the writable + // mark is removed, but because the stream is blocked by the + // connection-level send capacity it won't be marked as writable + // again once the capacity increases. + // + // Since the stream is writable already, mark it here instead. + self.streams.mark_writable(stream_id, true); } self.tx_cap -= sent; self.tx_data += sent as u64; + self.tx_buffered += sent; + qlog_with_type!(QLOG_DATA_MV, self.qlog, q, { let ev_data = EventData::DataMoved(qlog::events::quic::DataMoved { stream_id: Some(stream_id), @@ -3844,7 +4634,7 @@ impl Connection { length: Some(sent as u64), from: Some(DataRecipient::Application), to: Some(DataRecipient::Transport), - data: None, + raw: None, }); let now = time::Instant::now(); @@ -3901,17 +4691,40 @@ impl Connection { /// be sent to the peer to signal it to stop sending data. /// /// When the `direction` argument is set to [`Shutdown::Write`], outstanding - /// data in the stream's send buffer is dropped, and no additional data - /// is added to it. Data passed to [`stream_send()`] after calling this - /// method will be ignored. + /// data in the stream's send buffer is dropped, and no additional data is + /// added to it. Data passed to [`stream_send()`] after calling this method + /// will be ignored. In addition, a `RESET_STREAM` frame will be sent to the + /// peer to signal the reset. + /// + /// Locally-initiated unidirectional streams can only be closed in the + /// [`Shutdown::Write`] direction. Remotely-initiated unidirectional streams + /// can only be closed in the [`Shutdown::Read`] direction. Using an + /// incorrect direction will return [`InvalidStreamState`]. /// /// [`Shutdown::Read`]: enum.Shutdown.html#variant.Read /// [`Shutdown::Write`]: enum.Shutdown.html#variant.Write /// [`stream_recv()`]: struct.Connection.html#method.stream_recv /// [`stream_send()`]: struct.Connection.html#method.stream_send + /// [`InvalidStreamState`]: enum.Error.html#variant.InvalidStreamState pub fn stream_shutdown( &mut self, stream_id: u64, direction: Shutdown, err: u64, ) -> Result<()> { + // Don't try to stop a local unidirectional stream. + if direction == Shutdown::Read && + stream::is_local(stream_id, self.is_server) && + !stream::is_bidi(stream_id) + { + return Err(Error::InvalidStreamState(stream_id)); + } + + // Dont' try to reset a remote unidirectional stream. + if direction == Shutdown::Write && + !stream::is_local(stream_id, self.is_server) && + !stream::is_bidi(stream_id) + { + return Err(Error::InvalidStreamState(stream_id)); + } + // Get existing stream. let stream = self.streams.get_mut(stream_id).ok_or(Error::Done)?; @@ -3934,6 +4747,9 @@ impl Connection { // buffered but not actually sent before the stream was reset. self.tx_data = self.tx_data.saturating_sub(unsent); + self.tx_buffered = + self.tx_buffered.saturating_sub(unsent as usize); + // Update send capacity. self.update_tx_cap(); @@ -3969,6 +4785,26 @@ impl Connection { Err(Error::InvalidStreamState(stream_id)) } + /// Returns the next stream that has data to read. + /// + /// Note that once returned by this method, a stream ID will not be returned + /// again until it is "re-armed". + /// + /// The application will need to read all of the pending data on the stream, + /// and new data has to be received before the stream is reported again. + /// + /// This is unlike the [`readable()`] method, that returns the same list of + /// readable streams when called multiple times in succession. + /// + /// [`readable()`]: struct.Connection.html#method.readable + pub fn stream_readable_next(&mut self) -> Option<u64> { + let &stream_id = self.streams.readable.iter().next()?; + + self.streams.mark_readable(stream_id, false); + + Some(stream_id) + } + /// Returns true if the stream has data that can be read. pub fn stream_readable(&self, stream_id: u64) -> bool { let stream = match self.streams.get(stream_id) { @@ -3980,13 +4816,62 @@ impl Connection { stream.is_readable() } + /// Returns the next stream that can be written to. + /// + /// Note that once returned by this method, a stream ID will not be returned + /// again until it is "re-armed". + /// + /// This is unlike the [`writable()`] method, that returns the same list of + /// writable streams when called multiple times in succession. It is not + /// advised to use both `stream_writable_next()` and [`writable()`] on the + /// same connection, as it may lead to unexpected results. + /// + /// The [`stream_writable()`] method can also be used to fine-tune when a + /// stream is reported as writable again. + /// + /// [`stream_writable()`]: struct.Connection.html#method.stream_writable + /// [`writable()`]: struct.Connection.html#method.writable + pub fn stream_writable_next(&mut self) -> Option<u64> { + // If there is not enough connection-level send capacity, none of the + // streams are writable. + if self.tx_cap == 0 { + return None; + } + + for &stream_id in &self.streams.writable { + if let Some(stream) = self.streams.get(stream_id) { + let cap = match stream.send.cap() { + Ok(v) => v, + + // Return the stream to the application immediately if it's + // stopped. + Err(_) => + return { + self.streams.mark_writable(stream_id, false); + Some(stream_id) + }, + }; + + if cmp::min(self.tx_cap, cap) >= stream.send_lowat { + self.streams.mark_writable(stream_id, false); + return Some(stream_id); + } + } + } + + None + } + /// Returns true if the stream has enough send capacity. /// /// When `len` more bytes can be buffered into the given stream's send /// buffer, `true` will be returned, `false` otherwise. /// /// In the latter case, if the additional data can't be buffered due to - /// flow control limits, the peer will also be notified. + /// flow control limits, the peer will also be notified, and a "low send + /// watermark" will be set for the stream, such that it is not going to be + /// reported as writable again by [`stream_writable_next()`] until its send + /// capacity reaches `len`. /// /// If the specified stream doesn't exist (including when it has already /// been completed and closed), the [`InvalidStreamState`] error will be @@ -3996,6 +4881,7 @@ impl Connection { /// any more data from this stream by sending the `STOP_SENDING` frame, the /// [`StreamStopped`] error will be returned. /// + /// [`stream_writable_next()`]: struct.Connection.html#method.stream_writable_next /// [`InvalidStreamState`]: enum.Error.html#variant.InvalidStreamState /// [`StreamStopped`]: enum.Error.html#variant.StreamStopped #[inline] @@ -4006,19 +4892,34 @@ impl Connection { return Ok(true); } - let stream = match self.streams.get(stream_id) { + let stream = match self.streams.get_mut(stream_id) { Some(v) => v, None => return Err(Error::InvalidStreamState(stream_id)), }; + stream.send_lowat = cmp::max(1, len); + + let is_writable = stream.is_writable(); + if self.max_tx_data - self.tx_data < len as u64 { self.blocked_limit = Some(self.max_tx_data); } if stream.send.cap()? < len { let max_off = stream.send.max_off(); - self.streams.mark_blocked(stream_id, true, max_off); + if stream.send.blocked_at() != Some(max_off) { + stream.send.update_blocked_at(Some(max_off)); + self.streams.mark_blocked(stream_id, true, max_off); + } + } else if is_writable { + // When `stream_writable_next()` returns a stream, the writable + // mark is removed, but because the stream is blocked by the + // connection-level send capacity it won't be marked as writable + // again once the capacity increases. + // + // Since the stream is writable already, mark it here instead. + self.streams.mark_writable(stream_id, true); } Ok(false) @@ -4124,8 +5025,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// // Iterate over readable streams. /// for stream_id in conn.readable() { /// // Stream is readable, read until there's no more data. @@ -4159,8 +5061,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let local = socket.local_addr().unwrap(); + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// // Iterate over writable streams. /// for stream_id in conn.writable() { /// // Stream is writable, write some data. @@ -4195,15 +5098,63 @@ impl Connection { /// struct.Config.html#method.set_max_send_udp_payload_size /// [`send()`]: struct.Connection.html#method.send pub fn max_send_udp_payload_size(&self) -> usize { - if self.is_established() { - // We cap the maximum packet size to 16KB or so, so that it can be - // always encoded with a 2-byte varint. - cmp::min(16383, self.recovery.max_datagram_size()) - } else { - // Allow for 1200 bytes (minimum QUIC packet size) during the - // handshake. - MIN_CLIENT_INITIAL_LEN + let max_datagram_size = self + .paths + .get_active() + .ok() + .map(|p| p.recovery.max_datagram_size()); + + if let Some(max_datagram_size) = max_datagram_size { + if self.is_established() { + // We cap the maximum packet size to 16KB or so, so that it can be + // always encoded with a 2-byte varint. + return cmp::min(16383, max_datagram_size); + } + } + + // Allow for 1200 bytes (minimum QUIC packet size) during the + // handshake. + MIN_CLIENT_INITIAL_LEN + } + + /// Schedule an ack-eliciting packet on the active path. + /// + /// QUIC packets might not contain ack-eliciting frames during normal + /// operating conditions. If the packet would already contain + /// ack-eliciting frames, this method does not change any behavior. + /// However, if the packet would not ordinarily contain ack-eliciting + /// frames, this method ensures that a PING frame sent. + /// + /// Calling this method multiple times before [`send()`] has no effect. + /// + /// [`send()`]: struct.Connection.html#method.send + pub fn send_ack_eliciting(&mut self) -> Result<()> { + if self.is_closed() || self.is_draining() { + return Ok(()); } + self.paths.get_active_mut()?.needs_ack_eliciting = true; + Ok(()) + } + + /// Schedule an ack-eliciting packet on the specified path. + /// + /// See [`send_ack_eliciting()`] for more detail. [`InvalidState`] is + /// returned if there is no record of the path. + /// + /// [`send_ack_eliciting()`]: struct.Connection.html#method.send_ack_eliciting + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + pub fn send_ack_eliciting_on_path( + &mut self, local: SocketAddr, peer: SocketAddr, + ) -> Result<()> { + if self.is_closed() || self.is_draining() { + return Ok(()); + } + let path_id = self + .paths + .path_id_from_addrs(&(local, peer)) + .ok_or(Error::InvalidState)?; + self.paths.get_mut(path_id)?.needs_ack_eliciting = true; + Ok(()) } /// Reads the first received DATAGRAM. @@ -4225,8 +5176,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// let mut dgram_buf = [0; 512]; /// while let Ok((len)) = conn.dgram_recv(&mut dgram_buf) { /// println!("Got {} bytes of DATAGRAM", len); @@ -4312,6 +5264,18 @@ impl Connection { self.dgram_send_queue.byte_size() } + /// Returns whether or not the DATAGRAM send queue is full. + #[inline] + pub fn is_dgram_send_queue_full(&self) -> bool { + self.dgram_send_queue.is_full() + } + + /// Returns whether or not the DATAGRAM recv queue is full. + #[inline] + pub fn is_dgram_recv_queue_full(&self) -> bool { + self.dgram_recv_queue.is_full() + } + /// Sends data in a DATAGRAM frame. /// /// [`Done`] is returned if no data was written. @@ -4338,8 +5302,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// conn.dgram_send(b"hello")?; /// # Ok::<(), quiche::Error>(()) /// ``` @@ -4356,8 +5321,12 @@ impl Connection { self.dgram_send_queue.push(buf.to_vec())?; - if self.dgram_send_queue.byte_size() > self.recovery.cwnd_available() { - self.recovery.update_app_limited(false); + let active_path = self.paths.get_active_mut()?; + + if self.dgram_send_queue.byte_size() > + active_path.recovery.cwnd_available() + { + active_path.recovery.update_app_limited(false); } Ok(()) @@ -4382,8 +5351,12 @@ impl Connection { self.dgram_send_queue.push(buf)?; - if self.dgram_send_queue.byte_size() > self.recovery.cwnd_available() { - self.recovery.update_app_limited(false); + let active_path = self.paths.get_active_mut()?; + + if self.dgram_send_queue.byte_size() > + active_path.recovery.cwnd_available() + { + active_path.recovery.update_app_limited(false); } Ok(()) @@ -4398,8 +5371,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// conn.dgram_send(b"hello")?; /// conn.dgram_purge_outgoing(&|d: &[u8]| -> bool { d[0] == 0 }); /// # Ok::<(), quiche::Error>(()) @@ -4421,8 +5395,9 @@ impl Connection { /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); - /// # let from = "127.0.0.1:1234".parse().unwrap(); - /// # let mut conn = quiche::accept(&scid, None, from, &mut config)?; + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let local = socket.local_addr().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; /// if let Some(payload_size) = conn.dgram_max_writable_len() { /// if payload_size > 5 { /// conn.dgram_send(b"hello")?; @@ -4435,16 +5410,17 @@ impl Connection { match self.peer_transport_params.max_datagram_frame_size { None => None, Some(peer_frame_len) => { + let dcid = self.destination_id(); // Start from the maximum packet size... let mut max_len = self.max_send_udp_payload_size(); // ...subtract the Short packet header overhead... // (1 byte of pkt_len + len of dcid) - max_len = max_len.saturating_sub(1 + self.dcid.len()); + max_len = max_len.saturating_sub(1 + dcid.len()); // ...subtract the packet number (max len)... max_len = max_len.saturating_sub(packet::MAX_PKT_NUM_LEN); // ...subtract the crypto overhead... max_len = max_len.saturating_sub( - self.pkt_num_spaces[packet::EPOCH_APPLICATION] + self.pkt_num_spaces[packet::Epoch::Application] .crypto_overhead()?, ); // ...clamp to what peer can support... @@ -4462,18 +5438,19 @@ impl Connection { .is_some() } - /// Returns the amount of time until the next timeout event. + /// Returns when the next timeout event will occur. /// - /// Once the given duration has elapsed, the [`on_timeout()`] method should - /// be called. A timeout of `None` means that the timer should be disarmed. + /// Once the timeout Instant has been reached, the [`on_timeout()`] method + /// should be called. A timeout of `None` means that the timer should be + /// disarmed. /// /// [`on_timeout()`]: struct.Connection.html#method.on_timeout - pub fn timeout(&self) -> Option<time::Duration> { + pub fn timeout_instant(&self) -> Option<time::Instant> { if self.is_closed() { return None; } - let timeout = if self.is_draining() { + if self.is_draining() { // Draining timer takes precedence over all other timers. If it is // set it means the connection is closing so there's no point in // processing the other timers. @@ -4483,22 +5460,40 @@ impl Connection { // detection timers. If they are both unset (i.e. `None`) then the // result is `None`, but if at least one of them is set then a // `Some(...)` value is returned. - let timers = [self.idle_timer, self.recovery.loss_detection_timer()]; + let path_timer = self + .paths + .iter() + .filter_map(|(_, p)| p.recovery.loss_detection_timer()) + .min(); + + let key_update_timer = self.pkt_num_spaces + [packet::Epoch::Application] + .key_update + .as_ref() + .map(|key_update| key_update.timer); + + let timers = [self.idle_timer, path_timer, key_update_timer]; timers.iter().filter_map(|&x| x).min() - }; + } + } - if let Some(timeout) = timeout { + /// Returns the amount of time until the next timeout event. + /// + /// Once the given duration has elapsed, the [`on_timeout()`] method should + /// be called. A timeout of `None` means that the timer should be disarmed. + /// + /// [`on_timeout()`]: struct.Connection.html#method.on_timeout + pub fn timeout(&self) -> Option<time::Duration> { + self.timeout_instant().map(|timeout| { let now = time::Instant::now(); if timeout <= now { - return Some(time::Duration::ZERO); + time::Duration::ZERO + } else { + timeout.duration_since(now) } - - return Some(timeout.duration_since(now)); - } - - None + }) } /// Processes a timeout event. @@ -4538,22 +5533,415 @@ impl Connection { } } - if let Some(timer) = self.recovery.loss_detection_timer() { + if let Some(timer) = self.pkt_num_spaces[packet::Epoch::Application] + .key_update + .as_ref() + .map(|key_update| key_update.timer) + { if timer <= now { - trace!("{} loss detection timeout expired", self.trace_id); + // Discard previous key once key update timer expired. + let _ = self.pkt_num_spaces[packet::Epoch::Application] + .key_update + .take(); + } + } - self.recovery.on_loss_detection_timeout( - self.handshake_status(), - now, - &self.trace_id, - ); + let handshake_status = self.handshake_status(); - qlog_with_type!(QLOG_METRICS, self.qlog, q, { - if let Some(ev_data) = self.recovery.maybe_qlog() { - q.add_event_data_with_instant(ev_data, now).ok(); - } - }); + for (_, p) in self.paths.iter_mut() { + if let Some(timer) = p.recovery.loss_detection_timer() { + if timer <= now { + trace!("{} loss detection timeout expired", self.trace_id); + + let (lost_packets, lost_bytes) = p.on_loss_detection_timeout( + handshake_status, + now, + self.is_server, + &self.trace_id, + ); + + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; + + qlog_with_type!(QLOG_METRICS, self.qlog, q, { + if let Some(ev_data) = p.recovery.maybe_qlog() { + q.add_event_data_with_instant(ev_data, now).ok(); + } + }); + } + } + } + + // Notify timeout events to the application. + self.paths.notify_failed_validations(); + + // If the active path failed, try to find a new candidate. + if self.paths.get_active_path_id().is_err() { + match self.paths.find_candidate_path() { + Some(pid) => + if self.paths.set_active_path(pid).is_err() { + // The connection cannot continue. + self.closed = true; + }, + + // The connection cannot continue. + None => self.closed = true, + } + } + } + + /// Requests the stack to perform path validation of the proposed 4-tuple. + /// + /// Probing new paths requires spare Connection IDs at both the host and the + /// peer sides. If it is not the case, it raises an [`OutOfIdentifiers`]. + /// + /// The probing of new addresses can only be done by the client. The server + /// can only probe network paths that were previously advertised by + /// [`NewPath`]. If the server tries to probe such an unseen network path, + /// this call raises an [`InvalidState`]. + /// + /// The caller might also want to probe an existing path. In such case, it + /// triggers a PATH_CHALLENGE frame, but it does not require spare CIDs. + /// + /// A server always probes a new path it observes. Calling this method is + /// hence not required to validate a new path. However, a server can still + /// request an additional path validation of the proposed 4-tuple. + /// + /// Calling this method several times before calling [`send()`] or + /// [`send_on_path()`] results in a single probe being generated. An + /// application wanting to send multiple in-flight probes must call this + /// method again after having sent packets. + /// + /// Returns the Destination Connection ID sequence number associated to that + /// path. + /// + /// [`NewPath`]: enum.QuicEvent.html#NewPath + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`send()`]: struct.Connection.html#method.send + /// [`send_on_path()`]: struct.Connection.html#method.send_on_path + pub fn probe_path( + &mut self, local_addr: SocketAddr, peer_addr: SocketAddr, + ) -> Result<u64> { + // We may want to probe an existing path. + let pid = match self.paths.path_id_from_addrs(&(local_addr, peer_addr)) { + Some(pid) => pid, + None => self.create_path_on_client(local_addr, peer_addr)?, + }; + + let path = self.paths.get_mut(pid)?; + path.request_validation(); + + path.active_dcid_seq.ok_or(Error::InvalidState) + } + + /// Migrates the connection to a new local address `local_addr`. + /// + /// The behavior is similar to [`migrate()`], with the nuance that the + /// connection only changes the local address, but not the peer one. + /// + /// See [`migrate()`] for the full specification of this method. + /// + /// [`migrate()`]: struct.Connection.html#method.migrate + pub fn migrate_source(&mut self, local_addr: SocketAddr) -> Result<u64> { + let peer_addr = self.paths.get_active()?.peer_addr(); + self.migrate(local_addr, peer_addr) + } + + /// Migrates the connection over the given network path between `local_addr` + /// and `peer_addr`. + /// + /// Connection migration can only be initiated by the client. Calling this + /// method as a server returns [`InvalidState`]. + /// + /// To initiate voluntary migration, there should be enough Connection IDs + /// at both sides. If this requirement is not satisfied, this call returns + /// [`OutOfIdentifiers`]. + /// + /// Returns the Destination Connection ID associated to that migrated path. + /// + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + /// [`InvalidState`]: enum.Error.html#InvalidState + pub fn migrate( + &mut self, local_addr: SocketAddr, peer_addr: SocketAddr, + ) -> Result<u64> { + if self.is_server { + return Err(Error::InvalidState); + } + + // If the path already exists, mark it as the active one. + let (pid, dcid_seq) = if let Some(pid) = + self.paths.path_id_from_addrs(&(local_addr, peer_addr)) + { + let path = self.paths.get_mut(pid)?; + + // If it is already active, do nothing. + if path.active() { + return path.active_dcid_seq.ok_or(Error::OutOfIdentifiers); + } + + // Ensures that a Source Connection ID has been dedicated to this + // path, or a free one is available. This is only required if the + // host uses non-zero length Source Connection IDs. + if !self.ids.zero_length_scid() && + path.active_scid_seq.is_none() && + self.ids.available_scids() == 0 + { + return Err(Error::OutOfIdentifiers); + } + + // Ensures that the migrated path has a Destination Connection ID. + let dcid_seq = if let Some(dcid_seq) = path.active_dcid_seq { + dcid_seq + } else { + let dcid_seq = self + .ids + .lowest_available_dcid_seq() + .ok_or(Error::OutOfIdentifiers)?; + + self.ids.link_dcid_to_path_id(dcid_seq, pid)?; + path.active_dcid_seq = Some(dcid_seq); + + dcid_seq + }; + + (pid, dcid_seq) + } else { + let pid = self.create_path_on_client(local_addr, peer_addr)?; + + let dcid_seq = self + .paths + .get(pid)? + .active_dcid_seq + .ok_or(Error::InvalidState)?; + + (pid, dcid_seq) + }; + + // Change the active path. + self.paths.set_active_path(pid)?; + + Ok(dcid_seq) + } + + /// Provides additional source Connection IDs that the peer can use to reach + /// this host. + /// + /// This triggers sending NEW_CONNECTION_ID frames if the provided Source + /// Connection ID is not already present. In the case the caller tries to + /// reuse a Connection ID with a different reset token, this raises an + /// `InvalidState`. + /// + /// At any time, the peer cannot have more Destination Connection IDs than + /// the maximum number of active Connection IDs it negotiated. In such case + /// (i.e., when [`source_cids_left()`] returns 0), if the host agrees to + /// request the removal of previous connection IDs, it sets the + /// `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is returned. + /// + /// Note that setting `retire_if_needed` does not prevent this function from + /// returning an [`IdLimit`] in the case the caller wants to retire still + /// unannounced Connection IDs. + /// + /// The caller is responsible from ensuring that the provided `scid` is not + /// repeated several times over the connection. quiche ensures that as long + /// as the provided Connection ID is still in use (i.e., not retired), it + /// does not assign a different sequence number. + /// + /// Note that if the host uses zero-length Source Connection IDs, it cannot + /// advertise Source Connection IDs and calling this method returns an + /// [`InvalidState`]. + /// + /// Returns the sequence number associated to the provided Connection ID. + /// + /// [`source_cids_left()`]: struct.Connection.html#method.source_cids_left + /// [`IdLimit`]: enum.Error.html#IdLimit + /// [`InvalidState`]: enum.Error.html#InvalidState + pub fn new_source_cid( + &mut self, scid: &ConnectionId, reset_token: u128, retire_if_needed: bool, + ) -> Result<u64> { + self.ids.new_scid( + scid.to_vec().into(), + Some(reset_token), + true, + None, + retire_if_needed, + ) + } + + /// Returns the number of source Connection IDs that are active. This is + /// only meaningful if the host uses non-zero length Source Connection IDs. + pub fn active_source_cids(&self) -> usize { + self.ids.active_source_cids() + } + + /// Returns the maximum number of concurrently active source Connection IDs + /// that can be provided to the peer. + pub fn max_active_source_cids(&self) -> usize { + self.peer_transport_params.active_conn_id_limit as usize + } + + /// Returns the number of source Connection IDs that can still be provided + /// to the peer without exceeding the limit it advertised. + /// + /// The application should not issue the maximum number of permitted source + /// Connection IDs, but instead treat this as an untrusted upper bound. + /// Applications should limit how many outstanding source ConnectionIDs + /// are simultaneously issued to prevent issuing more than they can handle. + #[inline] + pub fn source_cids_left(&self) -> usize { + self.max_active_source_cids() - self.active_source_cids() + } + + /// Requests the retirement of the destination Connection ID used by the + /// host to reach its peer. + /// + /// This triggers sending RETIRE_CONNECTION_ID frames. + /// + /// If the application tries to retire a non-existing Destination Connection + /// ID sequence number, or if it uses zero-length Destination Connection ID, + /// this method returns an [`InvalidState`]. + /// + /// At any time, the host must have at least one Destination ID. If the + /// application tries to retire the last one, or if the caller tries to + /// retire the destination Connection ID used by the current active path + /// while having neither spare Destination Connection IDs nor validated + /// network paths, this method returns an [`OutOfIdentifiers`]. This + /// behavior prevents the caller from stalling the connection due to the + /// lack of validated path to send non-probing packets. + /// + /// [`InvalidState`]: enum.Error.html#InvalidState + /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers + pub fn retire_destination_cid(&mut self, dcid_seq: u64) -> Result<()> { + if self.ids.zero_length_dcid() { + return Err(Error::InvalidState); + } + + let active_path_dcid_seq = self + .paths + .get_active()? + .active_dcid_seq + .ok_or(Error::InvalidState)?; + + let active_path_id = self.paths.get_active_path_id()?; + + if active_path_dcid_seq == dcid_seq && + self.ids.lowest_available_dcid_seq().is_none() && + !self + .paths + .iter() + .any(|(pid, p)| pid != active_path_id && p.usable()) + { + return Err(Error::OutOfIdentifiers); + } + + if let Some(pid) = self.ids.retire_dcid(dcid_seq)? { + // The retired Destination CID was associated to a given path. Let's + // find an available DCID to associate to that path. + let path = self.paths.get_mut(pid)?; + let dcid_seq = self.ids.lowest_available_dcid_seq(); + + if let Some(dcid_seq) = dcid_seq { + self.ids.link_dcid_to_path_id(dcid_seq, pid)?; } + + path.active_dcid_seq = dcid_seq; + } + + Ok(()) + } + + /// Processes path-specific events. + /// + /// On success it returns a [`PathEvent`], or `None` when there are no + /// events to report. Please refer to [`PathEvent`] for the exhaustive event + /// list. + /// + /// Note that all events are edge-triggered, meaning that once reported they + /// will not be reported again by calling this method again, until the event + /// is re-armed. + /// + /// [`PathEvent`]: enum.PathEvent.html + pub fn path_event_next(&mut self) -> Option<PathEvent> { + self.paths.pop_event() + } + + /// Returns a source `ConnectionId` that has been retired. + /// + /// On success it returns a [`ConnectionId`], or `None` when there are no + /// more retired connection IDs. + /// + /// [`ConnectionId`]: struct.ConnectionId.html + pub fn retired_scid_next(&mut self) -> Option<ConnectionId<'static>> { + self.ids.pop_retired_scid() + } + + /// Returns the number of spare Destination Connection IDs, i.e., + /// Destination Connection IDs that are still unused. + /// + /// Note that this function returns 0 if the host uses zero length + /// Destination Connection IDs. + pub fn available_dcids(&self) -> usize { + self.ids.available_dcids() + } + + /// Returns an iterator over destination `SockAddr`s whose association + /// with `from` forms a known QUIC path on which packets can be sent to. + /// + /// This function is typically used in combination with [`send_on_path()`]. + /// + /// Note that the iterator includes all the possible combination of + /// destination `SockAddr`s, even those whose sending is not required now. + /// In other words, this is another way for the application to recall from + /// past [`NewPath`] events. + /// + /// [`NewPath`]: enum.QuicEvent.html#NewPath + /// [`send_on_path()`]: struct.Connection.html#method.send_on_path + /// + /// ## Examples: + /// + /// ```no_run + /// # let mut out = [0; 512]; + /// # let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?; + /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]); + /// # let local = socket.local_addr().unwrap(); + /// # let peer = "127.0.0.1:1234".parse().unwrap(); + /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?; + /// // Iterate over possible destinations for the given local `SockAddr`. + /// for dest in conn.paths_iter(local) { + /// loop { + /// let (write, send_info) = + /// match conn.send_on_path(&mut out, Some(local), Some(dest)) { + /// Ok(v) => v, + /// + /// Err(quiche::Error::Done) => { + /// // Done writing for this destination. + /// break; + /// }, + /// + /// Err(e) => { + /// // An error occurred, handle it. + /// break; + /// }, + /// }; + /// + /// socket.send_to(&out[..write], &send_info.to).unwrap(); + /// } + /// } + /// # Ok::<(), quiche::Error>(()) + /// ``` + #[inline] + pub fn paths_iter(&self, from: SocketAddr) -> SocketAddrIter { + // Instead of trying to identify whether packets will be sent on the + // given 4-tuple, simply filter paths that cannot be used. + SocketAddrIter { + sockaddrs: self + .paths + .iter() + .filter(|(_, p)| p.usable() || p.probing_required()) + .filter(|(_, p)| p.local_addr() == from) + .map(|(_, p)| p.peer_addr()) + .collect(), } } @@ -4562,6 +5950,13 @@ impl Connection { /// The `app` parameter specifies whether an application close should be /// sent to the peer. Otherwise a normal connection close is sent. /// + /// If `app` is true but the connection is not in a state that is safe to + /// send an application error (not established nor in early data), in + /// accordance with [RFC + /// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.3-3), the + /// error code is changed to APPLICATION_ERROR and the reason phrase is + /// cleared. + /// /// Returns [`Done`] if the connection had already been closed. /// /// Note that the connection will not be closed immediately. An application @@ -4584,11 +5979,23 @@ impl Connection { return Err(Error::Done); } - self.local_error = Some(ConnectionError { - is_app: app, - error_code: err, - reason: reason.to_vec(), - }); + let is_safe_to_send_app_data = + self.is_established() || self.is_in_early_data(); + + if app && !is_safe_to_send_app_data { + // Clear error information. + self.local_error = Some(ConnectionError { + is_app: false, + error_code: 0x0c, + reason: vec![], + }); + } else { + self.local_error = Some(ConnectionError { + is_app: app, + error_code: err, + reason: reason.to_vec(), + }); + } // When no packet was successfully processed close connection immediately. if self.recv_count == 0 { @@ -4627,6 +6034,17 @@ impl Connection { self.handshake.peer_cert() } + /// Returns the peer's certificate chain (if any) as a vector of DER-encoded + /// buffers. + /// + /// The certificate at index 0 is the peer's leaf certificate, the other + /// certificates (if any) are the chain certificate authorities used to + /// sign the leaf certificate. + #[inline] + pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> { + self.handshake.peer_cert_chain() + } + /// Returns the serialized cryptographic session for the connection. /// /// This can be used by a client to cache a connection's session, and resume @@ -4644,7 +6062,16 @@ impl Connection { /// lifetime. #[inline] pub fn source_id(&self) -> ConnectionId { - ConnectionId::from_ref(self.scid.as_ref()) + if let Ok(path) = self.paths.get_active() { + if let Some(active_scid_seq) = path.active_scid_seq { + if let Ok(e) = self.ids.get_scid(active_scid_seq) { + return ConnectionId::from_ref(e.cid.as_ref()); + } + } + } + + let e = self.ids.oldest_scid(); + ConnectionId::from_ref(e.cid.as_ref()) } /// Returns the destination connection ID. @@ -4653,7 +6080,16 @@ impl Connection { /// lifetime. #[inline] pub fn destination_id(&self) -> ConnectionId { - ConnectionId::from_ref(self.dcid.as_ref()) + if let Ok(path) = self.paths.get_active() { + if let Some(active_dcid_seq) = path.active_dcid_seq { + if let Ok(e) = self.ids.get_dcid(active_dcid_seq) { + return ConnectionId::from_ref(e.cid.as_ref()); + } + } + } + + let e = self.ids.oldest_dcid(); + ConnectionId::from_ref(e.cid.as_ref()) } /// Returns true if the connection handshake is complete. @@ -4681,13 +6117,33 @@ impl Connection { self.streams.has_readable() || self.dgram_recv_front_len().is_some() } + /// Returns whether the network path with local address `from` and remote + /// address `peer` has been validated. + /// + /// If the 4-tuple does not exist over the connection, returns an + /// [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + pub fn is_path_validated( + &self, from: SocketAddr, to: SocketAddr, + ) -> Result<bool> { + let pid = self + .paths + .path_id_from_addrs(&(from, to)) + .ok_or(Error::InvalidState)?; + + Ok(self.paths.get(pid)?.validated()) + } + /// Returns true if the connection is draining. /// - /// If this returns true, the connection object cannot yet be dropped, but + /// If this returns `true`, the connection object cannot yet be dropped, but /// no new application data can be sent or received. An application should - /// continue calling the [`recv()`], [`send()`], [`timeout()`], and - /// [`on_timeout()`] methods as normal, until the [`is_closed()`] method - /// returns `true`. + /// continue calling the [`recv()`], [`timeout()`], and [`on_timeout()`] + /// methods as normal, until the [`is_closed()`] method returns `true`. + /// + /// In contrast, once `is_draining()` returns `true`, calling [`send()`] + /// is not required because no new outgoing packets will be generated. /// /// [`recv()`]: struct.Connection.html#method.recv /// [`send()`]: struct.Connection.html#method.send @@ -4745,16 +6201,13 @@ impl Connection { Stats { recv: self.recv_count, sent: self.sent_count, - lost: self.recovery.lost_count, + lost: self.lost_count, retrans: self.retrans_count, - cwnd: self.recovery.cwnd(), - rtt: self.recovery.rtt(), sent_bytes: self.sent_bytes, - lost_bytes: self.recovery.bytes_lost, recv_bytes: self.recv_bytes, + lost_bytes: self.lost_bytes, stream_retrans_bytes: self.stream_retrans_bytes, - pmtu: self.recovery.max_datagram_size(), - delivery_rate: self.recovery.delivery_rate(), + paths_count: self.paths.len(), peer_max_idle_timeout: self.peer_transport_params.max_idle_timeout, peer_max_udp_payload_size: self .peer_transport_params @@ -4791,6 +6244,12 @@ impl Connection { } } + /// Collects and returns statistics about each known path for the + /// connection. + pub fn path_stats(&self) -> impl Iterator<Item = PathStats> + '_ { + self.paths.iter().map(|(_, p)| p.stats()) + } + fn encode_transport_params(&mut self) -> Result<()> { let mut raw_params = [0; 128]; @@ -4813,7 +6272,7 @@ impl Connection { { // Validate initial_source_connection_id. match &peer_params.initial_source_connection_id { - Some(v) if v != &self.dcid => + Some(v) if v != &self.destination_id() => return Err(Error::InvalidTransportParam), Some(_) => (), @@ -4863,14 +6322,16 @@ impl Connection { } } - self.process_peer_transport_params(peer_params); + self.process_peer_transport_params(peer_params)?; self.parsed_peer_transport_params = true; Ok(()) } - fn process_peer_transport_params(&mut self, peer_params: TransportParams) { + fn process_peer_transport_params( + &mut self, peer_params: TransportParams, + ) -> Result<()> { self.max_tx_data = peer_params.initial_max_data; // Update send capacity. @@ -4881,19 +6342,32 @@ impl Connection { self.streams .update_peer_max_streams_uni(peer_params.initial_max_streams_uni); - self.recovery.max_ack_delay = + let max_ack_delay = time::Duration::from_millis(peer_params.max_ack_delay); - self.recovery + self.recovery_config.max_ack_delay = max_ack_delay; + + let active_path = self.paths.get_active_mut()?; + + active_path.recovery.max_ack_delay = max_ack_delay; + + active_path + .recovery .update_max_datagram_size(peer_params.max_udp_payload_size as usize); + // Record the max_active_conn_id parameter advertised by the peer. + self.ids + .set_source_conn_id_limit(peer_params.active_conn_id_limit); + self.peer_transport_params = peer_params; + + Ok(()) } /// Continues the handshake. /// /// If the connection is already established, it does nothing. - fn do_handshake(&mut self) -> Result<()> { + fn do_handshake(&mut self, now: time::Instant) -> Result<()> { let mut ex_data = tls::ExData { application_protos: &self.application_protos, @@ -4952,26 +6426,35 @@ impl Connection { self.parse_peer_transport_params(peer_params)?; } - // Once the handshake is completed there's no point in processing 0-RTT - // packets anymore, so clear the buffer now. if self.handshake_completed { + // The handshake is considered confirmed at the server when the + // handshake completes, at which point we can also drop the + // handshake epoch. + if self.is_server { + self.handshake_confirmed = true; + + self.drop_epoch_state(packet::Epoch::Handshake, now); + } + + // Once the handshake is completed there's no point in processing + // 0-RTT packets anymore, so clear the buffer now. self.undecryptable_pkts.clear(); - } - trace!("{} connection established: proto={:?} cipher={:?} curve={:?} sigalg={:?} resumed={} {:?}", - &self.trace_id, - std::str::from_utf8(self.application_proto()), - self.handshake.cipher(), - self.handshake.curve(), - self.handshake.sigalg(), - self.handshake.is_resumed(), - self.peer_transport_params); + trace!("{} connection established: proto={:?} cipher={:?} curve={:?} sigalg={:?} resumed={} {:?}", + &self.trace_id, + std::str::from_utf8(self.application_proto()), + self.handshake.cipher(), + self.handshake.curve(), + self.handshake.sigalg(), + self.handshake.is_resumed(), + self.peer_transport_params); + } Ok(()) } /// Selects the packet type for the next outgoing packet. - fn write_pkt_type(&self) -> Result<packet::Type> { + fn write_pkt_type(&self, send_pid: usize) -> Result<packet::Type> { // On error send packet in the latest epoch available, but only send // 1-RTT ones when the handshake is completed. if self @@ -4980,22 +6463,36 @@ impl Connection { .map_or(false, |conn_err| !conn_err.is_app) { let epoch = match self.handshake.write_level() { - crypto::Level::Initial => packet::EPOCH_INITIAL, + crypto::Level::Initial => packet::Epoch::Initial, crypto::Level::ZeroRTT => unreachable!(), - crypto::Level::Handshake => packet::EPOCH_HANDSHAKE, - crypto::Level::OneRTT => packet::EPOCH_APPLICATION, + crypto::Level::Handshake => packet::Epoch::Handshake, + crypto::Level::OneRTT => packet::Epoch::Application, }; - if epoch == packet::EPOCH_APPLICATION && !self.is_established() { - // Downgrade the epoch to handshake as the handshake is not - // completed yet. - return Ok(packet::Type::Handshake); + if !self.is_established() { + match epoch { + // Downgrade the epoch to Handshake as the handshake is not + // completed yet. + packet::Epoch::Application => + return Ok(packet::Type::Handshake), + + // Downgrade the epoch to Initial as the remote peer might + // not be able to decrypt handshake packets yet. + packet::Epoch::Handshake + if self.pkt_num_spaces[packet::Epoch::Initial] + .has_keys() => + return Ok(packet::Type::Initial), + + _ => (), + }; } return Ok(packet::Type::from_epoch(epoch)); } - for epoch in packet::EPOCH_INITIAL..packet::EPOCH_COUNT { + for &epoch in packet::Epoch::epochs( + packet::Epoch::Initial..=packet::Epoch::Application, + ) { // Only send packets in a space when we have the send keys for it. if self.pkt_num_spaces[epoch].crypto_seal.is_none() { continue; @@ -5007,18 +6504,21 @@ impl Connection { } // There are lost frames in this packet number space. - if !self.recovery.lost[epoch].is_empty() { - return Ok(packet::Type::from_epoch(epoch)); - } + for (_, p) in self.paths.iter() { + if !p.recovery.lost[epoch].is_empty() { + return Ok(packet::Type::from_epoch(epoch)); + } - // We need to send PTO probe packets. - if self.recovery.loss_probes[epoch] > 0 { - return Ok(packet::Type::from_epoch(epoch)); + // We need to send PTO probe packets. + if p.recovery.loss_probes[epoch] > 0 { + return Ok(packet::Type::from_epoch(epoch)); + } } } // If there are flushable, almost full or blocked streams, use the // Application epoch. + let send_path = self.paths.get(send_pid)?; if (self.is_established() || self.is_in_early_data()) && (self.should_send_handshake_done() || self.almost_full || @@ -5033,7 +6533,11 @@ impl Connection { self.streams.has_almost_full() || self.streams.has_blocked() || self.streams.has_reset() || - self.streams.has_stopped()) + self.streams.has_stopped() || + self.ids.has_new_scids() || + self.ids.has_retire_dcids() || + send_path.needs_ack_eliciting || + send_path.probing_required()) { // Only clients can send 0-RTT packets. if !self.is_server && self.is_in_early_data() { @@ -5062,7 +6566,8 @@ impl Connection { /// Processes an incoming frame. fn process_frame( - &mut self, frame: frame::Frame, epoch: packet::Epoch, now: time::Instant, + &mut self, frame: frame::Frame, hdr: &packet::Header, + recv_path_id: usize, epoch: packet::Epoch, now: time::Instant, ) -> Result<()> { trace!("{} rx frm {:?}", self.trace_id, frame); @@ -5080,34 +6585,33 @@ impl Connection { )) .ok_or(Error::InvalidFrame)?; - if epoch == packet::EPOCH_HANDSHAKE { - self.peer_verified_address = true; + if epoch == packet::Epoch::Handshake || + (epoch == packet::Epoch::Application && + self.is_established()) + { + self.peer_verified_initial_address = true; } - // When we receive an ACK for a 1-RTT packet after handshake - // completion, it means the handshake has been confirmed. - if epoch == packet::EPOCH_APPLICATION && self.is_established() { - self.peer_verified_address = true; + let handshake_status = self.handshake_status(); - self.handshake_confirmed = true; - } - - if self.delivery_rate_check_if_app_limited() { - self.recovery.delivery_rate_update_app_limited(true); - } + let is_app_limited = self.delivery_rate_check_if_app_limited(); - self.recovery.on_ack_received( - &ranges, - ack_delay, - epoch, - self.handshake_status(), - now, - &self.trace_id, - )?; + for (_, p) in self.paths.iter_mut() { + if is_app_limited { + p.recovery.delivery_rate_update_app_limited(true); + } - // Once the handshake is confirmed, we can drop Handshake keys. - if self.handshake_confirmed { - self.drop_epoch_state(packet::EPOCH_HANDSHAKE, now); + let (lost_packets, lost_bytes) = p.recovery.on_ack_received( + &ranges, + ack_delay, + epoch, + handshake_status, + now, + &self.trace_id, + )?; + + self.lost_count += lost_packets; + self.lost_bytes += lost_bytes as u64; } }, @@ -5200,6 +6704,9 @@ impl Connection { // to touch it here. self.tx_data = self.tx_data.saturating_sub(unsent); + self.tx_buffered = + self.tx_buffered.saturating_sub(unsent as usize); + self.streams .mark_reset(stream_id, true, error_code, final_size); @@ -5226,7 +6733,7 @@ impl Connection { self.handshake.provide_data(level, recv_buf)?; } - self.do_handshake()?; + self.do_handshake(now)?; }, frame::Frame::CryptoHeader { .. } => unreachable!(), @@ -5272,6 +6779,8 @@ impl Connection { let was_readable = stream.is_readable(); + let was_draining = stream.is_draining(); + stream.recv.write(data)?; if !was_readable && stream.is_readable() { @@ -5279,6 +6788,18 @@ impl Connection { } self.rx_data += max_off_delta; + + if was_draining { + // When a stream is in draining state it will not queue + // incoming data for the application to read, so consider + // the received data as consumed, which might trigger a flow + // control update. + self.flow_control.add_consumed(max_off_delta); + + if self.should_update_max_data() { + self.almost_full = true; + } + } }, frame::Frame::StreamHeader { .. } => unreachable!(), @@ -5362,17 +6883,81 @@ impl Connection { return Err(Error::InvalidFrame); }, - // TODO: implement connection migration - frame::Frame::NewConnectionId { .. } => (), + frame::Frame::NewConnectionId { + seq_num, + retire_prior_to, + conn_id, + reset_token, + } => { + if self.ids.zero_length_dcid() { + return Err(Error::InvalidState); + } + + let retired_path_ids = self.ids.new_dcid( + conn_id.into(), + seq_num, + u128::from_be_bytes(reset_token), + retire_prior_to, + )?; + + for (dcid_seq, pid) in retired_path_ids { + let path = self.paths.get_mut(pid)?; + + // Maybe the path already switched to another DCID. + if path.active_dcid_seq != Some(dcid_seq) { + continue; + } + + if let Some(new_dcid_seq) = + self.ids.lowest_available_dcid_seq() + { + path.active_dcid_seq = Some(new_dcid_seq); + + self.ids.link_dcid_to_path_id(new_dcid_seq, pid)?; + + trace!( + "{} path ID {} changed DCID: old seq num {} new seq num {}", + self.trace_id, pid, dcid_seq, new_dcid_seq, + ); + } else { + // We cannot use this path anymore for now. + path.active_dcid_seq = None; + + trace!( + "{} path ID {} cannot be used; DCID seq num {} has been retired", + self.trace_id, pid, dcid_seq, + ); + } + } + }, + + frame::Frame::RetireConnectionId { seq_num } => { + if self.ids.zero_length_scid() { + return Err(Error::InvalidState); + } - // TODO: implement connection migration - frame::Frame::RetireConnectionId { .. } => (), + if let Some(pid) = self.ids.retire_scid(seq_num, &hdr.dcid)? { + let path = self.paths.get_mut(pid)?; + + // Maybe we already linked a new SCID to that path. + if path.active_scid_seq == Some(seq_num) { + // XXX: We do not remove unused paths now, we instead + // wait until we need to maintain more paths than the + // host is willing to. + path.active_scid_seq = None; + } + } + }, frame::Frame::PathChallenge { data } => { - self.challenge = Some(data); + self.paths + .get_mut(recv_path_id)? + .on_challenge_received(data); }, - frame::Frame::PathResponse { .. } => (), + frame::Frame::PathResponse { data } => { + self.paths.on_response_received(data)?; + }, frame::Frame::ConnectionClose { error_code, reason, .. @@ -5382,7 +6967,9 @@ impl Connection { error_code, reason, }); - self.draining_timer = Some(now + (self.recovery.pto() * 3)); + + let path = self.paths.get_active()?; + self.draining_timer = Some(now + (path.recovery.pto() * 3)); }, frame::Frame::ApplicationClose { error_code, reason } => { @@ -5391,7 +6978,9 @@ impl Connection { error_code, reason, }); - self.draining_timer = Some(now + (self.recovery.pto() * 3)); + + let path = self.paths.get_active()?; + self.draining_timer = Some(now + (path.recovery.pto() * 3)); }, frame::Frame::HandshakeDone => { @@ -5399,12 +6988,12 @@ impl Connection { return Err(Error::InvalidPacket); } - self.peer_verified_address = true; + self.peer_verified_initial_address = true; self.handshake_confirmed = true; // Once the handshake is confirmed, we can drop Handshake keys. - self.drop_epoch_state(packet::EPOCH_HANDSHAKE, now); + self.drop_epoch_state(packet::Epoch::Handshake, now); }, frame::Frame::Datagram { data } => { @@ -5440,11 +7029,11 @@ impl Connection { self.pkt_num_spaces[epoch].crypto_seal = None; self.pkt_num_spaces[epoch].clear(); - self.recovery.on_pkt_num_space_discarded( - epoch, - self.handshake_status(), - now, - ); + let handshake_status = self.handshake_status(); + for (_, p) in self.paths.iter_mut() { + p.recovery + .on_pkt_num_space_discarded(epoch, handshake_status, now); + } trace!("{} dropped epoch {} state", self.trace_id, epoch); } @@ -5462,11 +7051,6 @@ impl Connection { self.flow_control.max_data() } - /// Returns the updated connection level flow control limit. - fn max_rx_data_next(&self) -> u64 { - self.flow_control.max_data_next() - } - /// Returns true if the HANDSHAKE_DONE frame needs to be sent. fn should_send_handshake_done(&self) -> bool { self.is_established() && !self.handshake_done_sent && self.is_server @@ -5498,8 +7082,13 @@ impl Connection { ) }; + let path_pto = match self.paths.get_active() { + Ok(p) => p.recovery.pto(), + Err(_) => time::Duration::ZERO, + }; + let idle_timeout = time::Duration::from_millis(idle_timeout); - let idle_timeout = cmp::max(idle_timeout, 3 * self.recovery.pto()); + let idle_timeout = cmp::max(idle_timeout, 3 * path_pto); Some(idle_timeout) } @@ -5507,10 +7096,10 @@ impl Connection { /// Returns the connection's handshake status for use in loss recovery. fn handshake_status(&self) -> recovery::HandshakeStatus { recovery::HandshakeStatus { - has_handshake_keys: self.pkt_num_spaces[packet::EPOCH_HANDSHAKE] + has_handshake_keys: self.pkt_num_spaces[packet::Epoch::Handshake] .has_keys(), - peer_verified_address: self.peer_verified_address, + peer_verified_address: self.peer_verified_initial_address, completed: self.is_established(), } @@ -5518,10 +7107,13 @@ impl Connection { /// Updates send capacity. fn update_tx_cap(&mut self) { - self.tx_cap = cmp::min( - self.recovery.cwnd_available() as u64, - self.max_tx_data - self.tx_data, - ) as usize; + let cwin_available = match self.paths.get_active() { + Ok(p) => p.recovery.cwnd_available() as u64, + Err(_) => 0, + }; + + self.tx_cap = + cmp::min(cwin_available, self.max_tx_data - self.tx_data) as usize; } fn delivery_rate_check_if_app_limited(&self) -> bool { @@ -5540,10 +7132,206 @@ impl Connection { // Note that this is equivalent to CheckIfApplicationLimited() from the // delivery rate draft. This is also separate from `recovery.app_limited` // and only applies to delivery rate calculation. - self.tx_cap >= self.recovery.cwnd_available() && + let cwin_available = self + .paths + .iter() + .filter_map(|(_, p)| p.active().then(|| p.recovery.cwnd_available())) + .sum(); + + ((self.tx_buffered + self.dgram_send_queue_len()) < cwin_available) && (self.tx_data.saturating_sub(self.last_tx_data)) < - self.recovery.cwnd_available() as u64 && - self.recovery.cwnd_available() > 0 + cwin_available as u64 && + cwin_available > 0 + } + + fn set_initial_dcid( + &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>, + path_id: usize, + ) -> Result<()> { + self.ids.set_initial_dcid(cid, reset_token, Some(path_id)); + self.paths.get_mut(path_id)?.active_dcid_seq = Some(0); + + Ok(()) + } + + /// Selects the path that the incoming packet belongs to, or creates a new + /// one if no existing path matches. + fn get_or_create_recv_path_id( + &mut self, recv_pid: Option<usize>, dcid: &ConnectionId, buf_len: usize, + info: &RecvInfo, + ) -> Result<usize> { + let ids = &mut self.ids; + + let (in_scid_seq, mut in_scid_pid) = + ids.find_scid_seq(dcid).ok_or(Error::InvalidState)?; + + if let Some(recv_pid) = recv_pid { + // If the path observes a change of SCID used, note it. + let recv_path = self.paths.get_mut(recv_pid)?; + + let cid_entry = + recv_path.active_scid_seq.and_then(|v| ids.get_scid(v).ok()); + + if cid_entry.map(|e| &e.cid) != Some(dcid) { + let incoming_cid_entry = ids.get_scid(in_scid_seq)?; + + let prev_recv_pid = + incoming_cid_entry.path_id.unwrap_or(recv_pid); + + if prev_recv_pid != recv_pid { + trace!( + "{} peer reused CID {:?} from path {} on path {}", + self.trace_id, + dcid, + prev_recv_pid, + recv_pid + ); + + // TODO: reset congestion control. + } + + trace!( + "{} path ID {} now see SCID with seq num {}", + self.trace_id, + recv_pid, + in_scid_seq + ); + + recv_path.active_scid_seq = Some(in_scid_seq); + ids.link_scid_to_path_id(in_scid_seq, recv_pid)?; + } + + return Ok(recv_pid); + } + + // This is a new 4-tuple. See if the CID has not been assigned on + // another path. + + // Ignore this step if are using zero-length SCID. + if ids.zero_length_scid() { + in_scid_pid = None; + } + + if let Some(in_scid_pid) = in_scid_pid { + // This CID has been used by another path. If we have the + // room to do so, create a new `Path` structure holding this + // new 4-tuple. Otherwise, drop the packet. + let old_path = self.paths.get_mut(in_scid_pid)?; + let old_local_addr = old_path.local_addr(); + let old_peer_addr = old_path.peer_addr(); + + trace!( + "{} reused CID seq {} of ({},{}) (path {}) on ({},{})", + self.trace_id, + in_scid_seq, + old_local_addr, + old_peer_addr, + in_scid_pid, + info.to, + info.from + ); + + // Notify the application. + self.paths + .notify_event(path::PathEvent::ReusedSourceConnectionId( + in_scid_seq, + (old_local_addr, old_peer_addr), + (info.to, info.from), + )); + } + + // This is a new path using an unassigned CID; create it! + let mut path = + path::Path::new(info.to, info.from, &self.recovery_config, false); + + path.max_send_bytes = buf_len * MAX_AMPLIFICATION_FACTOR; + path.active_scid_seq = Some(in_scid_seq); + + // Automatically probes the new path. + path.request_validation(); + + let pid = self.paths.insert_path(path, self.is_server)?; + + // Do not record path reuse. + if in_scid_pid.is_none() { + ids.link_scid_to_path_id(in_scid_seq, pid)?; + } + + Ok(pid) + } + + /// Selects the path on which the next packet must be sent. + fn get_send_path_id( + &self, from: Option<SocketAddr>, to: Option<SocketAddr>, + ) -> Result<usize> { + // A probing packet must be sent, but only if the connection is fully + // established. + if self.is_established() { + let mut probing = self + .paths + .iter() + .filter(|(_, p)| from.is_none() || Some(p.local_addr()) == from) + .filter(|(_, p)| to.is_none() || Some(p.peer_addr()) == to) + .filter(|(_, p)| p.active_dcid_seq.is_some()) + .filter(|(_, p)| p.probing_required()) + .map(|(pid, _)| pid); + + if let Some(pid) = probing.next() { + return Ok(pid); + } + } + + if let Some((pid, p)) = self.paths.get_active_with_pid() { + if from.is_some() && Some(p.local_addr()) != from { + return Err(Error::Done); + } + + if to.is_some() && Some(p.peer_addr()) != to { + return Err(Error::Done); + } + + return Ok(pid); + }; + + Err(Error::InvalidState) + } + + /// Creates a new client-side path. + fn create_path_on_client( + &mut self, local_addr: SocketAddr, peer_addr: SocketAddr, + ) -> Result<usize> { + if self.is_server { + return Err(Error::InvalidState); + } + + // If we use zero-length SCID and go over our local active CID limit, + // the `insert_path()` call will raise an error. + if !self.ids.zero_length_scid() && self.ids.available_scids() == 0 { + return Err(Error::OutOfIdentifiers); + } + + // Do we have a spare DCID? If we are using zero-length DCID, just use + // the default having sequence 0 (note that if we exceed our local CID + // limit, the `insert_path()` call will raise an error. + let dcid_seq = if self.ids.zero_length_dcid() { + 0 + } else { + self.ids + .lowest_available_dcid_seq() + .ok_or(Error::OutOfIdentifiers)? + }; + + let mut path = + path::Path::new(local_addr, peer_addr, &self.recovery_config, false); + path.active_dcid_seq = Some(dcid_seq); + + let pid = self + .paths + .insert_path(path, false) + .map_err(|_| Error::OutOfIdentifiers)?; + self.ids.link_dcid_to_path_id(dcid_seq, pid)?; + + Ok(pid) } } @@ -5582,12 +7370,26 @@ fn drop_pkt_on_err( Error::Done } +struct AddrTupleFmt(SocketAddr, SocketAddr); + +impl std::fmt::Display for AddrTupleFmt { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let AddrTupleFmt(src, dst) = &self; + + if src.ip().is_unspecified() || dst.ip().is_unspecified() { + return Ok(()); + } + + f.write_fmt(format_args!("src:{src} dst:{dst}")) + } +} + /// Statistics about the connection. /// /// A connection's statistics can be collected using the [`stats()`] method. /// /// [`stats()`]: struct.Connection.html#method.stats -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Stats { /// The number of QUIC packets received. pub recv: usize, @@ -5601,36 +7403,20 @@ pub struct Stats { /// The number of sent QUIC packets with retransmitted data. pub retrans: usize, - /// The estimated round-trip time of the connection. - pub rtt: time::Duration, - - /// The size of the connection's congestion window in bytes. - pub cwnd: usize, - /// The number of sent bytes. pub sent_bytes: u64, /// The number of received bytes. pub recv_bytes: u64, - /// The number of bytes lost. + /// The number of bytes sent lost. pub lost_bytes: u64, /// The number of stream bytes retransmitted. pub stream_retrans_bytes: u64, - /// The current PMTU for the connection. - pub pmtu: usize, - - /// The most recent data delivery rate estimate in bytes/s. - /// - /// Note that this value could be inaccurate if the application does not - /// respect pacing hints (see [`SendInfo.at`] and [Pacing] for more - /// details). - /// - /// [`SendInfo.at`]: struct.SendInfo.html#structfield.at - /// [Pacing]: index.html#pacing - pub delivery_rate: u64, + /// The number of known paths for the connection. + pub paths_count: usize, /// The maximum idle timeout. pub peer_max_idle_timeout: u64, @@ -5677,13 +7463,19 @@ impl std::fmt::Debug for Stats { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "recv={} sent={} lost={} retrans={} rtt={:?} cwnd={}", - self.recv, self.sent, self.lost, self.retrans, self.rtt, self.cwnd, + "recv={} sent={} lost={} retrans={}", + self.recv, self.sent, self.lost, self.retrans, + )?; + + write!( + f, + " sent_bytes={} recv_bytes={} lost_bytes={}", + self.sent_bytes, self.recv_bytes, self.lost_bytes, )?; write!(f, " peer_tps={{")?; - write!(f, " max_idle_timeout={},", self.peer_max_idle_timeout,)?; + write!(f, " max_idle_timeout={},", self.peer_max_idle_timeout)?; write!( f, @@ -5691,7 +7483,7 @@ impl std::fmt::Debug for Stats { self.peer_max_udp_payload_size, )?; - write!(f, " initial_max_data={},", self.peer_initial_max_data,)?; + write!(f, " initial_max_data={},", self.peer_initial_max_data)?; write!( f, @@ -5723,9 +7515,9 @@ impl std::fmt::Debug for Stats { self.peer_initial_max_streams_uni, )?; - write!(f, " ack_delay_exponent={},", self.peer_ack_delay_exponent,)?; + write!(f, " ack_delay_exponent={},", self.peer_ack_delay_exponent)?; - write!(f, " max_ack_delay={},", self.peer_max_ack_delay,)?; + write!(f, " max_ack_delay={},", self.peer_max_ack_delay)?; write!( f, @@ -5745,7 +7537,7 @@ impl std::fmt::Debug for Stats { self.peer_max_datagram_frame_size, )?; - write!(f, " }}") + write!(f, "}}") } } @@ -5753,7 +7545,7 @@ impl std::fmt::Debug for Stats { struct TransportParams { pub original_destination_connection_id: Option<ConnectionId<'static>>, pub max_idle_timeout: u64, - pub stateless_reset_token: Option<Vec<u8>>, + pub stateless_reset_token: Option<u128>, pub max_udp_payload_size: u64, pub initial_max_data: u64, pub initial_max_stream_data_bidi_local: u64, @@ -5798,15 +7590,19 @@ impl Default for TransportParams { impl TransportParams { fn decode(buf: &[u8], is_server: bool) -> Result<TransportParams> { let mut params = octets::Octets::with_slice(buf); + let mut seen_params = HashSet::new(); let mut tp = TransportParams::default(); while params.cap() > 0 { let id = params.get_varint()?; - let mut val = params.get_bytes_with_varint_length()?; + if seen_params.contains(&id) { + return Err(Error::InvalidTransportParam); + } + seen_params.insert(id); - // TODO: forbid duplicated param + let mut val = params.get_bytes_with_varint_length()?; match id { 0x0000 => { @@ -5827,7 +7623,12 @@ impl TransportParams { return Err(Error::InvalidTransportParam); } - tp.stateless_reset_token = Some(val.get_bytes(16)?.to_vec()); + tp.stateless_reset_token = Some(u128::from_be_bytes( + val.get_bytes(16)? + .to_vec() + .try_into() + .map_err(|_| Error::BufferTooShort)?, + )); }, 0x0003 => { @@ -5972,8 +7773,8 @@ impl TransportParams { if is_server { if let Some(ref token) = tp.stateless_reset_token { - TransportParams::encode_param(&mut b, 0x0002, token.len())?; - b.put_bytes(token)?; + TransportParams::encode_param(&mut b, 0x0002, 16)?; + b.put_bytes(&token.to_be_bytes())?; } } @@ -6108,21 +7909,16 @@ impl TransportParams { self.original_destination_connection_id.as_ref(), ); - let stateless_reset_token = Some(qlog::Token { - ty: Some(qlog::TokenType::StatelessReset), - length: None, - data: qlog::HexSlice::maybe_string( - self.stateless_reset_token.as_ref(), - ), - details: None, - }); + let stateless_reset_token = qlog::HexSlice::maybe_string( + self.stateless_reset_token.map(|s| s.to_be_bytes()).as_ref(), + ); EventData::TransportParametersSet( qlog::events::quic::TransportParametersSet { owner: Some(owner), resumption_allowed: None, early_data_enabled: None, - tls_cipher: Some(format!("{:?}", cipher)), + tls_cipher: Some(format!("{cipher:?}")), aead_tag_length: None, original_destination_connection_id, initial_source_connection_id: None, @@ -6166,11 +7962,11 @@ pub mod testing { } impl Pipe { - pub fn default() -> Result<Pipe> { + pub fn new() -> Result<Pipe> { let mut config = Config::new(crate::PROTOCOL_VERSION)?; config.load_cert_chain_from_pem_file("examples/cert.crt")?; config.load_priv_key_from_pem_file("examples/cert.key")?; - config.set_application_protos(b"\x06proto1\x06proto2")?; + config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); config.set_initial_max_stream_data_bidi_remote(15); @@ -6184,25 +7980,71 @@ pub mod testing { Pipe::with_config(&mut config) } + pub fn client_addr() -> SocketAddr { + "127.0.0.1:1234".parse().unwrap() + } + + pub fn server_addr() -> SocketAddr { + "127.0.0.1:4321".parse().unwrap() + } + pub fn with_config(config: &mut Config) -> Result<Pipe> { let mut client_scid = [0; 16]; rand::rand_bytes(&mut client_scid[..]); let client_scid = ConnectionId::from_ref(&client_scid); - let client_addr = "127.0.0.1:1234".parse().unwrap(); + let client_addr = Pipe::client_addr(); let mut server_scid = [0; 16]; rand::rand_bytes(&mut server_scid[..]); let server_scid = ConnectionId::from_ref(&server_scid); - let server_addr = "127.0.0.1:4321".parse().unwrap(); + let server_addr = Pipe::server_addr(); + + Ok(Pipe { + client: connect( + Some("quic.tech"), + &client_scid, + client_addr, + server_addr, + config, + )?, + server: accept( + &server_scid, + None, + server_addr, + client_addr, + config, + )?, + }) + } + + pub fn with_config_and_scid_lengths( + config: &mut Config, client_scid_len: usize, server_scid_len: usize, + ) -> Result<Pipe> { + let mut client_scid = vec![0; client_scid_len]; + rand::rand_bytes(&mut client_scid[..]); + let client_scid = ConnectionId::from_ref(&client_scid); + let client_addr = Pipe::client_addr(); + + let mut server_scid = vec![0; server_scid_len]; + rand::rand_bytes(&mut server_scid[..]); + let server_scid = ConnectionId::from_ref(&server_scid); + let server_addr = Pipe::server_addr(); Ok(Pipe { client: connect( Some("quic.tech"), &client_scid, client_addr, + server_addr, + config, + )?, + server: accept( + &server_scid, + None, + server_addr, + client_addr, config, )?, - server: accept(&server_scid, None, server_addr, config)?, }) } @@ -6210,17 +8052,17 @@ pub mod testing { let mut client_scid = [0; 16]; rand::rand_bytes(&mut client_scid[..]); let client_scid = ConnectionId::from_ref(&client_scid); - let client_addr = "127.0.0.1:1234".parse().unwrap(); + let client_addr = Pipe::client_addr(); let mut server_scid = [0; 16]; rand::rand_bytes(&mut server_scid[..]); let server_scid = ConnectionId::from_ref(&server_scid); - let server_addr = "127.0.0.1:4321".parse().unwrap(); + let server_addr = Pipe::server_addr(); let mut config = Config::new(crate::PROTOCOL_VERSION)?; config.load_cert_chain_from_pem_file("examples/cert.crt")?; config.load_priv_key_from_pem_file("examples/cert.key")?; - config.set_application_protos(b"\x06proto1\x06proto2")?; + config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); config.set_initial_max_stream_data_bidi_remote(15); @@ -6233,9 +8075,16 @@ pub mod testing { Some("quic.tech"), &client_scid, client_addr, + server_addr, client_config, )?, - server: accept(&server_scid, None, server_addr, &mut config)?, + server: accept( + &server_scid, + None, + server_addr, + client_addr, + &mut config, + )?, }) } @@ -6243,15 +8092,15 @@ pub mod testing { let mut client_scid = [0; 16]; rand::rand_bytes(&mut client_scid[..]); let client_scid = ConnectionId::from_ref(&client_scid); - let client_addr = "127.0.0.1:1234".parse().unwrap(); + let client_addr = Pipe::client_addr(); let mut server_scid = [0; 16]; rand::rand_bytes(&mut server_scid[..]); let server_scid = ConnectionId::from_ref(&server_scid); - let server_addr = "127.0.0.1:4321".parse().unwrap(); + let server_addr = Pipe::server_addr(); let mut config = Config::new(crate::PROTOCOL_VERSION)?; - config.set_application_protos(b"\x06proto1\x06proto2")?; + config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); config.set_initial_max_stream_data_bidi_remote(15); @@ -6264,9 +8113,16 @@ pub mod testing { Some("quic.tech"), &client_scid, client_addr, + server_addr, &mut config, )?, - server: accept(&server_scid, None, server_addr, server_config)?, + server: accept( + &server_scid, + None, + server_addr, + client_addr, + server_config, + )?, }) } @@ -6308,16 +8164,20 @@ pub mod testing { } pub fn client_recv(&mut self, buf: &mut [u8]) -> Result<usize> { + let server_path = &self.server.paths.get_active().unwrap(); let info = RecvInfo { - from: self.client.peer_addr, + to: server_path.peer_addr(), + from: server_path.local_addr(), }; self.client.recv(buf, info) } pub fn server_recv(&mut self, buf: &mut [u8]) -> Result<usize> { + let client_path = &self.client.paths.get_active().unwrap(); let info = RecvInfo { - from: self.server.peer_addr, + to: client_path.peer_addr(), + from: client_path.local_addr(), }; self.server.recv(buf, info) @@ -6330,13 +8190,47 @@ pub mod testing { let written = encode_pkt(&mut self.client, pkt_type, frames, buf)?; recv_send(&mut self.server, buf, written) } + + pub fn client_update_key(&mut self) -> Result<()> { + let space = + &mut self.client.pkt_num_spaces[packet::Epoch::Application]; + + let open_next = space + .crypto_open + .as_ref() + .unwrap() + .derive_next_packet_key() + .unwrap(); + + let seal_next = space + .crypto_seal + .as_ref() + .unwrap() + .derive_next_packet_key()?; + + let open_prev = space.crypto_open.replace(open_next); + space.crypto_seal.replace(seal_next); + + space.key_update = Some(packet::KeyUpdate { + crypto_open: open_prev.unwrap(), + pn_on_update: space.next_pkt_num, + update_acked: true, + timer: time::Instant::now(), + }); + + self.client.key_phase = !self.client.key_phase; + + Ok(()) + } } pub fn recv_send( conn: &mut Connection, buf: &mut [u8], len: usize, ) -> Result<usize> { + let active_path = conn.paths.get_active()?; let info = RecvInfo { - from: conn.peer_addr, + to: active_path.local_addr(), + from: active_path.peer_addr(), }; conn.recv(&mut buf[..len], info)?; @@ -6355,11 +8249,12 @@ pub mod testing { } pub fn process_flight( - conn: &mut Connection, flight: Vec<Vec<u8>>, + conn: &mut Connection, flight: Vec<(Vec<u8>, SendInfo)>, ) -> Result<()> { - for mut pkt in flight { + for (mut pkt, si) in flight { let info = RecvInfo { - from: conn.peer_addr, + to: si.to, + from: si.from, }; conn.recv(&mut pkt, info)?; @@ -6368,21 +8263,26 @@ pub mod testing { Ok(()) } - pub fn emit_flight(conn: &mut Connection) -> Result<Vec<Vec<u8>>> { + pub fn emit_flight_with_max_buffer( + conn: &mut Connection, out_size: usize, + ) -> Result<Vec<(Vec<u8>, SendInfo)>> { let mut flight = Vec::new(); loop { - let mut out = vec![0u8; 65535]; + let mut out = vec![0u8; out_size]; - match conn.send(&mut out) { - Ok((written, _)) => out.truncate(written), + let info = match conn.send(&mut out) { + Ok((written, info)) => { + out.truncate(written); + info + }, Err(Error::Done) => break, Err(e) => return Err(e), }; - flight.push(out); + flight.push((out, info)); } if flight.is_empty() { @@ -6392,6 +8292,12 @@ pub mod testing { Ok(flight) } + pub fn emit_flight( + conn: &mut Connection, + ) -> Result<Vec<(Vec<u8>, SendInfo)>> { + emit_flight_with_max_buffer(conn, 65535) + } + pub fn encode_pkt( conn: &mut Connection, pkt_type: packet::Type, frames: &[frame::Frame], buf: &mut [u8], @@ -6405,16 +8311,30 @@ pub mod testing { let pn = space.next_pkt_num; let pn_len = 4; + let send_path = conn.paths.get_active()?; + let active_dcid_seq = send_path + .active_dcid_seq + .as_ref() + .ok_or(Error::InvalidState)?; + let active_scid_seq = send_path + .active_scid_seq + .as_ref() + .ok_or(Error::InvalidState)?; + let hdr = Header { ty: pkt_type, version: conn.version, - dcid: ConnectionId::from_ref(&conn.dcid), - scid: ConnectionId::from_ref(&conn.scid), + dcid: ConnectionId::from_ref( + conn.ids.get_dcid(*active_dcid_seq)?.cid.as_ref(), + ), + scid: ConnectionId::from_ref( + conn.ids.get_scid(*active_scid_seq)?.cid.as_ref(), + ), pkt_num: 0, pkt_num_len: pn_len, token: conn.token.clone(), versions: None, - key_phase: false, + key_phase: conn.key_phase, }; hdr.to_bytes(&mut b)?; @@ -6461,7 +8381,7 @@ pub mod testing { ) -> Result<Vec<frame::Frame>> { let mut b = octets::OctetsMut::with_slice(&mut buf[..len]); - let mut hdr = Header::from_bytes(&mut b, conn.scid.len()).unwrap(); + let mut hdr = Header::from_bytes(&mut b, conn.source_id().len()).unwrap(); let epoch = hdr.ty.to_epoch()?; @@ -6490,6 +8410,20 @@ pub mod testing { Ok(frames) } + + pub fn create_cid_and_reset_token( + cid_len: usize, + ) -> (ConnectionId<'static>, u128) { + let mut cid = vec![0; cid_len]; + rand::rand_bytes(&mut cid[..]); + let cid = ConnectionId::from_ref(&cid).into_owned(); + + let mut reset_token = [0; 16]; + rand::rand_bytes(&mut reset_token); + let reset_token = u128::from_be_bytes(reset_token); + + (cid, reset_token) + } } #[cfg(test)] @@ -6502,7 +8436,7 @@ mod tests { let tp = TransportParams { original_destination_connection_id: None, max_idle_timeout: 30, - stateless_reset_token: Some(vec![0xba; 16]), + stateless_reset_token: Some(u128::from_be_bytes([0xba; 16])), max_udp_payload_size: 23_421, initial_max_data: 424_645_563, initial_max_stream_data_bidi_local: 154_323_123, @@ -6524,7 +8458,7 @@ mod tests { TransportParams::encode(&tp, true, &mut raw_params).unwrap(); assert_eq!(raw_params.len(), 94); - let new_tp = TransportParams::decode(&raw_params, false).unwrap(); + let new_tp = TransportParams::decode(raw_params, false).unwrap(); assert_eq!(new_tp, tp); @@ -6554,16 +8488,51 @@ mod tests { TransportParams::encode(&tp, false, &mut raw_params).unwrap(); assert_eq!(raw_params.len(), 69); - let new_tp = TransportParams::decode(&raw_params, true).unwrap(); + let new_tp = TransportParams::decode(raw_params, true).unwrap(); assert_eq!(new_tp, tp); } #[test] + fn transport_params_forbid_duplicates() { + // Given an encoded param. + let initial_source_connection_id = b"id"; + let initial_source_connection_id_raw = [ + 15, + initial_source_connection_id.len() as u8, + initial_source_connection_id[0], + initial_source_connection_id[1], + ]; + + // No error when decoding the param. + let tp = TransportParams::decode( + initial_source_connection_id_raw.as_slice(), + true, + ) + .unwrap(); + + assert_eq!( + tp.initial_source_connection_id, + Some(initial_source_connection_id.to_vec().into()) + ); + + // Duplicate the param. + let mut raw_params = Vec::new(); + raw_params.append(&mut initial_source_connection_id_raw.to_vec()); + raw_params.append(&mut initial_source_connection_id_raw.to_vec()); + + // Decoding fails. + assert_eq!( + TransportParams::decode(raw_params.as_slice(), true), + Err(Error::InvalidTransportParam) + ); + } + + #[test] fn unknown_version() { let mut config = Config::new(0xbabababa).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.verify_peer(false); @@ -6591,7 +8560,7 @@ mod tests { let mut config = Config::new(0xbabababa).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.verify_peer(false); @@ -6618,7 +8587,7 @@ mod tests { .load_verify_locations_from_file("examples/rootca.crt") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_client_config(&mut config).unwrap(); @@ -6629,7 +8598,7 @@ mod tests { fn missing_initial_source_connection_id() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Reset initial_source_connection_id. pipe.client @@ -6651,7 +8620,7 @@ mod tests { fn invalid_initial_source_connection_id() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Scramble initial_source_connection_id. pipe.client @@ -6671,7 +8640,7 @@ mod tests { #[test] fn handshake() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -6684,7 +8653,7 @@ mod tests { #[test] fn handshake_done() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Disable session tickets on the server (SSL_OP_NO_TICKET) to avoid // triggering 1-RTT packet send with a CRYPTO frame. @@ -6697,7 +8666,7 @@ mod tests { #[test] fn handshake_confirmation() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends initial flight. let flight = testing::emit_flight(&mut pipe.client).unwrap(); @@ -6725,14 +8694,14 @@ mod tests { testing::process_flight(&mut pipe.server, flight).unwrap(); - // Server completes handshake and sends HANDSHAKE_DONE. + // Server completes and confirms handshake, and sends HANDSHAKE_DONE. let flight = testing::emit_flight(&mut pipe.server).unwrap(); assert!(pipe.client.is_established()); assert!(!pipe.client.handshake_confirmed); assert!(pipe.server.is_established()); - assert!(!pipe.server.handshake_confirmed); + assert!(pipe.server.handshake_confirmed); testing::process_flight(&mut pipe.client, flight).unwrap(); @@ -6743,11 +8712,10 @@ mod tests { assert!(pipe.client.handshake_confirmed); assert!(pipe.server.is_established()); - assert!(!pipe.server.handshake_confirmed); + assert!(pipe.server.handshake_confirmed); testing::process_flight(&mut pipe.server, flight).unwrap(); - // Server handshake is confirmed. assert!(pipe.client.is_established()); assert!(pipe.client.handshake_confirmed); @@ -6767,7 +8735,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -6797,7 +8765,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -6807,7 +8775,7 @@ mod tests { let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); - assert_eq!(pipe.client.set_session(&session), Ok(())); + assert_eq!(pipe.client.set_session(session), Ok(())); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.is_established(), true); @@ -6823,7 +8791,7 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto3\x06proto4") + .set_application_protos(&[b"proto3\x06proto4"]) .unwrap(); config.verify_peer(false); @@ -6853,7 +8821,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -6871,7 +8839,7 @@ mod tests { // Configure session on new connection. let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); - assert_eq!(pipe.client.set_session(&session), Ok(())); + assert_eq!(pipe.client.set_session(session), Ok(())); // Client sends initial flight. let (len, _) = pipe.client.send(&mut buf).unwrap(); @@ -6914,7 +8882,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -6932,11 +8900,11 @@ mod tests { // Configure session on new connection. let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); - assert_eq!(pipe.client.set_session(&session), Ok(())); + assert_eq!(pipe.client.set_session(session), Ok(())); // Client sends initial flight. let (len, _) = pipe.client.send(&mut buf).unwrap(); - let mut initial = (&buf[..len]).to_vec(); + let mut initial = buf[..len].to_vec(); // Client sends 0-RTT packet. let pkt_type = packet::Type::ZeroRTT; @@ -6949,7 +8917,7 @@ mod tests { let len = testing::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf) .unwrap(); - let mut zrtt = (&buf[..len]).to_vec(); + let mut zrtt = buf[..len].to_vec(); // 0-RTT packet is received before the Initial one. assert_eq!(pipe.server_recv(&mut zrtt), Ok(zrtt.len())); @@ -6985,7 +8953,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -7003,7 +8971,7 @@ mod tests { // Configure session on new connection. let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); - assert_eq!(pipe.client.set_session(&session), Ok(())); + assert_eq!(pipe.client.set_session(session), Ok(())); // Client sends initial flight. pipe.client.send(&mut buf).unwrap(); @@ -7021,7 +8989,7 @@ mod tests { .unwrap(); // Simulate a truncated packet by sending one byte less. - let mut zrtt = (&buf[..len - 1]).to_vec(); + let mut zrtt = buf[..len - 1].to_vec(); // 0-RTT packet is received before the Initial one. assert_eq!(pipe.server_recv(&mut zrtt), Err(Error::InvalidPacket)); @@ -7037,7 +9005,7 @@ mod tests { fn handshake_downgrade_v1() { let mut config = Config::new(PROTOCOL_VERSION_DRAFT29).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.verify_peer(false); @@ -7058,24 +9026,24 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); let flight = testing::emit_flight(&mut pipe.client).unwrap(); - let client_sent = flight.iter().fold(0, |out, p| out + p.len()); + let client_sent = flight.iter().fold(0, |out, p| out + p.0.len()); testing::process_flight(&mut pipe.server, flight).unwrap(); let flight = testing::emit_flight(&mut pipe.server).unwrap(); - let server_sent = flight.iter().fold(0, |out, p| out + p.len()); + let server_sent = flight.iter().fold(0, |out, p| out + p.0.len()); assert_eq!(server_sent, client_sent * MAX_AMPLIFICATION_FACTOR); } #[test] fn stream() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.stream_send(4, b"hello, world", true), Ok(12)); @@ -7106,7 +9074,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -7124,11 +9092,11 @@ mod tests { // Configure session on new connection. let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); - assert_eq!(pipe.client.set_session(&session), Ok(())); + assert_eq!(pipe.client.set_session(session), Ok(())); // Client sends initial flight. let (len, _) = pipe.client.send(&mut buf).unwrap(); - let mut initial = (&buf[..len]).to_vec(); + let mut initial = buf[..len].to_vec(); assert_eq!(pipe.client.is_in_early_data(), true); @@ -7136,7 +9104,7 @@ mod tests { assert_eq!(pipe.client.stream_send(4, b"hello, world", true), Ok(12)); let (len, _) = pipe.client.send(&mut buf).unwrap(); - let mut zrtt = (&buf[..len]).to_vec(); + let mut zrtt = buf[..len].to_vec(); // Server receives packets. assert_eq!(pipe.server_recv(&mut initial), Ok(initial.len())); @@ -7164,7 +9132,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(2_u64.pow(32) + 5); config.set_initial_max_stream_data_bidi_local(15); @@ -7190,7 +9158,7 @@ mod tests { fn empty_stream_frame() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::Stream { @@ -7232,12 +9200,95 @@ mod tests { } #[test] + fn update_key_request() { + let mut b = [0; 15]; + + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + assert_eq!(pipe.advance(), Ok(())); + + // Client sends message with key update request. + assert_eq!(pipe.client_update_key(), Ok(())); + assert_eq!(pipe.client.stream_send(4, b"hello", false), Ok(5)); + assert_eq!(pipe.advance(), Ok(())); + + // Ensure server updates key and it correctly decrypts the message. + let mut r = pipe.server.readable(); + assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), None); + assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, false))); + assert_eq!(&b[..5], b"hello"); + + // Ensure ACK for key update. + assert!( + pipe.server.pkt_num_spaces[packet::Epoch::Application] + .key_update + .as_ref() + .unwrap() + .update_acked + ); + + // Server sends message with the new key. + assert_eq!(pipe.server.stream_send(4, b"world", true), Ok(5)); + assert_eq!(pipe.advance(), Ok(())); + + // Ensure update key is completed and client can decrypt packet. + let mut r = pipe.client.readable(); + assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), None); + assert_eq!(pipe.client.stream_recv(4, &mut b), Ok((5, true))); + assert_eq!(&b[..5], b"world"); + } + + #[test] + fn update_key_request_twice_error() { + let mut buf = [0; 65535]; + + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + assert_eq!(pipe.advance(), Ok(())); + + let frames = [frame::Frame::Stream { + stream_id: 4, + data: stream::RangeBuf::from(b"hello", 0, false), + }]; + + // Client sends stream frame with key update request. + assert_eq!(pipe.client_update_key(), Ok(())); + let written = testing::encode_pkt( + &mut pipe.client, + packet::Type::Short, + &frames, + &mut buf, + ) + .unwrap(); + + // Server correctly decode with new key. + assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written)); + + // Client sends stream frame with another key update request before server + // ACK. + assert_eq!(pipe.client_update_key(), Ok(())); + let written = testing::encode_pkt( + &mut pipe.client, + packet::Type::Short, + &frames, + &mut buf, + ) + .unwrap(); + + // Check server correctly closes the connection with a key update error + // for the peer. + assert_eq!(pipe.server_recv(&mut buf[..written]), Err(Error::KeyUpdate)); + } + + #[test] /// Tests that receiving a MAX_STREAM_DATA frame for a receive-only /// unidirectional stream is forbidden. fn max_stream_data_receive_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client opens unidirectional stream. @@ -7261,7 +9312,7 @@ mod tests { fn empty_payload() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Send a packet with no frames. @@ -7276,7 +9327,7 @@ mod tests { fn min_payload() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Send a non-ack-eliciting packet. let frames = [frame::Frame::Padding { len: 4 }]; @@ -7287,13 +9338,30 @@ mod tests { .unwrap(); assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written)); - assert_eq!(pipe.server.max_send_bytes, 195); + let initial_path = pipe + .server + .paths + .get_active() + .expect("initial path not found"); + + assert_eq!(initial_path.max_send_bytes, 195); // Force server to send a single PING frame. - pipe.server.recovery.loss_probes[packet::EPOCH_INITIAL] = 1; + pipe.server + .paths + .get_active_mut() + .expect("no active path") + .recovery + .loss_probes[packet::Epoch::Initial] = 1; + + let initial_path = pipe + .server + .paths + .get_active_mut() + .expect("initial path not found"); // Artificially limit the amount of bytes the server can send. - pipe.server.max_send_bytes = 60; + initial_path.max_send_bytes = 60; assert_eq!(pipe.server.send(&mut buf), Err(Error::Done)); } @@ -7302,20 +9370,20 @@ mod tests { fn flow_control_limit() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ frame::Frame::Stream { - stream_id: 4, + stream_id: 0, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, frame::Frame::Stream { - stream_id: 8, + stream_id: 4, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, frame::Frame::Stream { - stream_id: 12, + stream_id: 8, data: stream::RangeBuf::from(b"a", 0, false), }, ]; @@ -7331,22 +9399,22 @@ mod tests { fn flow_control_limit_dup() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ // One byte less than stream limit. frame::Frame::Stream { - stream_id: 4, + stream_id: 0, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaa", 0, false), }, // Same stream, but one byte more. frame::Frame::Stream { - stream_id: 4, + stream_id: 0, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, frame::Frame::Stream { - stream_id: 12, + stream_id: 8, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, ]; @@ -7359,16 +9427,16 @@ mod tests { fn flow_control_update() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ frame::Frame::Stream { - stream_id: 4, + stream_id: 0, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, frame::Frame::Stream { - stream_id: 8, + stream_id: 4, data: stream::RangeBuf::from(b"a", 0, false), }, ]; @@ -7377,11 +9445,11 @@ mod tests { assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok()); + pipe.server.stream_recv(0, &mut buf).unwrap(); pipe.server.stream_recv(4, &mut buf).unwrap(); - pipe.server.stream_recv(8, &mut buf).unwrap(); let frames = [frame::Frame::Stream { - stream_id: 8, + stream_id: 4, data: stream::RangeBuf::from(b"a", 1, false), }]; @@ -7401,7 +9469,7 @@ mod tests { assert_eq!( iter.next(), Some(&frame::Frame::MaxStreamData { - stream_id: 4, + stream_id: 0, max: 30 }) ); @@ -7412,7 +9480,7 @@ mod tests { /// Tests that flow control is properly updated even when a stream is shut /// down. fn flow_control_drain() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client opens a stream and sends some data. @@ -7446,7 +9514,7 @@ mod tests { fn stream_flow_control_limit_bidi() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::Stream { @@ -7465,7 +9533,7 @@ mod tests { fn stream_flow_control_limit_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::Stream { @@ -7484,7 +9552,7 @@ mod tests { fn stream_flow_control_update() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::Stream { @@ -7529,7 +9597,7 @@ mod tests { fn stream_left_bidi() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(3, pipe.client.peer_streams_left_bidi()); @@ -7555,7 +9623,7 @@ mod tests { fn stream_left_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(3, pipe.client.peer_streams_left_uni()); @@ -7581,7 +9649,7 @@ mod tests { fn stream_limit_bidi() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ @@ -7626,7 +9694,7 @@ mod tests { fn stream_limit_max_bidi() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::MaxStreamsBidi { max: MAX_STREAM_ID }]; @@ -7649,7 +9717,7 @@ mod tests { fn stream_limit_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ @@ -7694,7 +9762,7 @@ mod tests { fn stream_limit_max_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::MaxStreamsUni { max: MAX_STREAM_ID }]; @@ -7717,7 +9785,7 @@ mod tests { fn streams_blocked_max_bidi() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::StreamsBlockedBidi { @@ -7742,7 +9810,7 @@ mod tests { fn streams_blocked_max_uni() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::StreamsBlockedUni { @@ -7767,7 +9835,7 @@ mod tests { fn stream_data_overlap() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ @@ -7797,7 +9865,7 @@ mod tests { fn stream_data_overlap_with_reordering() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ @@ -7830,37 +9898,37 @@ mod tests { let mut b = [0; 15]; let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data. - assert_eq!(pipe.client.stream_send(4, b"hello", false), Ok(5)); + assert_eq!(pipe.client.stream_send(0, b"hello", false), Ok(5)); assert_eq!(pipe.advance(), Ok(())); // Server gets data and sends data back, closing stream. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, false))); - assert!(!pipe.server.stream_finished(4)); + assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((5, false))); + assert!(!pipe.server.stream_finished(0)); let mut r = pipe.server.readable(); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_send(4, b"", true), Ok(0)); + assert_eq!(pipe.server.stream_send(0, b"", true), Ok(0)); assert_eq!(pipe.advance(), Ok(())); let mut r = pipe.client.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.client.stream_recv(4, &mut b), Ok((0, true))); - assert!(pipe.client.stream_finished(4)); + assert_eq!(pipe.client.stream_recv(0, &mut b), Ok((0, true))); + assert!(pipe.client.stream_finished(0)); // Client sends RESET_STREAM, closing stream. let frames = [frame::Frame::ResetStream { - stream_id: 4, + stream_id: 0, error_code: 42, final_size: 5, }]; @@ -7870,18 +9938,19 @@ mod tests { // Server is notified of stream readability, due to reset. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); assert_eq!( - pipe.server.stream_recv(4, &mut b), + pipe.server.stream_recv(0, &mut b), Err(Error::StreamReset(42)) ); - assert!(pipe.server.stream_finished(4)); + assert!(pipe.server.stream_finished(0)); // Sending RESET_STREAM again shouldn't make stream readable again. - assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39)); + pipe.send_pkt_to_server(pkt_type, &frames, &mut buf) + .unwrap(); let mut r = pipe.server.readable(); assert_eq!(r.next(), None); @@ -7894,37 +9963,37 @@ mod tests { let mut b = [0; 15]; let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data. - assert_eq!(pipe.client.stream_send(4, b"h", false), Ok(1)); + assert_eq!(pipe.client.stream_send(0, b"h", false), Ok(1)); assert_eq!(pipe.advance(), Ok(())); // Server gets data and sends data back, closing stream. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((1, false))); - assert!(!pipe.server.stream_finished(4)); + assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((1, false))); + assert!(!pipe.server.stream_finished(0)); let mut r = pipe.server.readable(); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_send(4, b"", true), Ok(0)); + assert_eq!(pipe.server.stream_send(0, b"", true), Ok(0)); assert_eq!(pipe.advance(), Ok(())); let mut r = pipe.client.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.client.stream_recv(4, &mut b), Ok((0, true))); - assert!(pipe.client.stream_finished(4)); + assert_eq!(pipe.client.stream_recv(0, &mut b), Ok((0, true))); + assert!(pipe.client.stream_finished(0)); // Client sends RESET_STREAM, closing stream. let frames = [frame::Frame::ResetStream { - stream_id: 4, + stream_id: 0, error_code: 42, final_size: 5, }]; @@ -7934,15 +10003,15 @@ mod tests { // Server is notified of stream readability, due to reset. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); assert_eq!( - pipe.server.stream_recv(4, &mut b), + pipe.server.stream_recv(0, &mut b), Err(Error::StreamReset(42)) ); - assert!(pipe.server.stream_finished(4)); + assert!(pipe.server.stream_finished(0)); // Sending RESET_STREAM again shouldn't make stream readable again. assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39)); @@ -7957,25 +10026,25 @@ mod tests { fn reset_stream_flow_control() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ frame::Frame::Stream { - stream_id: 4, + stream_id: 0, data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false), }, frame::Frame::Stream { - stream_id: 8, + stream_id: 4, data: stream::RangeBuf::from(b"a", 0, false), }, frame::Frame::ResetStream { - stream_id: 8, + stream_id: 4, error_code: 0, final_size: 15, }, frame::Frame::Stream { - stream_id: 12, + stream_id: 8, data: stream::RangeBuf::from(b"a", 0, false), }, ]; @@ -7993,7 +10062,7 @@ mod tests { fn reset_stream_flow_control_stream() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [ @@ -8019,7 +10088,7 @@ mod tests { fn path_challenge() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::PathChallenge { data: [0xba; 8] }]; @@ -8051,7 +10120,7 @@ mod tests { fn early_1rtt_packet() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends initial flight let flight = testing::emit_flight(&mut pipe.client).unwrap(); @@ -8066,7 +10135,7 @@ mod tests { // Emulate handshake packet delay by not making server process client // packet. - let delayed = flight.clone(); + let delayed = flight; testing::emit_flight(&mut pipe.server).ok(); @@ -8104,7 +10173,7 @@ mod tests { // Note that `largest_rx_pkt_num` is initialized to 0, so we need to // send another 1-RTT packet to make this check meaningful. assert_eq!( - pipe.server.pkt_num_spaces[packet::EPOCH_APPLICATION] + pipe.server.pkt_num_spaces[packet::Epoch::Application] .largest_rx_pkt_num, 0 ); @@ -8115,7 +10184,7 @@ mod tests { assert!(pipe.server.is_established()); assert_eq!( - pipe.server.pkt_num_spaces[packet::EPOCH_APPLICATION] + pipe.server.pkt_num_spaces[packet::Epoch::Application] .largest_rx_pkt_num, 0 ); @@ -8127,31 +10196,31 @@ mod tests { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data, and closes stream. - assert_eq!(pipe.client.stream_send(4, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(0, b"hello", true), Ok(5)); assert_eq!(pipe.advance(), Ok(())); // Server gets data. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, true))); - assert!(pipe.server.stream_finished(4)); + assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((5, true))); + assert!(pipe.server.stream_finished(0)); let mut r = pipe.server.readable(); assert_eq!(r.next(), None); // Server sends data, until blocked. let mut r = pipe.server.writable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); loop { - if pipe.server.stream_send(4, b"world", false) == Err(Error::Done) { + if pipe.server.stream_send(0, b"world", false) == Err(Error::Done) { break; } @@ -8163,7 +10232,7 @@ mod tests { // Client sends STOP_SENDING. let frames = [frame::Frame::StopSending { - stream_id: 4, + stream_id: 0, error_code: 42, }]; @@ -8184,7 +10253,7 @@ mod tests { assert_eq!( iter.next(), Some(&frame::Frame::ResetStream { - stream_id: 4, + stream_id: 0, error_code: 42, final_size: 15, }) @@ -8192,11 +10261,11 @@ mod tests { // Stream is writable, but writing returns an error. let mut r = pipe.server.writable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); assert_eq!( - pipe.server.stream_send(4, b"world", true), + pipe.server.stream_send(0, b"world", true), Err(Error::StreamStopped(42)), ); @@ -8219,7 +10288,7 @@ mod tests { // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again. let frames = [frame::Frame::StopSending { - stream_id: 4, + stream_id: 0, error_code: 42, }]; @@ -8232,7 +10301,7 @@ mod tests { assert_eq!(frames.len(), 1); - match frames.iter().next() { + match frames.first() { Some(frame::Frame::ACK { .. }) => (), f => panic!("expected ACK frame, got {:?}", f), @@ -8248,7 +10317,7 @@ mod tests { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data, and closes stream. @@ -8323,7 +10392,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(15); config.set_initial_max_stream_data_bidi_local(30); @@ -8358,10 +10427,6 @@ mod tests { pipe.server.stream_send(4, b"hello", false), Err(Error::Done) ); - assert_eq!( - pipe.server.stream_send(8, b"hello", false), - Err(Error::Done) - ); // Client sends STOP_SENDING. let frames = [frame::Frame::StopSending { @@ -8390,7 +10455,7 @@ mod tests { fn stream_shutdown_read() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data. @@ -8466,7 +10531,7 @@ mod tests { fn stream_shutdown_read_after_fin() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data. @@ -8514,10 +10579,83 @@ mod tests { } #[test] + fn stream_shutdown_read_update_max_data() { + let mut buf = [0; 65535]; + + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_bidi_local(10000); + config.set_initial_max_stream_data_bidi_remote(10000); + config.set_initial_max_streams_bidi(10); + config.verify_peer(false); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + assert_eq!(pipe.client.stream_send(0, b"a", false), Ok(1)); + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.server.stream_recv(0, &mut buf), Ok((1, false))); + assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Read, 123), Ok(())); + + assert_eq!(pipe.server.rx_data, 1); + assert_eq!(pipe.client.tx_data, 1); + assert_eq!(pipe.client.max_tx_data, 30); + + assert_eq!( + pipe.client + .stream_send(0, &buf[..pipe.client.tx_cap], false), + Ok(29) + ); + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.server.stream_readable(0), false); // nothing can be consumed + + // The client has increased its tx_data, and server has received it, so + // it increases flow control accordingly. + assert_eq!(pipe.client.tx_data, 30); + assert_eq!(pipe.server.rx_data, 30); + assert_eq!(pipe.client.tx_cap, 45); + } + + #[test] + fn stream_shutdown_uni() { + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Exchange some data on uni streams. + assert_eq!(pipe.client.stream_send(2, b"hello, world", false), Ok(10)); + assert_eq!(pipe.server.stream_send(3, b"hello, world", false), Ok(10)); + assert_eq!(pipe.advance(), Ok(())); + + // Test local and remote shutdown. + assert_eq!(pipe.client.stream_shutdown(2, Shutdown::Write, 42), Ok(())); + assert_eq!( + pipe.client.stream_shutdown(2, Shutdown::Read, 42), + Err(Error::InvalidStreamState(2)) + ); + + assert_eq!( + pipe.client.stream_shutdown(3, Shutdown::Write, 42), + Err(Error::InvalidStreamState(3)) + ); + assert_eq!(pipe.client.stream_shutdown(3, Shutdown::Read, 42), Ok(())); + } + + #[test] fn stream_shutdown_write() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends some data. @@ -8614,7 +10752,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(15); config.set_initial_max_stream_data_bidi_local(30); @@ -8649,10 +10787,6 @@ mod tests { pipe.server.stream_send(4, b"hello", false), Err(Error::Done) ); - assert_eq!( - pipe.server.stream_send(8, b"hello", false), - Err(Error::Done) - ); // Client shouldn't update flow control. assert_eq!(pipe.client.should_update_max_data(), false); @@ -8680,7 +10814,7 @@ mod tests { fn stream_round_robin() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.stream_send(8, b"aaaaa", false), Ok(5)); @@ -8711,7 +10845,7 @@ mod tests { testing::decode_pkt(&mut pipe.server, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 0, data: stream::RangeBuf::from(b"aaaaa", 0, false), @@ -8724,7 +10858,7 @@ mod tests { testing::decode_pkt(&mut pipe.server, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 4, data: stream::RangeBuf::from(b"aaaaa", 0, false), @@ -8735,14 +10869,14 @@ mod tests { #[test] /// Tests the readable iterator. fn stream_readable() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // No readable streams. let mut r = pipe.client.readable(); assert_eq!(r.next(), None); - assert_eq!(pipe.client.stream_send(4, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(0, b"aaaaa", false), Ok(5)); let mut r = pipe.client.readable(); assert_eq!(r.next(), None); @@ -8754,22 +10888,22 @@ mod tests { // Server received stream. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); assert_eq!( - pipe.server.stream_send(4, b"aaaaaaaaaaaaaaa", false), + pipe.server.stream_send(0, b"aaaaaaaaaaaaaaa", false), Ok(15) ); assert_eq!(pipe.advance(), Ok(())); let mut r = pipe.client.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); // Client drains stream. let mut b = [0; 15]; - pipe.client.stream_recv(4, &mut b).unwrap(); + pipe.client.stream_recv(0, &mut b).unwrap(); assert_eq!(pipe.advance(), Ok(())); let mut r = pipe.client.readable(); @@ -8777,19 +10911,19 @@ mod tests { // Server shuts down stream. let mut r = pipe.server.readable(); - assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(0)); assert_eq!(r.next(), None); - assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 0), Ok(())); + assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Read, 0), Ok(())); let mut r = pipe.server.readable(); assert_eq!(r.next(), None); // Client creates multiple streams. - assert_eq!(pipe.client.stream_send(8, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(4, b"aaaaa", false), Ok(5)); assert_eq!(pipe.advance(), Ok(())); - assert_eq!(pipe.client.stream_send(12, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(8, b"aaaaa", false), Ok(5)); assert_eq!(pipe.advance(), Ok(())); let mut r = pipe.server.readable(); @@ -8805,29 +10939,29 @@ mod tests { #[test] /// Tests the writable iterator. fn stream_writable() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // No writable streams. let mut w = pipe.client.writable(); assert_eq!(w.next(), None); - assert_eq!(pipe.client.stream_send(4, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(0, b"aaaaa", false), Ok(5)); // Client created stream. let mut w = pipe.client.writable(); - assert_eq!(w.next(), Some(4)); + assert_eq!(w.next(), Some(0)); assert_eq!(w.next(), None); assert_eq!(pipe.advance(), Ok(())); // Server created stream. let mut w = pipe.server.writable(); - assert_eq!(w.next(), Some(4)); + assert_eq!(w.next(), Some(0)); assert_eq!(w.next(), None); assert_eq!( - pipe.server.stream_send(4, b"aaaaaaaaaaaaaaa", false), + pipe.server.stream_send(0, b"aaaaaaaaaaaaaaa", false), Ok(15) ); @@ -8839,25 +10973,25 @@ mod tests { // Client drains stream. let mut b = [0; 15]; - pipe.client.stream_recv(4, &mut b).unwrap(); + pipe.client.stream_recv(0, &mut b).unwrap(); assert_eq!(pipe.advance(), Ok(())); // Server stream is writable again. let mut w = pipe.server.writable(); - assert_eq!(w.next(), Some(4)); + assert_eq!(w.next(), Some(0)); assert_eq!(w.next(), None); // Server shuts down stream. - assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Write, 0), Ok(())); + assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Write, 0), Ok(())); let mut w = pipe.server.writable(); assert_eq!(w.next(), None); // Client creates multiple streams. - assert_eq!(pipe.client.stream_send(8, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(4, b"aaaaa", false), Ok(5)); assert_eq!(pipe.advance(), Ok(())); - assert_eq!(pipe.client.stream_send(12, b"aaaaa", false), Ok(5)); + assert_eq!(pipe.client.stream_send(8, b"aaaaa", false), Ok(5)); assert_eq!(pipe.advance(), Ok(())); let mut w = pipe.server.writable(); @@ -8870,18 +11004,71 @@ mod tests { assert_eq!(w.len(), 0); // Server finishes stream. - assert_eq!(pipe.server.stream_send(12, b"aaaaa", true), Ok(5)); + assert_eq!(pipe.server.stream_send(8, b"aaaaa", true), Ok(5)); let mut w = pipe.server.writable(); - assert_eq!(w.next(), Some(8)); + assert_eq!(w.next(), Some(4)); assert_eq!(w.next(), None); } #[test] + fn stream_writable_blocked() { + let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config.set_application_protos(&[b"h3"]).unwrap(); + config.set_initial_max_data(70); + config.set_initial_max_stream_data_bidi_local(150000); + config.set_initial_max_stream_data_bidi_remote(150000); + config.set_initial_max_stream_data_uni(150000); + config.set_initial_max_streams_bidi(100); + config.set_initial_max_streams_uni(5); + config.verify_peer(false); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Client creates stream and sends some data. + let send_buf = [0; 35]; + assert_eq!(pipe.client.stream_send(0, &send_buf, false), Ok(35)); + + // Stream is still writable as it still has capacity. + assert_eq!(pipe.client.stream_writable_next(), Some(0)); + assert_eq!(pipe.client.stream_writable_next(), None); + + // Client fills stream, which becomes unwritable due to connection + // capacity. + let send_buf = [0; 36]; + assert_eq!(pipe.client.stream_send(0, &send_buf, false), Ok(35)); + + assert_eq!(pipe.client.stream_writable_next(), None); + + assert_eq!(pipe.client.tx_cap, 0); + + assert_eq!(pipe.advance(), Ok(())); + + let mut b = [0; 70]; + pipe.server.stream_recv(0, &mut b).unwrap(); + + assert_eq!(pipe.advance(), Ok(())); + + // The connection capacity has increased and the stream is now writable + // again. + assert_ne!(pipe.client.tx_cap, 0); + + assert_eq!(pipe.client.stream_writable_next(), Some(0)); + assert_eq!(pipe.client.stream_writable_next(), None); + } + + #[test] /// Tests that we don't exceed the per-connection flow control limit set by /// the peer. fn flow_control_limit_send() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -8908,7 +11095,7 @@ mod tests { /// the server to close the connection immediately. fn invalid_initial_server() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); let frames = [frame::Frame::Padding { len: 10 }]; @@ -8940,7 +11127,7 @@ mod tests { /// the client to close the connection immediately. fn invalid_initial_client() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends initial flight. let (len, _) = pipe.client.send(&mut buf).unwrap(); @@ -8978,7 +11165,7 @@ mod tests { /// valid packet cause the server to close the connection immediately. fn invalid_initial_payload() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); let mut b = octets::OctetsMut::with_slice(&mut buf); @@ -8987,11 +11174,14 @@ mod tests { let pn = 0; let pn_len = packet::pkt_num_len(pn).unwrap(); + let dcid = pipe.client.destination_id(); + let scid = pipe.client.source_id(); + let hdr = Header { ty: packet::Type::Initial, version: pipe.client.version, - dcid: ConnectionId::from_ref(&pipe.client.dcid), - scid: ConnectionId::from_ref(&pipe.client.scid), + dcid: ConnectionId::from_ref(&dcid), + scid: ConnectionId::from_ref(&scid), pkt_num: 0, pkt_num_len: pn_len, token: pipe.client.token.clone(), @@ -9050,7 +11240,7 @@ mod tests { fn invalid_packet() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); let frames = [frame::Frame::Padding { len: 10 }]; @@ -9080,7 +11270,7 @@ mod tests { fn recv_empty_buffer() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.server_recv(&mut buf[..0]), Err(Error::BufferTooShort)); @@ -9097,7 +11287,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -9173,7 +11363,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -9230,7 +11420,7 @@ mod tests { /// data in the buffer, and that the buffer becomes readable on the other /// side. fn stream_zero_length_fin() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -9275,7 +11465,7 @@ mod tests { /// data in the buffer, that the buffer becomes readable on the other /// side and stays readable even if the stream is fin'd locally. fn stream_zero_length_fin_deferred_collection() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -9334,7 +11524,7 @@ mod tests { /// Tests that the stream gets created with stream_send() even if there's /// no data in the buffer and the fin flag is not set. fn stream_zero_length_non_fin() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.stream_send(0, b"", false), Ok(0)); @@ -9354,7 +11544,7 @@ mod tests { fn collect_streams() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.streams.len(), 0); @@ -9418,7 +11608,7 @@ mod tests { #[test] fn peer_cert() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); match pipe.client.peer_cert() { @@ -9429,6 +11619,29 @@ mod tests { } #[test] + fn peer_cert_chain() { + let mut config = Config::new(PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert-big.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + + let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + match pipe.client.peer_cert_chain() { + Some(c) => assert_eq!(c.len(), 5), + + None => panic!("missing server certificate chain"), + } + } + + #[test] fn retry() { let mut buf = [0; 65535]; @@ -9440,7 +11653,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); @@ -9479,7 +11692,14 @@ mod tests { // Server accepts connection. let from = "127.0.0.1:1234".parse().unwrap(); - pipe.server = accept(&scid, Some(&odcid), from, &mut config).unwrap(); + pipe.server = accept( + &scid, + Some(&odcid), + testing::Pipe::server_addr(), + from, + &mut config, + ) + .unwrap(); assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); assert_eq!(pipe.advance(), Ok(())); @@ -9500,7 +11720,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); @@ -9535,7 +11755,9 @@ mod tests { // Server accepts connection and send first flight. But original // destination connection ID is ignored. let from = "127.0.0.1:1234".parse().unwrap(); - pipe.server = accept(&scid, None, from, &mut config).unwrap(); + pipe.server = + accept(&scid, None, testing::Pipe::server_addr(), from, &mut config) + .unwrap(); assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); let flight = testing::emit_flight(&mut pipe.server).unwrap(); @@ -9558,7 +11780,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); @@ -9594,7 +11816,14 @@ mod tests { // destination connection ID is invalid. let from = "127.0.0.1:1234".parse().unwrap(); let odcid = ConnectionId::from_ref(b"bogus value"); - pipe.server = accept(&scid, Some(&odcid), from, &mut config).unwrap(); + pipe.server = accept( + &scid, + Some(&odcid), + testing::Pipe::server_addr(), + from, + &mut config, + ) + .unwrap(); assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); let flight = testing::emit_flight(&mut pipe.server).unwrap(); @@ -9615,7 +11844,7 @@ mod tests { #[test] fn connection_must_be_send() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); check_send(&mut pipe.client); } @@ -9629,7 +11858,7 @@ mod tests { #[test] fn connection_must_be_sync() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); check_sync(&mut pipe.client); } @@ -9637,7 +11866,7 @@ mod tests { fn data_blocked() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.stream_send(0, b"aaaaaaaaaa", false), Ok(10)); @@ -9676,7 +11905,7 @@ mod tests { fn stream_data_blocked() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.stream_send(0, b"aaaaa", false), Ok(5)); @@ -9752,7 +11981,7 @@ mod tests { #[test] fn stream_data_blocked_unblocked_flow_control() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -9819,7 +12048,7 @@ mod tests { fn app_limited_true() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(50000); @@ -9845,14 +12074,22 @@ mod tests { assert_eq!(pipe.advance(), Ok(())); // app_limited should be true because we send less than cwnd. - assert_eq!(pipe.server.recovery.app_limited(), true); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + true + ); } #[test] fn app_limited_false() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(50000); @@ -9880,14 +12117,164 @@ mod tests { // We can't create a new packet header because there is no room by cwnd. // app_limited should be false because we can't send more by cwnd. - assert_eq!(pipe.server.recovery.app_limited(), false); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + false + ); + } + + #[test] + fn sends_ack_only_pkt_when_full_cwnd_and_ack_elicited() { + let mut config = Config::new(PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.set_initial_max_data(50000); + config.set_initial_max_stream_data_bidi_local(50000); + config.set_initial_max_stream_data_bidi_remote(50000); + config.set_initial_max_streams_bidi(3); + config.set_initial_max_streams_uni(3); + config.set_max_recv_udp_payload_size(1200); + config.verify_peer(false); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Client sends stream data bigger than cwnd (it will never arrive to the + // server). + let send_buf1 = [0; 20000]; + assert_eq!(pipe.client.stream_send(0, &send_buf1, false), Ok(12000)); + + testing::emit_flight(&mut pipe.client).ok(); + + // Server sends some stream data that will need ACKs. + assert_eq!( + pipe.server.stream_send(1, &send_buf1[..500], false), + Ok(500) + ); + + testing::process_flight( + &mut pipe.client, + testing::emit_flight(&mut pipe.server).unwrap(), + ) + .unwrap(); + + let mut buf = [0; 2000]; + + let ret = pipe.client.send(&mut buf); + + assert_eq!(pipe.client.tx_cap, 0); + + assert!(matches!(ret, Ok((_, _))), "the client should at least send one packet to acknowledge the newly received data"); + + let (sent, _) = ret.unwrap(); + + assert_ne!(sent, 0, "the client should at least send a pure ACK packet"); + + let frames = + testing::decode_pkt(&mut pipe.server, &mut buf, sent).unwrap(); + assert_eq!(1, frames.len()); + assert!( + matches!(frames[0], frame::Frame::ACK { .. }), + "the packet sent by the client must be an ACK only packet" + ); + } + + /// Like sends_ack_only_pkt_when_full_cwnd_and_ack_elicited, but when + /// ack_eliciting is explicitly requested. + #[test] + fn sends_ack_only_pkt_when_full_cwnd_and_ack_elicited_despite_max_unacknowledging( + ) { + let mut config = Config::new(PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.set_initial_max_data(50000); + config.set_initial_max_stream_data_bidi_local(50000); + config.set_initial_max_stream_data_bidi_remote(50000); + config.set_initial_max_streams_bidi(3); + config.set_initial_max_streams_uni(3); + config.set_max_recv_udp_payload_size(1200); + config.verify_peer(false); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Client sends stream data bigger than cwnd (it will never arrive to the + // server). This exhausts the congestion window. + let send_buf1 = [0; 20000]; + assert_eq!(pipe.client.stream_send(0, &send_buf1, false), Ok(12000)); + + testing::emit_flight(&mut pipe.client).ok(); + + // Client gets PING frames from server, which elicit ACK + let mut buf = [0; 2000]; + for _ in 0..recovery::MAX_OUTSTANDING_NON_ACK_ELICITING { + let written = testing::encode_pkt( + &mut pipe.server, + packet::Type::Short, + &[frame::Frame::Ping], + &mut buf, + ) + .unwrap(); + + pipe.client_recv(&mut buf[..written]) + .expect("client recv ping"); + + // Client acknowledges despite a full congestion window + let ret = pipe.client.send(&mut buf); + + assert!(matches!(ret, Ok((_, _))), "the client should at least send one packet to acknowledge the newly received data"); + + let (sent, _) = ret.unwrap(); + + assert_ne!( + sent, 0, + "the client should at least send a pure ACK packet" + ); + + let frames = + testing::decode_pkt(&mut pipe.server, &mut buf, sent).unwrap(); + + assert_eq!(1, frames.len()); + + assert!( + matches!(frames[0], frame::Frame::ACK { .. }), + "the packet sent by the client must be an ACK only packet" + ); + } + + // The client shouldn't need to send any more packets after the ACK only + // packet it just sent. + assert_eq!( + pipe.client.send(&mut buf), + Err(Error::Done), + "nothing for client to send after ACK-only packet" + ); } #[test] fn app_limited_false_no_frame() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(50000); @@ -9915,14 +12302,22 @@ mod tests { // We can't create a new packet header because there is no room by cwnd. // app_limited should be false because we can't send more by cwnd. - assert_eq!(pipe.server.recovery.app_limited(), false); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + false + ); } #[test] fn app_limited_false_no_header() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(50000); @@ -9950,14 +12345,22 @@ mod tests { // We can't create a new frame because there is no room by cwnd. // app_limited should be false because we can't send more by cwnd. - assert_eq!(pipe.server.recovery.app_limited(), false); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + false + ); } #[test] fn app_limited_not_changed_on_no_new_frames() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(50000); @@ -9979,23 +12382,39 @@ mod tests { // Client's app_limited is true because its bytes-in-flight // is much smaller than the current cwnd. - assert_eq!(pipe.client.recovery.app_limited(), true); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + true + ); // Client has no new frames to send - returns Done. assert_eq!(testing::emit_flight(&mut pipe.client), Err(Error::Done)); // Client's app_limited should remain the same. - assert_eq!(pipe.client.recovery.app_limited(), true); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .app_limited(), + true + ); } #[test] fn limit_ack_ranges() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); - let epoch = packet::EPOCH_APPLICATION; + let epoch = packet::Epoch::Application; assert_eq!(pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.len(), 0); @@ -10051,7 +12470,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(1_000_000); config.set_initial_max_stream_data_bidi_local(1_000_000); @@ -10137,7 +12556,7 @@ mod tests { let frames = testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); - let stream = frames.iter().next().unwrap(); + let stream = frames.first().unwrap(); assert_eq!(stream, &frame::Frame::Stream { stream_id: 8, @@ -10160,7 +12579,7 @@ mod tests { let frames = testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); - let stream = frames.iter().next().unwrap(); + let stream = frames.first().unwrap(); assert_eq!(stream, &frame::Frame::Stream { stream_id: 16, @@ -10183,7 +12602,7 @@ mod tests { let frames = testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); - let stream = frames.iter().next().unwrap(); + let stream = frames.first().unwrap(); assert_eq!(stream, &frame::Frame::Stream { stream_id: 20, @@ -10208,7 +12627,7 @@ mod tests { testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 12, data: stream::RangeBuf::from(&out, off, false), @@ -10221,7 +12640,7 @@ mod tests { let frames = testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); - let stream = frames.iter().next().unwrap(); + let stream = frames.first().unwrap(); assert_eq!(stream, &frame::Frame::Stream { stream_id: 4, @@ -10244,7 +12663,7 @@ mod tests { let frames = testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); - let stream = frames.iter().next().unwrap(); + let stream = frames.first().unwrap(); assert_eq!(stream, &frame::Frame::Stream { stream_id: 0, @@ -10277,7 +12696,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -10330,7 +12749,7 @@ mod tests { testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 8, data: stream::RangeBuf::from(b"b", 0, false), @@ -10344,7 +12763,7 @@ mod tests { testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 0, data: stream::RangeBuf::from(b"b", 0, false), @@ -10358,7 +12777,7 @@ mod tests { testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 12, data: stream::RangeBuf::from(b"b", 0, false), @@ -10371,7 +12790,7 @@ mod tests { testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::Stream { stream_id: 4, data: stream::RangeBuf::from(b"b", 0, false), @@ -10397,7 +12816,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(1_000_000); config.set_initial_max_stream_data_bidi_local(1_000_000); @@ -10521,7 +12940,7 @@ mod tests { fn early_retransmit() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); // Client sends stream data. @@ -10538,12 +12957,28 @@ mod tests { pipe.client.on_timeout(); - let epoch = packet::EPOCH_APPLICATION; - assert_eq!(pipe.client.recovery.loss_probes[epoch], 1); + let epoch = packet::Epoch::Application; + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 1, + ); // Client retransmits stream data in PTO probe. let (len, _) = pipe.client.send(&mut buf).unwrap(); - assert_eq!(pipe.client.recovery.loss_probes[epoch], 0); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 0, + ); let frames = testing::decode_pkt(&mut pipe.server, &mut buf, len).unwrap(); @@ -10568,7 +13003,7 @@ mod tests { fn dont_coalesce_probes() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends Initial packet. let (len, _) = pipe.client.send(&mut buf).unwrap(); @@ -10580,13 +13015,29 @@ mod tests { pipe.client.on_timeout(); - let epoch = packet::EPOCH_INITIAL; - assert_eq!(pipe.client.recovery.loss_probes[epoch], 1); + let epoch = packet::Epoch::Initial; + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 1, + ); // Client sends PTO probe. let (len, _) = pipe.client.send(&mut buf).unwrap(); assert_eq!(len, 1200); - assert_eq!(pipe.client.recovery.loss_probes[epoch], 0); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 0, + ); // Wait for PTO to expire. let timer = pipe.client.timeout().unwrap(); @@ -10594,24 +13045,48 @@ mod tests { pipe.client.on_timeout(); - assert_eq!(pipe.client.recovery.loss_probes[epoch], 2); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 2, + ); // Client sends first PTO probe. let (len, _) = pipe.client.send(&mut buf).unwrap(); assert_eq!(len, 1200); - assert_eq!(pipe.client.recovery.loss_probes[epoch], 1); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 1, + ); // Client sends second PTO probe. let (len, _) = pipe.client.send(&mut buf).unwrap(); assert_eq!(len, 1200); - assert_eq!(pipe.client.recovery.loss_probes[epoch], 0); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .recovery + .loss_probes[epoch], + 0, + ); } #[test] fn coalesce_padding_short() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends first flight. let (len, _) = pipe.client.send(&mut buf).unwrap(); @@ -10653,7 +13128,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap(); @@ -10697,7 +13172,7 @@ mod tests { fn handshake_packet_type_corruption() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); // Client sends padded Initial. let (len, _) = pipe.client.send(&mut buf).unwrap(); @@ -10710,13 +13185,21 @@ mod tests { testing::process_flight(&mut pipe.client, flight).unwrap(); // Client sends Initial packet with ACK. - let (ty, len) = pipe.client.send_single(&mut buf, false).unwrap(); + let active_pid = + pipe.client.paths.get_active_path_id().expect("no active"); + let (ty, len) = pipe + .client + .send_single(&mut buf, active_pid, false) + .unwrap(); assert_eq!(ty, Type::Initial); assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); // Client sends Handshake packet. - let (ty, len) = pipe.client.send_single(&mut buf, false).unwrap(); + let (ty, len) = pipe + .client + .send_single(&mut buf, active_pid, false) + .unwrap(); assert_eq!(ty, Type::Handshake); // Packet type is corrupted to Initial. @@ -10731,7 +13214,7 @@ mod tests { #[test] fn dgram_send_fails_invalidstate() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!( @@ -10753,7 +13236,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -10772,14 +13255,26 @@ mod tests { assert_eq!(pipe.client.dgram_send(&send_buf), Ok(())); } - assert!(!pipe.client.recovery.app_limited()); + assert!(!pipe + .client + .paths + .get_active() + .expect("no active") + .recovery + .app_limited()); assert_eq!(pipe.client.dgram_send_queue.byte_size(), 1_000_000); let (len, _) = pipe.client.send(&mut buf).unwrap(); assert_ne!(pipe.client.dgram_send_queue.byte_size(), 0); assert_ne!(pipe.client.dgram_send_queue.byte_size(), 1_000_000); - assert!(!pipe.client.recovery.app_limited()); + assert!(!pipe + .client + .paths + .get_active() + .expect("no active") + .recovery + .app_limited()); assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len)); @@ -10792,7 +13287,13 @@ mod tests { assert_ne!(pipe.client.dgram_send_queue.byte_size(), 0); assert_ne!(pipe.client.dgram_send_queue.byte_size(), 1_000_000); - assert!(!pipe.client.recovery.app_limited()); + assert!(!pipe + .client + .paths + .get_active() + .expect("no active") + .recovery + .app_limited()); } #[test] @@ -10807,7 +13308,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -10844,7 +13345,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -10852,7 +13353,7 @@ mod tests { config.set_initial_max_stream_data_uni(10); config.set_initial_max_streams_bidi(3); config.set_initial_max_streams_uni(3); - config.enable_dgram(true, 10, 10); + config.enable_dgram(true, 2, 3); config.verify_peer(false); let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); @@ -10864,6 +13365,7 @@ mod tests { assert_eq!(pipe.client.dgram_send(b"hello, world"), Ok(())); assert_eq!(pipe.client.dgram_send(b"ciao, mondo"), Ok(())); assert_eq!(pipe.client.dgram_send(b"hola, mundo"), Ok(())); + assert!(pipe.client.is_dgram_send_queue_full()); assert_eq!(pipe.client.dgram_send_queue_byte_size(), 34); @@ -10872,6 +13374,7 @@ mod tests { assert_eq!(pipe.client.dgram_send_queue_len(), 2); assert_eq!(pipe.client.dgram_send_queue_byte_size(), 23); + assert!(!pipe.client.is_dgram_send_queue_full()); // Before packets exchanged, no dgrams on server receive side. assert_eq!(pipe.server.dgram_recv_queue_len(), 0); @@ -10884,11 +13387,13 @@ mod tests { assert_eq!(pipe.server.dgram_recv_queue_len(), 2); assert_eq!(pipe.server.dgram_recv_queue_byte_size(), 23); + assert!(pipe.server.is_dgram_recv_queue_full()); let result1 = pipe.server.dgram_recv(&mut buf); assert_eq!(result1, Ok(12)); assert_eq!(buf[0], b'h'); assert_eq!(buf[1], b'e'); + assert!(!pipe.server.is_dgram_recv_queue_full()); let result2 = pipe.server.dgram_recv(&mut buf); assert_eq!(result2, Ok(11)); @@ -10914,7 +13419,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -10960,7 +13465,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -11007,7 +13512,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -11058,7 +13563,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -11134,7 +13639,7 @@ mod tests { fn close() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.close(false, 0x1234, b"hello?"), Ok(())); @@ -11150,7 +13655,7 @@ mod tests { testing::decode_pkt(&mut pipe.server, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::ConnectionClose { error_code: 0x1234, frame_type: 0, @@ -11160,10 +13665,10 @@ mod tests { } #[test] - fn app_close() { + fn app_close_by_client() { let mut buf = [0; 65535]; - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.client.close(true, 0x1234, b"hello!"), Ok(())); @@ -11176,7 +13681,7 @@ mod tests { testing::decode_pkt(&mut pipe.server, &mut buf, len).unwrap(); assert_eq!( - frames.iter().next(), + frames.first(), Some(&frame::Frame::ApplicationClose { error_code: 0x1234, reason: b"hello!".to_vec(), @@ -11185,8 +13690,169 @@ mod tests { } #[test] + fn app_close_by_server_during_handshake_private_key_failure() { + let mut pipe = testing::Pipe::new().unwrap(); + pipe.server.handshake.set_failing_private_key_method(); + + // Client sends initial flight. + let flight = testing::emit_flight(&mut pipe.client).unwrap(); + assert_eq!( + testing::process_flight(&mut pipe.server, flight), + Err(Error::TlsFail) + ); + + let flight = testing::emit_flight(&mut pipe.server).unwrap(); + + // Both connections are not established. + assert!(!pipe.server.is_established()); + assert!(!pipe.client.is_established()); + + // Connection should already be closed due the failure during key signing. + assert_eq!( + pipe.server.close(true, 123, b"fail whale"), + Err(Error::Done) + ); + + testing::process_flight(&mut pipe.client, flight).unwrap(); + + // Connection should already be closed due the failure during key signing. + assert_eq!( + pipe.client.close(true, 123, b"fail whale"), + Err(Error::Done) + ); + + // Connection is not established on the server / client (and never + // will be) + assert!(!pipe.server.is_established()); + assert!(!pipe.client.is_established()); + + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!( + pipe.server.local_error(), + Some(&ConnectionError { + is_app: false, + error_code: 0x01, + reason: vec![], + }) + ); + assert_eq!( + pipe.client.peer_error(), + Some(&ConnectionError { + is_app: false, + error_code: 0x01, + reason: vec![], + }) + ); + } + + #[test] + fn app_close_by_server_during_handshake_not_established() { + let mut pipe = testing::Pipe::new().unwrap(); + + // Client sends initial flight. + let flight = testing::emit_flight(&mut pipe.client).unwrap(); + testing::process_flight(&mut pipe.server, flight).unwrap(); + + let flight = testing::emit_flight(&mut pipe.server).unwrap(); + + // Both connections are not established. + assert!(!pipe.client.is_established() && !pipe.server.is_established()); + + // Server closes before connection is established. + pipe.server.close(true, 123, b"fail whale").unwrap(); + + testing::process_flight(&mut pipe.client, flight).unwrap(); + + // Connection is established on the client. + assert!(pipe.client.is_established()); + + // Client sends after connection is established. + pipe.client.stream_send(0, b"badauthtoken", true).unwrap(); + + let flight = testing::emit_flight(&mut pipe.client).unwrap(); + testing::process_flight(&mut pipe.server, flight).unwrap(); + + // Connection is not established on the server (and never will be) + assert!(!pipe.server.is_established()); + + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!( + pipe.server.local_error(), + Some(&ConnectionError { + is_app: false, + error_code: 0x0c, + reason: vec![], + }) + ); + assert_eq!( + pipe.client.peer_error(), + Some(&ConnectionError { + is_app: false, + error_code: 0x0c, + reason: vec![], + }) + ); + } + + #[test] + fn app_close_by_server_during_handshake_established() { + let mut pipe = testing::Pipe::new().unwrap(); + + // Client sends initial flight. + let flight = testing::emit_flight(&mut pipe.client).unwrap(); + testing::process_flight(&mut pipe.server, flight).unwrap(); + + let flight = testing::emit_flight(&mut pipe.server).unwrap(); + + // Both connections are not established. + assert!(!pipe.client.is_established() && !pipe.server.is_established()); + + testing::process_flight(&mut pipe.client, flight).unwrap(); + + // Connection is established on the client. + assert!(pipe.client.is_established()); + + // Client sends after connection is established. + pipe.client.stream_send(0, b"badauthtoken", true).unwrap(); + + let flight = testing::emit_flight(&mut pipe.client).unwrap(); + testing::process_flight(&mut pipe.server, flight).unwrap(); + + // Connection is established on the server but the Handshake ACK has not + // been sent yet. + assert!(pipe.server.is_established()); + + // Server closes after connection is established. + pipe.server + .close(true, 123, b"Invalid authentication") + .unwrap(); + + // Server sends Handshake ACK and then 1RTT CONNECTION_CLOSE. + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!( + pipe.server.local_error(), + Some(&ConnectionError { + is_app: true, + error_code: 123, + reason: b"Invalid authentication".to_vec() + }) + ); + assert_eq!( + pipe.client.peer_error(), + Some(&ConnectionError { + is_app: true, + error_code: 123, + reason: b"Invalid authentication".to_vec() + }) + ); + } + + #[test] fn peer_error() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.server.close(false, 0x1234, b"hello?"), Ok(())); @@ -11204,7 +13870,7 @@ mod tests { #[test] fn app_peer_error() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.server.close(true, 0x1234, b"hello!"), Ok(())); @@ -11222,7 +13888,7 @@ mod tests { #[test] fn local_error() { - let mut pipe = testing::Pipe::default().unwrap(); + let mut pipe = testing::Pipe::new().unwrap(); assert_eq!(pipe.handshake(), Ok(())); assert_eq!(pipe.server.local_error(), None); @@ -11253,7 +13919,7 @@ mod tests { let mut client_config = Config::new(crate::PROTOCOL_VERSION).unwrap(); client_config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); client_config.set_max_recv_udp_payload_size(1200); @@ -11265,11 +13931,11 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); server_config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); server_config.verify_peer(false); server_config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); // Larger than the client server_config.set_max_send_udp_payload_size(1500); @@ -11279,22 +13945,53 @@ mod tests { Some("quic.tech"), &client_scid, client_addr, + server_addr, &mut client_config, ) .unwrap(), - server: accept(&server_scid, None, server_addr, &mut server_config) - .unwrap(), + server: accept( + &server_scid, + None, + server_addr, + client_addr, + &mut server_config, + ) + .unwrap(), }; // Before handshake - assert_eq!(pipe.server.recovery.max_datagram_size(), 1500); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .max_datagram_size(), + 1500, + ); assert_eq!(pipe.handshake(), Ok(())); // After handshake, max_datagram_size should match to client's // max_recv_udp_payload_size which is smaller - assert_eq!(pipe.server.recovery.max_datagram_size(), 1200); - assert_eq!(pipe.server.recovery.cwnd(), 12000); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .max_datagram_size(), + 1200, + ); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .recovery + .cwnd(), + 12000, + ); } #[test] @@ -11311,7 +14008,7 @@ mod tests { .load_priv_key_from_pem_file("examples/cert.key") .unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(100000); config.set_initial_max_stream_data_bidi_local(10000); @@ -11390,7 +14087,7 @@ mod tests { client_config.load_priv_key_from_pem_file("examples/cert.key")?; for config in [&mut client_config, &mut server_config] { - config.set_application_protos(b"\x06proto1\x06proto2")?; + config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); config.set_initial_max_stream_data_bidi_remote(15); @@ -11417,9 +14114,16 @@ mod tests { Some("quic.tech"), &client_scid, client_addr, + server_addr, &mut client_config, )?, - server: accept(&server_scid, None, server_addr, &mut server_config)?, + server: accept( + &server_scid, + None, + server_addr, + client_addr, + &mut server_config, + )?, }; assert_eq!(pipe.handshake(), Ok(())); @@ -11432,7 +14136,7 @@ mod tests { fn last_tx_data_larger_than_tx_data() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config - .set_application_protos(b"\x06proto1\x06proto2") + .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); config.set_initial_max_data(12000); config.set_initial_max_stream_data_bidi_local(20000); @@ -11487,16 +14191,1649 @@ mod tests { pipe.send_pkt_to_server(pkt_type, &frames, &mut buf) .unwrap(); } + + /// Tests that when the client provides a new ConnectionId, it eventually + /// reaches the server and notifies the application. + #[test] + fn send_connection_ids() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // So far, there should not have any QUIC event. + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 2); + + let (scid, reset_token) = testing::create_cid_and_reset_token(16); + assert_eq!(pipe.client.new_source_cid(&scid, reset_token, false), Ok(1)); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + // At this point, the server should be notified that it has a new CID. + assert_eq!(pipe.server.available_dcids(), 1); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 1); + + // Now, a second CID can be provided. + let (scid, reset_token) = testing::create_cid_and_reset_token(16); + assert_eq!(pipe.client.new_source_cid(&scid, reset_token, false), Ok(2)); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + // At this point, the server should be notified that it has a new CID. + assert_eq!(pipe.server.available_dcids(), 2); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 0); + + // If now the client tries to send another CID, it reports an error + // since it exceeds the limit of active CIDs. + let (scid, reset_token) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.client.new_source_cid(&scid, reset_token, false), + Err(Error::IdLimit), + ); + } + + #[test] + /// Exercices the handling of NEW_CONNECTION_ID and RETIRE_CONNECTION_ID + /// frames. + fn connection_id_handling() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // So far, there should not have any QUIC event. + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 1); + + let scid = pipe.client.source_id().into_owned(); + + let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_1, false), + Ok(1) + ); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + // At this point, the server should be notified that it has a new CID. + assert_eq!(pipe.server.available_dcids(), 1); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 0); + + // Now we assume that the client wants to advertise more source + // Connection IDs than the advertised limit. This is valid if it + // requests its peer to retire enough Connection IDs to fit within the + // limits. + + let (scid_2, reset_token_2) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.client.new_source_cid(&scid_2, reset_token_2, true), + Ok(2) + ); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + // At this point, the server still have a spare DCID. + assert_eq!(pipe.server.available_dcids(), 1); + assert_eq!(pipe.server.path_event_next(), None); + + // Client should have received a retired notification. + assert_eq!(pipe.client.retired_scid_next(), Some(scid)); + assert_eq!(pipe.client.retired_scid_next(), None); + + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.client.source_cids_left(), 0); + + // The active Destination Connection ID of the server should now be the + // one with sequence number 1. + assert_eq!(pipe.server.destination_id(), scid_1); + + // Now tries to experience CID retirement. If the server tries to remove + // non-existing DCIDs, it fails. + assert_eq!( + pipe.server.retire_destination_cid(0), + Err(Error::InvalidState) + ); + assert_eq!( + pipe.server.retire_destination_cid(3), + Err(Error::InvalidState) + ); + + // Now it removes DCID with sequence 1. + assert_eq!(pipe.server.retire_destination_cid(1), Ok(())); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.retired_scid_next(), Some(scid_1)); + assert_eq!(pipe.client.retired_scid_next(), None); + + assert_eq!(pipe.server.destination_id(), scid_2); + assert_eq!(pipe.server.available_dcids(), 0); + + // Trying to remove the last DCID triggers an error. + assert_eq!( + pipe.server.retire_destination_cid(2), + Err(Error::OutOfIdentifiers) + ); + } + + #[test] + fn lost_connection_id_frames() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let scid = pipe.client.source_id().into_owned(); + + let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_1, false), + Ok(1) + ); + + // Packets are sent, but never received. + testing::emit_flight(&mut pipe.client).unwrap(); + + // Wait until timer expires. Since the RTT is very low, wait a bit more. + let timer = pipe.client.timeout().unwrap(); + std::thread::sleep(timer + time::Duration::from_millis(1)); + + pipe.client.on_timeout(); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + // At this point, the server should be notified that it has a new CID. + assert_eq!(pipe.server.available_dcids(), 1); + + // Now the server retires the first Destination CID. + assert_eq!(pipe.server.retire_destination_cid(0), Ok(())); + + // But the packet never reaches the client. + testing::emit_flight(&mut pipe.server).unwrap(); + + // Wait until timer expires. Since the RTT is very low, wait a bit more. + let timer = pipe.server.timeout().unwrap(); + std::thread::sleep(timer + time::Duration::from_millis(1)); + + pipe.server.on_timeout(); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.client.retired_scid_next(), Some(scid)); + assert_eq!(pipe.client.retired_scid_next(), None); + } + + #[test] + fn sending_duplicate_scids() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let (scid_1, reset_token_1) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_1, false), + Ok(1) + ); + assert_eq!(pipe.advance(), Ok(())); + + // Trying to send the same CID with a different reset token raises an + // InvalidState error. + let reset_token_2 = reset_token_1.wrapping_add(1); + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_2, false), + Err(Error::InvalidState), + ); + + // Retrying to send the exact same CID with the same token returns the + // previously assigned CID seq, but without sending anything. + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_1, false), + Ok(1) + ); + assert_eq!(pipe.client.ids.has_new_scids(), false); + + // Now retire this new CID. + assert_eq!(pipe.server.retire_destination_cid(1), Ok(())); + assert_eq!(pipe.advance(), Ok(())); + + // It is up to the application to ensure that a given SCID is not reused + // later. + assert_eq!( + pipe.client.new_source_cid(&scid_1, reset_token_1, false), + Ok(2), + ); + } + + // Utility function. + fn pipe_with_exchanged_cids( + config: &mut Config, client_scid_len: usize, server_scid_len: usize, + additional_cids: usize, + ) -> testing::Pipe { + let mut pipe = testing::Pipe::with_config_and_scid_lengths( + config, + client_scid_len, + server_scid_len, + ) + .unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let mut c_cids = Vec::new(); + let mut c_reset_tokens = Vec::new(); + let mut s_cids = Vec::new(); + let mut s_reset_tokens = Vec::new(); + + for i in 0..additional_cids { + if client_scid_len > 0 { + let (c_cid, c_reset_token) = + testing::create_cid_and_reset_token(client_scid_len); + c_cids.push(c_cid); + c_reset_tokens.push(c_reset_token); + + assert_eq!( + pipe.client.new_source_cid( + &c_cids[i], + c_reset_tokens[i], + true + ), + Ok(i as u64 + 1) + ); + } + + if server_scid_len > 0 { + let (s_cid, s_reset_token) = + testing::create_cid_and_reset_token(server_scid_len); + s_cids.push(s_cid); + s_reset_tokens.push(s_reset_token); + assert_eq!( + pipe.server.new_source_cid( + &s_cids[i], + s_reset_tokens[i], + true + ), + Ok(i as u64 + 1) + ); + } + } + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + if client_scid_len > 0 { + assert_eq!(pipe.server.available_dcids(), additional_cids); + } + + if server_scid_len > 0 { + assert_eq!(pipe.client.available_dcids(), additional_cids); + } + + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.path_event_next(), None); + + pipe + } + + #[test] + fn path_validation() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + + // We cannot probe a new path if there are not enough identifiers. + assert_eq!( + pipe.client.probe_path(client_addr_2, server_addr), + Err(Error::OutOfIdentifiers) + ); + + let (c_cid, c_reset_token) = testing::create_cid_and_reset_token(16); + + assert_eq!( + pipe.client.new_source_cid(&c_cid, c_reset_token, true), + Ok(1) + ); + + let (s_cid, s_reset_token) = testing::create_cid_and_reset_token(16); + assert_eq!( + pipe.server.new_source_cid(&s_cid, s_reset_token, true), + Ok(1) + ); + + // We need to exchange the CIDs first. + assert_eq!( + pipe.client.probe_path(client_addr_2, server_addr), + Err(Error::OutOfIdentifiers) + ); + + // Let exchange packets over the connection. + assert_eq!(pipe.advance(), Ok(())); + + assert_eq!(pipe.server.available_dcids(), 1); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!(pipe.client.available_dcids(), 1); + assert_eq!(pipe.client.path_event_next(), None); + + // Now the path probing can work. + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + // But the server cannot probe a yet-unseen path. + assert_eq!( + pipe.server.probe_path(server_addr, client_addr_2), + Err(Error::InvalidState), + ); + + assert_eq!(pipe.advance(), Ok(())); + + // The path should be validated at some point. + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)), + ); + assert_eq!(pipe.client.path_event_next(), None); + + // The server should be notified of this new path. + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)), + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)), + ); + assert_eq!(pipe.server.path_event_next(), None); + + // The server can later probe the path again. + assert_eq!(pipe.server.probe_path(server_addr, client_addr_2), Ok(1)); + + // This should not trigger any event at client side. + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!(pipe.server.path_event_next(), None); + } + + #[test] + fn losing_probing_packets() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + // The client creates the PATH CHALLENGE, but it is lost. + testing::emit_flight(&mut pipe.client).unwrap(); + + // Wait until probing timer expires. Since the RTT is very low, + // wait a bit more. + let probed_pid = pipe + .client + .paths + .path_id_from_addrs(&(client_addr_2, server_addr)) + .unwrap(); + let probe_instant = pipe + .client + .paths + .get(probed_pid) + .unwrap() + .recovery + .loss_detection_timer() + .unwrap(); + let timer = probe_instant.duration_since(time::Instant::now()); + std::thread::sleep(timer + time::Duration::from_millis(1)); + + pipe.client.on_timeout(); + + assert_eq!(pipe.advance(), Ok(())); + + // The path should be validated at some point. + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + assert_eq!(pipe.client.path_event_next(), None); + + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + // The path should be validated at some point. + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + } + + #[test] + fn failed_path_validation() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + for _ in 0..MAX_PROBING_TIMEOUTS { + // The client creates the PATH CHALLENGE, but it is always lost. + testing::emit_flight(&mut pipe.client).unwrap(); + + // Wait until probing timer expires. Since the RTT is very low, + // wait a bit more. + let probed_pid = pipe + .client + .paths + .path_id_from_addrs(&(client_addr_2, server_addr)) + .unwrap(); + let probe_instant = pipe + .client + .paths + .get(probed_pid) + .unwrap() + .recovery + .loss_detection_timer() + .unwrap(); + let timer = probe_instant.duration_since(time::Instant::now()); + std::thread::sleep(timer + time::Duration::from_millis(1)); + + pipe.client.on_timeout(); + } + + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::FailedValidation(client_addr_2, server_addr)), + ); + } + + #[test] + fn client_discard_unknown_address() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_uni(10); + config.set_initial_max_streams_uni(3); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Server sends stream data. + assert_eq!(pipe.server.stream_send(3, b"a", true), Ok(1)); + + let mut flight = + testing::emit_flight(&mut pipe.server).expect("no packet"); + // Let's change the address info. + flight + .iter_mut() + .for_each(|(_, si)| si.from = "127.0.0.1:9292".parse().unwrap()); + assert_eq!(testing::process_flight(&mut pipe.client, flight), Ok(())); + assert_eq!(pipe.client.paths.len(), 1); + } + + #[test] + fn path_validation_limited_mtu() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + // Limited MTU of 1199 bytes for some reason. + testing::process_flight( + &mut pipe.server, + testing::emit_flight_with_max_buffer(&mut pipe.client, 1199) + .expect("no packet"), + ) + .expect("error when processing client packets"); + testing::process_flight( + &mut pipe.client, + testing::emit_flight(&mut pipe.server).expect("no packet"), + ) + .expect("error when processing client packets"); + let probed_pid = pipe + .client + .paths + .path_id_from_addrs(&(client_addr_2, server_addr)) + .unwrap(); + assert_eq!( + pipe.client.paths.get(probed_pid).unwrap().validated(), + false, + ); + assert_eq!(pipe.client.path_event_next(), None); + // Now let the client probe at its MTU. + assert_eq!(pipe.advance(), Ok(())); + assert_eq!(pipe.client.paths.get(probed_pid).unwrap().validated(), true); + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + } + + #[test] + fn path_probing_dos() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + assert_eq!(pipe.advance(), Ok(())); + + // The path should be validated at some point. + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + assert_eq!(pipe.client.path_event_next(), None); + + // The server should be notified of this new path. + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + + assert_eq!(pipe.server.paths.len(), 2); + + // Now forge a packet reusing the unverified path's CID over another + // 4-tuple. + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + let client_addr_3 = "127.0.0.1:9012".parse().unwrap(); + let mut flight = + testing::emit_flight(&mut pipe.client).expect("no generated packet"); + flight + .iter_mut() + .for_each(|(_, si)| si.from = client_addr_3); + testing::process_flight(&mut pipe.server, flight) + .expect("failed to process"); + assert_eq!(pipe.server.paths.len(), 2); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::ReusedSourceConnectionId( + 1, + (server_addr, client_addr_2), + (server_addr, client_addr_3) + )) + ); + assert_eq!(pipe.server.path_event_next(), None); + } + + #[test] + fn retiring_active_path_dcid() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + assert_eq!( + pipe.client.retire_destination_cid(0), + Err(Error::OutOfIdentifiers) + ); + } + + #[test] + fn send_on_path_test() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_initial_max_data(100000); + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(100000); + config.set_initial_max_streams_bidi(2); + config.set_active_connection_id_limit(4); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 3); + + let server_addr = testing::Pipe::server_addr(); + let client_addr = testing::Pipe::client_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + + let mut buf = [0; 65535]; + // There is nothing to send on the initial path. + assert_eq!( + pipe.client.send_on_path( + &mut buf, + Some(client_addr), + Some(server_addr) + ), + Err(Error::Done) + ); + // Client should send padded PATH_CHALLENGE. + let (sent, si) = pipe + .client + .send_on_path(&mut buf, Some(client_addr_2), Some(server_addr)) + .expect("No error"); + assert_eq!(sent, MIN_CLIENT_INITIAL_LEN); + assert_eq!(si.from, client_addr_2); + assert_eq!(si.to, server_addr); + // A non-existing 4-tuple raises an InvalidState. + let client_addr_3 = "127.0.0.1:9012".parse().unwrap(); + let server_addr_2 = "127.0.0.1:9876".parse().unwrap(); + assert_eq!( + pipe.client.send_on_path( + &mut buf, + Some(client_addr_3), + Some(server_addr) + ), + Err(Error::InvalidState) + ); + assert_eq!( + pipe.client.send_on_path( + &mut buf, + Some(client_addr), + Some(server_addr_2) + ), + Err(Error::InvalidState) + ); + + // Let's introduce some additional path challenges and data exchange. + assert_eq!(pipe.client.probe_path(client_addr, server_addr_2), Ok(2)); + assert_eq!(pipe.client.probe_path(client_addr_3, server_addr), Ok(3)); + // Just to fit in two packets. + assert_eq!(pipe.client.stream_send(0, &buf[..1201], true), Ok(1201)); + + // PATH_CHALLENGE + let (sent, si) = pipe + .client + .send_on_path(&mut buf, Some(client_addr), None) + .expect("No error"); + assert_eq!(sent, MIN_CLIENT_INITIAL_LEN); + assert_eq!(si.from, client_addr); + assert_eq!(si.to, server_addr_2); + // STREAM frame on active path. + let (_, si) = pipe + .client + .send_on_path(&mut buf, Some(client_addr), None) + .expect("No error"); + assert_eq!(si.from, client_addr); + assert_eq!(si.to, server_addr); + // PATH_CHALLENGE + let (sent, si) = pipe + .client + .send_on_path(&mut buf, None, Some(server_addr)) + .expect("No error"); + assert_eq!(sent, MIN_CLIENT_INITIAL_LEN); + assert_eq!(si.from, client_addr_3); + assert_eq!(si.to, server_addr); + // STREAM frame on active path. + let (_, si) = pipe + .client + .send_on_path(&mut buf, None, Some(server_addr)) + .expect("No error"); + assert_eq!(si.from, client_addr); + assert_eq!(si.to, server_addr); + + // No more data to exchange leads to Error::Done. + assert_eq!( + pipe.client.send_on_path(&mut buf, Some(client_addr), None), + Err(Error::Done) + ); + assert_eq!( + pipe.client.send_on_path(&mut buf, None, Some(server_addr)), + Err(Error::Done) + ); + + assert_eq!( + pipe.client + .paths_iter(client_addr) + .collect::<Vec<_>>() + .sort(), + vec![server_addr, server_addr_2].sort(), + ); + assert_eq!( + pipe.client + .paths_iter(client_addr_2) + .collect::<Vec<_>>() + .sort(), + vec![server_addr].sort(), + ); + assert_eq!( + pipe.client + .paths_iter(client_addr_3) + .collect::<Vec<_>>() + .sort(), + vec![server_addr].sort(), + ); + } + + #[test] + fn connection_migration() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_bidi_local(15); + config.set_initial_max_stream_data_bidi_remote(15); + config.set_initial_max_stream_data_uni(10); + config.set_initial_max_streams_bidi(3); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 2); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + let client_addr_3 = "127.0.0.1:9012".parse().unwrap(); + let client_addr_4 = "127.0.0.1:8908".parse().unwrap(); + + // Case 1: the client first probes the new address, the server too, and + // then migrates. + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.client.is_path_validated(client_addr_2, server_addr), + Ok(true) + ); + assert_eq!( + pipe.server.is_path_validated(server_addr, client_addr_2), + Ok(true) + ); + // The server can never initiates the connection migration. + assert_eq!( + pipe.server.migrate(server_addr, client_addr_2), + Err(Error::InvalidState) + ); + assert_eq!(pipe.client.migrate(client_addr_2, server_addr), Ok(1)); + assert_eq!(pipe.client.stream_send(0, b"data", true), Ok(4)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .local_addr(), + client_addr_2 + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .peer_addr(), + server_addr + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::PeerMigrated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .local_addr(), + server_addr + ); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .peer_addr(), + client_addr_2 + ); + + // Case 2: the client migrates on a path that was not previously + // validated, and has spare SCIDs/DCIDs to do so. + assert_eq!(pipe.client.migrate(client_addr_3, server_addr), Ok(2)); + assert_eq!(pipe.client.stream_send(4, b"data", true), Ok(4)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .local_addr(), + client_addr_3 + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .peer_addr(), + server_addr + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_3)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_3)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::PeerMigrated(server_addr, client_addr_3)) + ); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .local_addr(), + server_addr + ); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .peer_addr(), + client_addr_3 + ); + + // Case 3: the client tries to migrate on the current active path. + // This is not an error, but it triggers nothing. + assert_eq!(pipe.client.migrate(client_addr_3, server_addr), Ok(2)); + assert_eq!(pipe.client.stream_send(8, b"data", true), Ok(4)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .local_addr(), + client_addr_3 + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .peer_addr(), + server_addr + ); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .local_addr(), + server_addr + ); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .peer_addr(), + client_addr_3 + ); + + // Case 4: the client tries to migrate on a path that was not previously + // validated, and has no spare SCIDs/DCIDs. Prevent active migration. + assert_eq!( + pipe.client.migrate(client_addr_4, server_addr), + Err(Error::OutOfIdentifiers) + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .local_addr(), + client_addr_3 + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .peer_addr(), + server_addr + ); + } + + #[test] + fn connection_migration_zero_length_cid() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_bidi_local(15); + config.set_initial_max_stream_data_bidi_remote(15); + config.set_initial_max_stream_data_uni(10); + config.set_initial_max_streams_bidi(3); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 0, 16, 1); + + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + + // The client migrates on a path that was not previously + // validated, and has spare SCIDs/DCIDs to do so. + assert_eq!(pipe.client.migrate(client_addr_2, server_addr), Ok(1)); + assert_eq!(pipe.client.stream_send(4, b"data", true), Ok(4)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .local_addr(), + client_addr_2 + ); + assert_eq!( + pipe.client + .paths + .get_active() + .expect("no active") + .peer_addr(), + server_addr + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::PeerMigrated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .local_addr(), + server_addr + ); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .peer_addr(), + client_addr_2 + ); + } + + #[test] + fn connection_migration_reordered_non_probing() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(2); + config.set_initial_max_data(30); + config.set_initial_max_stream_data_bidi_local(15); + config.set_initial_max_stream_data_bidi_remote(15); + config.set_initial_max_stream_data_uni(10); + config.set_initial_max_streams_bidi(3); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let client_addr = testing::Pipe::client_addr(); + let server_addr = testing::Pipe::server_addr(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + + assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1)); + assert_eq!(pipe.advance(), Ok(())); + assert_eq!( + pipe.client.path_event_next(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + assert_eq!(pipe.client.path_event_next(), None); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, client_addr_2)) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::Validated(server_addr, client_addr_2)) + ); + assert_eq!(pipe.server.path_event_next(), None); + + // A first flight sent from secondary address. + assert_eq!(pipe.client.stream_send(0, b"data", true), Ok(4)); + let mut first = testing::emit_flight(&mut pipe.client).unwrap(); + first.iter_mut().for_each(|(_, si)| si.from = client_addr_2); + // A second one, but sent from the original one. + assert_eq!(pipe.client.stream_send(4, b"data", true), Ok(4)); + let second = testing::emit_flight(&mut pipe.client).unwrap(); + // Second flight is received before first one. + assert_eq!(testing::process_flight(&mut pipe.server, second), Ok(())); + assert_eq!(testing::process_flight(&mut pipe.server, first), Ok(())); + + // Server does not perform connection migration because of packet + // reordering. + assert_eq!(pipe.server.path_event_next(), None); + assert_eq!( + pipe.server + .paths + .get_active() + .expect("no active") + .peer_addr(), + client_addr + ); + } + + #[test] + fn resilience_against_migration_attack() { + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.verify_peer(false); + config.set_active_connection_id_limit(3); + config.set_initial_max_data(100000); + config.set_initial_max_stream_data_bidi_local(100000); + config.set_initial_max_stream_data_bidi_remote(100000); + config.set_initial_max_streams_bidi(2); + + let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1); + + let client_addr = testing::Pipe::client_addr(); + let server_addr = testing::Pipe::server_addr(); + let spoofed_client_addr = "127.0.0.1:6666".parse().unwrap(); + + const DATA_BYTES: usize = 24000; + let buf = [42; DATA_BYTES]; + let mut recv_buf = [0; DATA_BYTES]; + assert_eq!(pipe.server.stream_send(1, &buf, true), Ok(12000)); + assert_eq!( + testing::process_flight( + &mut pipe.client, + testing::emit_flight(&mut pipe.server).unwrap() + ), + Ok(()) + ); + let (rcv_data_1, _) = pipe.client.stream_recv(1, &mut recv_buf).unwrap(); + + // Fake the source address of client. + let mut faked_addr_flight = + testing::emit_flight(&mut pipe.client).unwrap(); + faked_addr_flight + .iter_mut() + .for_each(|(_, si)| si.from = spoofed_client_addr); + assert_eq!( + testing::process_flight(&mut pipe.server, faked_addr_flight), + Ok(()) + ); + assert_eq!(pipe.server.stream_send(1, &buf[12000..], true), Ok(12000)); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::ReusedSourceConnectionId( + 0, + (server_addr, client_addr), + (server_addr, spoofed_client_addr) + )) + ); + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::New(server_addr, spoofed_client_addr)) + ); + + assert_eq!( + pipe.server.is_path_validated(server_addr, client_addr), + Ok(true) + ); + assert_eq!( + pipe.server + .is_path_validated(server_addr, spoofed_client_addr), + Ok(false) + ); + + // The client creates the PATH CHALLENGE, but it is always lost. + testing::emit_flight(&mut pipe.server).unwrap(); + + // Wait until probing timer expires. Since the RTT is very low, + // wait a bit more. + let probed_pid = pipe + .server + .paths + .path_id_from_addrs(&(server_addr, spoofed_client_addr)) + .unwrap(); + let probe_instant = pipe + .server + .paths + .get(probed_pid) + .unwrap() + .recovery + .loss_detection_timer() + .unwrap(); + let timer = probe_instant.duration_since(time::Instant::now()); + std::thread::sleep(timer + time::Duration::from_millis(1)); + + pipe.server.on_timeout(); + + // Because of the small ACK size, the server cannot send more to the + // client. Fallback on the previous active path. + assert_eq!( + pipe.server.path_event_next(), + Some(PathEvent::FailedValidation( + server_addr, + spoofed_client_addr + )) + ); + + assert_eq!( + pipe.server.is_path_validated(server_addr, client_addr), + Ok(true) + ); + assert_eq!( + pipe.server + .is_path_validated(server_addr, spoofed_client_addr), + Ok(false) + ); + + let server_active_path = pipe.server.paths.get_active().unwrap(); + assert_eq!(server_active_path.local_addr(), server_addr); + assert_eq!(server_active_path.peer_addr(), client_addr); + assert_eq!(pipe.advance(), Ok(())); + let (rcv_data_2, fin) = + pipe.client.stream_recv(1, &mut recv_buf).unwrap(); + assert_eq!(fin, true); + assert_eq!(rcv_data_1 + rcv_data_2, DATA_BYTES); + } + + #[test] + fn consecutive_non_ack_eliciting() { + let mut buf = [0; 65535]; + + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Client sends a bunch of PING frames, causing server to ACK (ACKs aren't + // ack-eliciting) + let frames = [frame::Frame::Ping]; + let pkt_type = packet::Type::Short; + for _ in 0..24 { + let len = pipe + .send_pkt_to_server(pkt_type, &frames, &mut buf) + .unwrap(); + assert!(len > 0); + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + assert!( + frames + .iter() + .all(|frame| matches!(frame, frame::Frame::ACK { .. })), + "ACK only" + ); + } + + // After 24 non-ack-eliciting, an ACK is explicitly elicited with a PING + let len = pipe + .send_pkt_to_server(pkt_type, &frames, &mut buf) + .unwrap(); + assert!(len > 0); + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + assert!( + frames + .iter() + .any(|frame| matches!(frame, frame::Frame::Ping)), + "found a PING" + ); + } + + #[test] + fn send_ack_eliciting_causes_ping() { + // First establish a connection + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Queue a PING frame + pipe.server.send_ack_eliciting().unwrap(); + + // Make sure ping is sent + let mut buf = [0; 1500]; + let (len, _) = pipe.server.send(&mut buf).unwrap(); + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + let mut iter = frames.iter(); + + assert_eq!(iter.next(), Some(&frame::Frame::Ping)); + } + + #[test] + fn send_ack_eliciting_no_ping() { + // First establish a connection + let mut pipe = testing::Pipe::new().unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + // Queue a PING frame + pipe.server.send_ack_eliciting().unwrap(); + + // Send a stream frame, which is ACK-eliciting to make sure the ping is + // not sent + assert_eq!(pipe.server.stream_send(1, b"a", false), Ok(1)); + + // Make sure ping is not sent + let mut buf = [0; 1500]; + let (len, _) = pipe.server.send(&mut buf).unwrap(); + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + let mut iter = frames.iter(); + + assert!(matches!( + iter.next(), + Some(&frame::Frame::Stream { + stream_id: 1, + data: _ + }) + )); + assert!(iter.next().is_none()); + } + + /// Tests that streams do not keep being "writable" after being collected + /// on reset. + #[test] + fn stop_sending_stream_send_after_reset_stream_ack() { + let mut b = [0; 15]; + + let mut buf = [0; 65535]; + + let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + config + .load_cert_chain_from_pem_file("examples/cert.crt") + .unwrap(); + config + .load_priv_key_from_pem_file("examples/cert.key") + .unwrap(); + config + .set_application_protos(&[b"proto1", b"proto2"]) + .unwrap(); + config.set_initial_max_data(999999999); + config.set_initial_max_stream_data_bidi_local(30); + config.set_initial_max_stream_data_bidi_remote(30); + config.set_initial_max_stream_data_uni(30); + config.set_initial_max_streams_bidi(1000); + config.set_initial_max_streams_uni(0); + config.verify_peer(false); + + let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); + assert_eq!(pipe.handshake(), Ok(())); + + assert_eq!(pipe.server.streams.len(), 0); + assert_eq!(pipe.server.readable().len(), 0); + assert_eq!(pipe.server.writable().len(), 0); + + // Client opens a load of streams + assert_eq!(pipe.client.stream_send(0, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(4, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(8, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(12, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(16, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(20, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(24, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(28, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(32, b"hello", true), Ok(5)); + assert_eq!(pipe.client.stream_send(36, b"hello", true), Ok(5)); + assert_eq!(pipe.advance(), Ok(())); + + // Server iterators are populated + let mut r = pipe.server.readable(); + assert_eq!(r.len(), 10); + assert_eq!(r.next(), Some(28)); + assert_eq!(r.next(), Some(12)); + assert_eq!(r.next(), Some(24)); + assert_eq!(r.next(), Some(8)); + assert_eq!(r.next(), Some(36)); + assert_eq!(r.next(), Some(20)); + assert_eq!(r.next(), Some(4)); + assert_eq!(r.next(), Some(32)); + assert_eq!(r.next(), Some(16)); + assert_eq!(r.next(), Some(0)); + assert_eq!(r.next(), None); + + let mut w = pipe.server.writable(); + assert_eq!(w.len(), 10); + assert_eq!(w.next(), Some(28)); + assert_eq!(w.next(), Some(12)); + assert_eq!(w.next(), Some(24)); + assert_eq!(w.next(), Some(8)); + assert_eq!(w.next(), Some(36)); + assert_eq!(w.next(), Some(20)); + assert_eq!(w.next(), Some(4)); + assert_eq!(w.next(), Some(32)); + assert_eq!(w.next(), Some(16)); + assert_eq!(w.next(), Some(0)); + assert_eq!(w.next(), None); + + // Read one stream + assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((5, true))); + assert!(pipe.server.stream_finished(0)); + + assert_eq!(pipe.server.readable().len(), 9); + assert_eq!(pipe.server.writable().len(), 10); + + assert_eq!(pipe.server.stream_writable(0, 0), Ok(true)); + + // Server sends data on stream 0, until blocked. + loop { + if pipe.server.stream_send(0, b"world", false) == Err(Error::Done) { + break; + } + + assert_eq!(pipe.advance(), Ok(())); + } + + assert_eq!(pipe.server.writable().len(), 9); + assert_eq!(pipe.server.stream_writable(0, 0), Ok(true)); + + // Client sends STOP_SENDING. + let frames = [frame::Frame::StopSending { + stream_id: 0, + error_code: 42, + }]; + + let pkt_type = packet::Type::Short; + let len = pipe + .send_pkt_to_server(pkt_type, &frames, &mut buf) + .unwrap(); + + // Server sent a RESET_STREAM frame in response. + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + + let mut iter = frames.iter(); + + // Skip ACK frame. + iter.next(); + + assert_eq!( + iter.next(), + Some(&frame::Frame::ResetStream { + stream_id: 0, + error_code: 42, + final_size: 30, + }) + ); + + // Stream 0 is now writable in order to make apps aware of STOP_SENDING + // via returning an error. + let mut w = pipe.server.writable(); + assert_eq!(w.len(), 10); + + assert!(w.find(|&s| s == 0).is_some()); + assert_eq!( + pipe.server.stream_writable(0, 1), + Err(Error::StreamStopped(42)) + ); + + assert_eq!(pipe.server.writable().len(), 10); + assert_eq!(pipe.server.streams.len(), 10); + + // Client acks RESET_STREAM frame. + let mut ranges = ranges::RangeSet::default(); + ranges.insert(0..12); + + let frames = [frame::Frame::ACK { + ack_delay: 15, + ranges, + ecn_counts: None, + }]; + + assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0)); + + // Stream is collected on the server after RESET_STREAM is acked. + assert_eq!(pipe.server.streams.len(), 9); + + // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again. + let frames = [frame::Frame::StopSending { + stream_id: 0, + error_code: 42, + }]; + + let len = pipe + .send_pkt_to_server(pkt_type, &frames, &mut buf) + .unwrap(); + + let frames = + testing::decode_pkt(&mut pipe.client, &mut buf, len).unwrap(); + + assert_eq!(frames.len(), 1); + + match frames.first() { + Some(frame::Frame::ACK { .. }) => (), + + f => panic!("expected ACK frame, got {:?}", f), + }; + + assert_eq!(pipe.server.streams.len(), 9); + + // Stream 0 has been collected and must not be writable anymore. + let mut w = pipe.server.writable(); + assert_eq!(w.len(), 9); + assert!(w.find(|&s| s == 0).is_none()); + + // If we called send before the client ACK of reset stream, it would + // have failed with StreamStopped. + assert_eq!(pipe.server.stream_send(0, b"world", true), Err(Error::Done),); + + // Stream 0 is still not writable. + let mut w = pipe.server.writable(); + assert_eq!(w.len(), 9); + assert!(w.find(|&s| s == 0).is_none()); + } } pub use crate::packet::ConnectionId; pub use crate::packet::Header; pub use crate::packet::Type; +pub use crate::path::PathEvent; +pub use crate::path::PathStats; +pub use crate::path::SocketAddrIter; + pub use crate::recovery::CongestionControlAlgorithm; pub use crate::stream::StreamIter; +mod cid; mod crypto; mod dgram; #[cfg(feature = "ffi")] @@ -11506,6 +15843,7 @@ mod frame; pub mod h3; mod minmax; mod packet; +mod path; mod rand; mod ranges; mod recovery; diff --git a/src/minmax.rs b/src/minmax.rs index 8d81c28..274d138 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -108,7 +108,7 @@ impl<T: PartialOrd + Copy> Minmax<T> { } /// Updates the max estimate based on the given measurement, and returns it. - pub fn _running_max(&mut self, win: Duration, time: Instant, meas: T) -> T { + pub fn running_max(&mut self, win: Duration, time: Instant, meas: T) -> T { let val = MinmaxSample { time, value: meas }; let delta_time = time.duration_since(self.estimate[2].time); @@ -269,13 +269,13 @@ mod tests { assert_eq!(rtt_max, rtt_24); time += Duration::from_millis(250); - rtt_max = f._running_max(win, time, rtt_25); + rtt_max = f.running_max(win, time, rtt_25); assert_eq!(rtt_max, rtt_25); assert_eq!(f.estimate[1].value, rtt_25); assert_eq!(f.estimate[2].value, rtt_25); time += Duration::from_millis(600); - rtt_max = f._running_max(win, time, rtt_24); + rtt_max = f.running_max(win, time, rtt_24); assert_eq!(rtt_max, rtt_24); assert_eq!(f.estimate[1].value, rtt_24); assert_eq!(f.estimate[2].value, rtt_24); @@ -293,13 +293,13 @@ mod tests { assert_eq!(bw_max, bw_200); time += Duration::from_millis(5000); - bw_max = f._running_max(win, time, bw_500); + bw_max = f.running_max(win, time, bw_500); assert_eq!(bw_max, bw_500); assert_eq!(f.estimate[1].value, bw_500); assert_eq!(f.estimate[2].value, bw_500); time += Duration::from_millis(600); - bw_max = f._running_max(win, time, bw_200); + bw_max = f.running_max(win, time, bw_200); assert_eq!(bw_max, bw_200); assert_eq!(f.estimate[1].value, bw_200); assert_eq!(f.estimate[2].value, bw_200); @@ -383,19 +383,19 @@ mod tests { assert_eq!(rtt_max, rtt_25); time += Duration::from_millis(300); - rtt_max = f._running_max(win, time, rtt_24); + rtt_max = f.running_max(win, time, rtt_24); assert_eq!(rtt_max, rtt_25); assert_eq!(f.estimate[1].value, rtt_24); assert_eq!(f.estimate[2].value, rtt_24); time += Duration::from_millis(300); - rtt_max = f._running_max(win, time, rtt_23); + rtt_max = f.running_max(win, time, rtt_23); assert_eq!(rtt_max, rtt_25); assert_eq!(f.estimate[1].value, rtt_24); assert_eq!(f.estimate[2].value, rtt_23); time += Duration::from_millis(300); - rtt_max = f._running_max(win, time, rtt_26); + rtt_max = f.running_max(win, time, rtt_26); assert_eq!(rtt_max, rtt_26); assert_eq!(f.estimate[1].value, rtt_26); assert_eq!(f.estimate[2].value, rtt_26); @@ -415,19 +415,19 @@ mod tests { assert_eq!(bw_max, bw_500); time += Duration::from_millis(300); - bw_max = f._running_max(win, time, bw_400); + bw_max = f.running_max(win, time, bw_400); assert_eq!(bw_max, bw_500); assert_eq!(f.estimate[1].value, bw_400); assert_eq!(f.estimate[2].value, bw_400); time += Duration::from_millis(300); - bw_max = f._running_max(win, time, bw_300); + bw_max = f.running_max(win, time, bw_300); assert_eq!(bw_max, bw_500); assert_eq!(f.estimate[1].value, bw_400); assert_eq!(f.estimate[2].value, bw_300); time += Duration::from_millis(300); - bw_max = f._running_max(win, time, bw_600); + bw_max = f.running_max(win, time, bw_600); assert_eq!(bw_max, bw_600); assert_eq!(f.estimate[1].value, bw_600); assert_eq!(f.estimate[2].value, bw_600); diff --git a/src/packet.rs b/src/packet.rs index cc06031..39194f0 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -24,6 +24,10 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::fmt::Display; +use std::ops::Index; +use std::ops::IndexMut; +use std::ops::RangeInclusive; use std::time; use ring::aead; @@ -49,20 +53,62 @@ pub const MAX_PKT_NUM_LEN: usize = 4; const SAMPLE_LEN: usize = 16; -pub const EPOCH_INITIAL: usize = 0; -pub const EPOCH_HANDSHAKE: usize = 1; -pub const EPOCH_APPLICATION: usize = 2; -pub const EPOCH_COUNT: usize = 3; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum Epoch { + Initial = 0, + Handshake = 1, + Application = 2, +} + +static EPOCHS: [Epoch; 3] = + [Epoch::Initial, Epoch::Handshake, Epoch::Application]; + +impl Epoch { + /// Returns an ordered slice containing the `Epoch`s that fit in the + /// provided `range`. + pub fn epochs(range: RangeInclusive<Epoch>) -> &'static [Epoch] { + &EPOCHS[*range.start() as usize..=*range.end() as usize] + } + + pub const fn count() -> usize { + 3 + } +} + +impl Display for Epoch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", usize::from(*self)) + } +} + +impl From<Epoch> for usize { + fn from(e: Epoch) -> Self { + e as usize + } +} + +impl<T> Index<Epoch> for [T] +where + T: Sized, +{ + type Output = T; -/// Packet number space epoch. -/// -/// This should only ever be one of `EPOCH_INITIAL`, `EPOCH_HANDSHAKE` or -/// `EPOCH_APPLICATION`, and can be used to index state specific to a packet -/// number space in `Connection` and `Recovery`. -pub type Epoch = usize; + fn index(&self, index: Epoch) -> &Self::Output { + self.index(usize::from(index)) + } +} + +impl<T> IndexMut<Epoch> for [T] +where + T: Sized, +{ + fn index_mut(&mut self, index: Epoch) -> &mut Self::Output { + self.index_mut(usize::from(index)) + } +} /// QUIC packet type. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Type { /// Initial packet. Initial, @@ -86,25 +132,23 @@ pub enum Type { impl Type { pub(crate) fn from_epoch(e: Epoch) -> Type { match e { - EPOCH_INITIAL => Type::Initial, - - EPOCH_HANDSHAKE => Type::Handshake, + Epoch::Initial => Type::Initial, - EPOCH_APPLICATION => Type::Short, + Epoch::Handshake => Type::Handshake, - _ => unreachable!(), + Epoch::Application => Type::Short, } } pub(crate) fn to_epoch(self) -> Result<Epoch> { match self { - Type::Initial => Ok(EPOCH_INITIAL), + Type::Initial => Ok(Epoch::Initial), - Type::ZeroRTT => Ok(EPOCH_APPLICATION), + Type::ZeroRTT => Ok(Epoch::Application), - Type::Handshake => Ok(EPOCH_HANDSHAKE), + Type::Handshake => Ok(Epoch::Handshake), - Type::Short => Ok(EPOCH_APPLICATION), + Type::Short => Ok(Epoch::Application), _ => Err(Error::InvalidPacket), } @@ -230,7 +274,7 @@ impl<'a> std::fmt::Debug for ConnectionId<'a> { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { for c in self.as_ref() { - write!(f, "{:02x}", c)?; + write!(f, "{c:02x}")?; } Ok(()) @@ -238,7 +282,7 @@ impl<'a> std::fmt::Debug for ConnectionId<'a> { } /// A QUIC packet's header. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct Header<'a> { /// The type of the packet. pub ty: Type, @@ -495,12 +539,12 @@ impl<'a> std::fmt::Debug for Header<'a> { if let Some(ref token) = self.token { write!(f, " token=")?; for b in token { - write!(f, "{:02x}", b)?; + write!(f, "{b:02x}")?; } } if let Some(ref versions) = self.versions { - write!(f, " versions={:x?}", versions)?; + write!(f, " versions={versions:x?}")?; } if self.ty == Type::Short { @@ -512,11 +556,13 @@ impl<'a> std::fmt::Debug for Header<'a> { } pub fn pkt_num_len(pn: u64) -> Result<usize> { - let len = if pn < u64::from(std::u8::MAX) { + let len = if pn < u64::from(u8::MAX) { 1 - } else if pn < u64::from(std::u16::MAX) { + } else if pn < u64::from(u16::MAX) { 2 - } else if pn < u64::from(std::u32::MAX) { + } else if pn < 16_777_215u64 { + 3 + } else if pn < u64::from(u32::MAX) { 4 } else { return Err(Error::InvalidPacket); @@ -625,7 +671,8 @@ pub fn decrypt_pkt<'a>( pub fn encrypt_hdr( b: &mut octets::OctetsMut, pn_len: usize, payload: &[u8], aead: &crypto::Seal, ) -> Result<()> { - let sample = &payload[4 - pn_len..16 + (4 - pn_len)]; + let sample = &payload + [MAX_PKT_NUM_LEN - pn_len..SAMPLE_LEN + (MAX_PKT_NUM_LEN - pn_len)]; let mask = aead.new_mask(sample)?; @@ -814,11 +861,29 @@ fn compute_retry_integrity_tag( .map_err(|_| Error::CryptoFail) } +pub struct KeyUpdate { + /// 1-RTT key used prior to a key update. + pub crypto_open: crypto::Open, + + /// The packet number triggered the latest key-update. + /// + /// Incoming packets with lower pn should use this (prev) crypto key. + pub pn_on_update: u64, + + /// Whether ACK frame for key-update has been sent. + pub update_acked: bool, + + /// When the old key should be discarded. + pub timer: time::Instant, +} + pub struct PktNumSpace { pub largest_rx_pkt_num: u64, pub largest_rx_pkt_time: time::Instant, + pub largest_rx_non_probing_pkt_num: u64, + pub next_pkt_num: u64, pub recv_pkt_need_ack: ranges::RangeSet, @@ -827,6 +892,8 @@ pub struct PktNumSpace { pub ack_elicited: bool, + pub key_update: Option<KeyUpdate>, + pub crypto_open: Option<crypto::Open>, pub crypto_seal: Option<crypto::Seal>, @@ -843,6 +910,8 @@ impl PktNumSpace { largest_rx_pkt_time: time::Instant::now(), + largest_rx_non_probing_pkt_num: 0, + next_pkt_num: 0, recv_pkt_need_ack: ranges::RangeSet::new(crate::MAX_ACK_RANGES), @@ -851,6 +920,8 @@ impl PktNumSpace { ack_elicited: false, + key_update: None, + crypto_open: None, crypto_seal: None, @@ -858,8 +929,8 @@ impl PktNumSpace { crypto_0rtt_seal: None, crypto_stream: stream::Stream::new( - std::u64::MAX, - std::u64::MAX, + u64::MAX, + u64::MAX, true, true, stream::MAX_STREAM_WINDOW, @@ -869,8 +940,8 @@ impl PktNumSpace { pub fn clear(&mut self) { self.crypto_stream = stream::Stream::new( - std::u64::MAX, - std::u64::MAX, + u64::MAX, + u64::MAX, true, true, stream::MAX_STREAM_WINDOW, @@ -966,7 +1037,7 @@ mod tests { assert!(hdr.to_bytes(&mut b).is_ok()); // Add fake retry integrity token. - b.put_bytes(&vec![0xba; 16]).unwrap(); + b.put_bytes(&[0xba; 16]).unwrap(); let mut b = octets::OctetsMut::with_slice(&mut d); assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr); @@ -1213,7 +1284,7 @@ mod tests { assert!(!win.contains(1025)); assert!(!win.contains(1026)); - win.insert(std::u64::MAX - 1); + win.insert(u64::MAX - 1); assert!(win.contains(0)); assert!(win.contains(1)); assert!(win.contains(2)); @@ -1235,8 +1306,8 @@ mod tests { assert!(win.contains(1024)); assert!(win.contains(1025)); assert!(win.contains(1026)); - assert!(!win.contains(std::u64::MAX - 2)); - assert!(win.contains(std::u64::MAX - 1)); + assert!(!win.contains(u64::MAX - 2)); + assert!(win.contains(u64::MAX - 1)); } fn assert_decrypt_initial_pkt( @@ -1875,7 +1946,7 @@ mod tests { .unwrap(); assert_eq!(written, expected_pkt.len()); - assert_eq!(&out[..written], &expected_pkt[..]); + assert_eq!(&out[..written], expected_pkt); } #[test] diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..ea59659 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,1069 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::time; + +use std::collections::BTreeMap; +use std::collections::VecDeque; +use std::net::SocketAddr; + +use slab::Slab; + +use crate::Error; +use crate::Result; + +use crate::recovery; +use crate::recovery::HandshakeStatus; + +/// The different states of the path validation. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PathState { + /// The path failed its validation. + Failed, + + /// The path exists, but no path validation has been performed. + Unknown, + + /// The path is under validation. + Validating, + + /// The remote address has been validated, but not the path MTU. + ValidatingMTU, + + /// The path has been validated. + Validated, +} + +impl PathState { + #[cfg(feature = "ffi")] + pub fn to_c(self) -> libc::ssize_t { + match self { + PathState::Failed => -1, + PathState::Unknown => 0, + PathState::Validating => 1, + PathState::ValidatingMTU => 2, + PathState::Validated => 3, + } + } +} + +/// A path-specific event. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PathEvent { + /// A new network path (local address, peer address) has been seen on a + /// received packet. Note that this event is only triggered for servers, as + /// the client is responsible from initiating new paths. The application may + /// then probe this new path, if desired. + New(SocketAddr, SocketAddr), + + /// The related network path between local `SocketAddr` and peer + /// `SocketAddr` has been validated. + Validated(SocketAddr, SocketAddr), + + /// The related network path between local `SocketAddr` and peer + /// `SocketAddr` failed to be validated. This network path will not be used + /// anymore, unless the application requests probing this path again. + FailedValidation(SocketAddr, SocketAddr), + + /// The related network path between local `SocketAddr` and peer + /// `SocketAddr` has been closed and is now unusable on this connection. + Closed(SocketAddr, SocketAddr), + + /// The stack observes that the Source Connection ID with the given sequence + /// number, initially used by the peer over the first pair of `SocketAddr`s, + /// is now reused over the second pair of `SocketAddr`s. + ReusedSourceConnectionId( + u64, + (SocketAddr, SocketAddr), + (SocketAddr, SocketAddr), + ), + + /// The connection observed that the peer migrated over the network path + /// denoted by the pair of `SocketAddr`, i.e., non-probing packets have been + /// received on this network path. This is a server side only event. + /// + /// Note that this event is only raised if the path has been validated. + PeerMigrated(SocketAddr, SocketAddr), +} + +/// A network path on which QUIC packets can be sent. +#[derive(Debug)] +pub struct Path { + /// The local address. + local_addr: SocketAddr, + + /// The remote address. + peer_addr: SocketAddr, + + /// Source CID sequence number used over that path. + pub active_scid_seq: Option<u64>, + + /// Destination CID sequence number used over that path. + pub active_dcid_seq: Option<u64>, + + /// The current validation state of the path. + state: PathState, + + /// Is this path used to send non-probing packets. + active: bool, + + /// Loss recovery and congestion control state. + pub recovery: recovery::Recovery, + + /// Pending challenge data with the size of the packet containing them and + /// when they were sent. + in_flight_challenges: VecDeque<([u8; 8], usize, time::Instant)>, + + /// The maximum challenge size that got acknowledged. + max_challenge_size: usize, + + /// Number of consecutive (spaced by at least 1 RTT) probing packets lost. + probing_lost: usize, + + /// Last instant when a probing packet got lost. + last_probe_lost_time: Option<time::Instant>, + + /// Received challenge data. + received_challenges: VecDeque<[u8; 8]>, + + /// Number of packets sent on this path. + pub sent_count: usize, + + /// Number of packets received on this path. + pub recv_count: usize, + + /// Total number of packets sent with data retransmitted from this path. + pub retrans_count: usize, + + /// Total number of sent bytes over this path. + pub sent_bytes: u64, + + /// Total number of bytes received over this path. + pub recv_bytes: u64, + + /// Total number of bytes retransmitted from this path. + /// This counts only STREAM and CRYPTO data. + pub stream_retrans_bytes: u64, + + /// Total number of bytes the server can send before the peer's address + /// is verified. + pub max_send_bytes: usize, + + /// Whether the peer's address has been verified. + pub verified_peer_address: bool, + + /// Whether the peer has verified our address. + pub peer_verified_local_address: bool, + + /// Does it requires sending PATH_CHALLENGE? + challenge_requested: bool, + + /// Whether the failure of this path was notified. + failure_notified: bool, + + /// Whether the connection tries to migrate to this path, but it still needs + /// to be validated. + migrating: bool, + + /// Whether or not we should force eliciting of an ACK (e.g. via PING frame) + pub needs_ack_eliciting: bool, +} + +impl Path { + /// Create a new Path instance with the provided addresses, the remaining of + /// the fields being set to their default value. + pub fn new( + local_addr: SocketAddr, peer_addr: SocketAddr, + recovery_config: &recovery::RecoveryConfig, is_initial: bool, + ) -> Self { + let (state, active_scid_seq, active_dcid_seq) = if is_initial { + (PathState::Validated, Some(0), Some(0)) + } else { + (PathState::Unknown, None, None) + }; + + Self { + local_addr, + peer_addr, + active_scid_seq, + active_dcid_seq, + state, + active: false, + recovery: recovery::Recovery::new_with_config(recovery_config), + in_flight_challenges: VecDeque::new(), + max_challenge_size: 0, + probing_lost: 0, + last_probe_lost_time: None, + received_challenges: VecDeque::new(), + sent_count: 0, + recv_count: 0, + retrans_count: 0, + sent_bytes: 0, + recv_bytes: 0, + stream_retrans_bytes: 0, + max_send_bytes: 0, + verified_peer_address: false, + peer_verified_local_address: false, + challenge_requested: false, + failure_notified: false, + migrating: false, + needs_ack_eliciting: false, + } + } + + /// Returns the local address on which this path operates. + #[inline] + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } + + /// Returns the peer address on which this path operates. + #[inline] + pub fn peer_addr(&self) -> SocketAddr { + self.peer_addr + } + + /// Returns whether the path is working (i.e., not failed). + #[inline] + fn working(&self) -> bool { + self.state > PathState::Failed + } + + /// Returns whether the path is active. + #[inline] + pub fn active(&self) -> bool { + self.active && self.working() && self.active_dcid_seq.is_some() + } + + /// Returns whether the path can be used to send non-probing packets. + #[inline] + pub fn usable(&self) -> bool { + self.active() || + (self.state == PathState::Validated && + self.active_dcid_seq.is_some()) + } + + /// Returns whether the path is unused. + #[inline] + fn unused(&self) -> bool { + // FIXME: we should check that there is nothing in the sent queue. + !self.active() && self.active_dcid_seq.is_none() + } + + /// Returns whether the path requires sending a probing packet. + #[inline] + pub fn probing_required(&self) -> bool { + !self.received_challenges.is_empty() || self.validation_requested() + } + + /// Promotes the path to the provided state only if the new state is greater + /// than the current one. + fn promote_to(&mut self, state: PathState) { + if self.state < state { + self.state = state; + } + } + + /// Returns whether the path is validated. + #[inline] + pub fn validated(&self) -> bool { + self.state == PathState::Validated + } + + /// Returns whether this path failed its validation. + #[inline] + fn validation_failed(&self) -> bool { + self.state == PathState::Failed + } + + // Returns whether this path is under path validation process. + #[inline] + pub fn under_validation(&self) -> bool { + matches!(self.state, PathState::Validating | PathState::ValidatingMTU) + } + + /// Requests path validation. + #[inline] + pub fn request_validation(&mut self) { + self.challenge_requested = true; + } + + /// Returns whether a validation is requested. + #[inline] + pub fn validation_requested(&self) -> bool { + self.challenge_requested + } + + pub fn on_challenge_sent(&mut self) { + self.promote_to(PathState::Validating); + self.challenge_requested = false; + } + + /// Handles the sending of PATH_CHALLENGE. + pub fn add_challenge_sent( + &mut self, data: [u8; 8], pkt_size: usize, sent_time: time::Instant, + ) { + self.on_challenge_sent(); + self.in_flight_challenges + .push_back((data, pkt_size, sent_time)); + } + + pub fn on_challenge_received(&mut self, data: [u8; 8]) { + self.received_challenges.push_back(data); + self.peer_verified_local_address = true; + } + + pub fn has_pending_challenge(&self, data: [u8; 8]) -> bool { + self.in_flight_challenges.iter().any(|(d, ..)| *d == data) + } + + /// Returns whether the path is now validated. + pub fn on_response_received(&mut self, data: [u8; 8]) -> bool { + self.verified_peer_address = true; + self.probing_lost = 0; + + let mut challenge_size = 0; + self.in_flight_challenges.retain(|(d, s, _)| { + if *d == data { + challenge_size = *s; + false + } else { + true + } + }); + + // The 4-tuple is reachable, but we didn't check Path MTU yet. + self.promote_to(PathState::ValidatingMTU); + + self.max_challenge_size = + std::cmp::max(self.max_challenge_size, challenge_size); + + if self.state == PathState::ValidatingMTU { + if self.max_challenge_size >= crate::MIN_CLIENT_INITIAL_LEN { + // Path MTU is sufficient for QUIC traffic. + self.promote_to(PathState::Validated); + return true; + } + + // If the MTU was not validated, probe again. + self.request_validation(); + } + + false + } + + fn on_failed_validation(&mut self) { + self.state = PathState::Failed; + self.active = false; + } + + #[inline] + pub fn pop_received_challenge(&mut self) -> Option<[u8; 8]> { + self.received_challenges.pop_front() + } + + pub fn on_loss_detection_timeout( + &mut self, handshake_status: HandshakeStatus, now: time::Instant, + is_server: bool, trace_id: &str, + ) -> (usize, usize) { + let (lost_packets, lost_bytes) = self.recovery.on_loss_detection_timeout( + handshake_status, + now, + trace_id, + ); + + let mut lost_probe_time = None; + self.in_flight_challenges.retain(|(_, _, sent_time)| { + if *sent_time <= now { + if lost_probe_time.is_none() { + lost_probe_time = Some(*sent_time); + } + false + } else { + true + } + }); + + // If we lost probing packets, check if the path failed + // validation. + if let Some(lost_probe_time) = lost_probe_time { + self.last_probe_lost_time = match self.last_probe_lost_time { + Some(last) => { + // Count a loss if at least 1-RTT happened. + if lost_probe_time - last >= self.recovery.rtt() { + self.probing_lost += 1; + Some(lost_probe_time) + } else { + Some(last) + } + }, + None => { + self.probing_lost += 1; + Some(lost_probe_time) + }, + }; + // As a server, if requesting a challenge is not + // possible due to the amplification attack, declare the + // validation as failed. + if self.probing_lost >= crate::MAX_PROBING_TIMEOUTS || + (is_server && self.max_send_bytes < crate::MIN_PROBING_SIZE) + { + self.on_failed_validation(); + } else { + self.request_validation(); + } + } + + (lost_packets, lost_bytes) + } + + pub fn stats(&self) -> PathStats { + PathStats { + local_addr: self.local_addr, + peer_addr: self.peer_addr, + validation_state: self.state, + active: self.active, + recv: self.recv_count, + sent: self.sent_count, + lost: self.recovery.lost_count, + retrans: self.retrans_count, + rtt: self.recovery.rtt(), + min_rtt: self.recovery.min_rtt(), + rttvar: self.recovery.rttvar(), + cwnd: self.recovery.cwnd(), + sent_bytes: self.sent_bytes, + recv_bytes: self.recv_bytes, + lost_bytes: self.recovery.bytes_lost, + stream_retrans_bytes: self.stream_retrans_bytes, + pmtu: self.recovery.max_datagram_size(), + delivery_rate: self.recovery.delivery_rate(), + } + } +} + +/// An iterator over SocketAddr. +#[derive(Default)] +pub struct SocketAddrIter { + pub(crate) sockaddrs: Vec<SocketAddr>, +} + +impl Iterator for SocketAddrIter { + type Item = SocketAddr; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + self.sockaddrs.pop() + } +} + +impl ExactSizeIterator for SocketAddrIter { + #[inline] + fn len(&self) -> usize { + self.sockaddrs.len() + } +} + +/// All path-related information. +pub struct PathMap { + /// The paths of the connection. Each of them has an internal identifier + /// that is used by `addrs_to_paths` and `ConnectionEntry`. + paths: Slab<Path>, + + /// The maximum number of concurrent paths allowed. + max_concurrent_paths: usize, + + /// The mapping from the (local `SocketAddr`, peer `SocketAddr`) to the + /// `Path` structure identifier. + addrs_to_paths: BTreeMap<(SocketAddr, SocketAddr), usize>, + + /// Path-specific events to be notified to the application. + events: VecDeque<PathEvent>, + + /// Whether this manager serves a connection as a server. + is_server: bool, +} + +impl PathMap { + /// Creates a new `PathMap` with the initial provided `path` and a + /// capacity limit. + pub fn new( + mut initial_path: Path, max_concurrent_paths: usize, is_server: bool, + ) -> Self { + let mut paths = Slab::with_capacity(1); // most connections only have one path + let mut addrs_to_paths = BTreeMap::new(); + + let local_addr = initial_path.local_addr; + let peer_addr = initial_path.peer_addr; + + // As it is the first path, it is active by default. + initial_path.active = true; + + let active_path_id = paths.insert(initial_path); + addrs_to_paths.insert((local_addr, peer_addr), active_path_id); + + Self { + paths, + max_concurrent_paths, + addrs_to_paths, + events: VecDeque::new(), + is_server, + } + } + + /// Gets an immutable reference to the path identified by `path_id`. If the + /// provided `path_id` does not identify any current `Path`, returns an + /// [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + #[inline] + pub fn get(&self, path_id: usize) -> Result<&Path> { + self.paths.get(path_id).ok_or(Error::InvalidState) + } + + /// Gets a mutable reference to the path identified by `path_id`. If the + /// provided `path_id` does not identify any current `Path`, returns an + /// [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + #[inline] + pub fn get_mut(&mut self, path_id: usize) -> Result<&mut Path> { + self.paths.get_mut(path_id).ok_or(Error::InvalidState) + } + + #[inline] + /// Gets an immutable reference to the active path with the value of the + /// lowest identifier. If there is no active path, returns `None`. + pub fn get_active_with_pid(&self) -> Option<(usize, &Path)> { + self.paths.iter().find(|(_, p)| p.active()) + } + + /// Gets an immutable reference to the active path with the lowest + /// identifier. If there is no active path, returns an [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + #[inline] + pub fn get_active(&self) -> Result<&Path> { + self.get_active_with_pid() + .map(|(_, p)| p) + .ok_or(Error::InvalidState) + } + + /// Gets the lowest active path identifier. If there is no active path, + /// returns an [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + #[inline] + pub fn get_active_path_id(&self) -> Result<usize> { + self.get_active_with_pid() + .map(|(pid, _)| pid) + .ok_or(Error::InvalidState) + } + + /// Gets an mutable reference to the active path with the lowest identifier. + /// If there is no active path, returns an [`InvalidState`]. + /// + /// [`InvalidState`]: enum.Error.html#variant.InvalidState + #[inline] + pub fn get_active_mut(&mut self) -> Result<&mut Path> { + self.paths + .iter_mut() + .map(|(_, p)| p) + .find(|p| p.active()) + .ok_or(Error::InvalidState) + } + + /// Returns an iterator over all existing paths. + #[inline] + pub fn iter(&self) -> slab::Iter<Path> { + self.paths.iter() + } + + /// Returns a mutable iterator over all existing paths. + #[inline] + pub fn iter_mut(&mut self) -> slab::IterMut<Path> { + self.paths.iter_mut() + } + + /// Returns the number of existing paths. + #[inline] + pub fn len(&self) -> usize { + self.paths.len() + } + + /// Returns the `Path` identifier related to the provided `addrs`. + #[inline] + pub fn path_id_from_addrs( + &self, addrs: &(SocketAddr, SocketAddr), + ) -> Option<usize> { + self.addrs_to_paths.get(addrs).copied() + } + + /// Checks if creating a new path will not exceed the current `self.paths` + /// capacity. If yes, this method tries to remove one unused path. If it + /// fails to do so, returns [`Done`]. + /// + /// [`Done`]: enum.Error.html#variant.Done + fn make_room_for_new_path(&mut self) -> Result<()> { + if self.paths.len() < self.max_concurrent_paths { + return Ok(()); + } + + let (pid_to_remove, _) = self + .paths + .iter() + .find(|(_, p)| p.unused()) + .ok_or(Error::Done)?; + + let path = self.paths.remove(pid_to_remove); + self.addrs_to_paths + .remove(&(path.local_addr, path.peer_addr)); + + self.notify_event(PathEvent::Closed(path.local_addr, path.peer_addr)); + + Ok(()) + } + + /// Records the provided `Path` and returns its assigned identifier. + /// + /// On success, this method takes care of creating a notification to the + /// serving application, if it serves a server-side connection. + /// + /// If there are already `max_concurrent_paths` currently recorded, this + /// method tries to remove an unused `Path` first. If it fails to do so, + /// it returns [`Done`]. + /// + /// [`Done`]: enum.Error.html#variant.Done + pub fn insert_path(&mut self, path: Path, is_server: bool) -> Result<usize> { + self.make_room_for_new_path()?; + + let local_addr = path.local_addr; + let peer_addr = path.peer_addr; + + let pid = self.paths.insert(path); + self.addrs_to_paths.insert((local_addr, peer_addr), pid); + + // Notifies the application if we are in server mode. + if is_server { + self.notify_event(PathEvent::New(local_addr, peer_addr)); + } + + Ok(pid) + } + + /// Notifies a path event to the application served by the connection. + pub fn notify_event(&mut self, ev: PathEvent) { + self.events.push_back(ev); + } + + /// Gets the first path event to be notified to the application. + pub fn pop_event(&mut self) -> Option<PathEvent> { + self.events.pop_front() + } + + /// Notifies all failed validations to the application. + pub fn notify_failed_validations(&mut self) { + let validation_failed = self + .paths + .iter_mut() + .filter(|(_, p)| p.validation_failed() && !p.failure_notified); + + for (_, p) in validation_failed { + self.events.push_back(PathEvent::FailedValidation( + p.local_addr, + p.peer_addr, + )); + + p.failure_notified = true; + } + } + + /// Finds a path candidate to be active and returns its identifier. + pub fn find_candidate_path(&self) -> Option<usize> { + // TODO: also consider unvalidated paths if there are no more validated. + self.paths + .iter() + .find(|(_, p)| p.usable()) + .map(|(pid, _)| pid) + } + + /// Handles incoming PATH_RESPONSE data. + pub fn on_response_received(&mut self, data: [u8; 8]) -> Result<()> { + let active_pid = self.get_active_path_id()?; + + let challenge_pending = + self.iter_mut().find(|(_, p)| p.has_pending_challenge(data)); + + if let Some((pid, p)) = challenge_pending { + if p.on_response_received(data) { + let local_addr = p.local_addr; + let peer_addr = p.peer_addr; + let was_migrating = p.migrating; + + p.migrating = false; + + // Notifies the application. + self.notify_event(PathEvent::Validated(local_addr, peer_addr)); + + // If this path was the candidate for migration, notifies the + // application. + if pid == active_pid && was_migrating { + self.notify_event(PathEvent::PeerMigrated( + local_addr, peer_addr, + )); + } + } + } + Ok(()) + } + + /// Sets the path with identifier 'path_id' to be active. + /// + /// There can be exactly one active path on which non-probing packets can be + /// sent. If another path is marked as active, it will be superseded by the + /// one having `path_id` as identifier. + /// + /// A server should always ensure that the active path is validated. If it + /// is already the case, it notifies the application that the connection + /// migrated. Otherwise, it triggers a path validation and defers the + /// notification once it is actually validated. + pub fn set_active_path(&mut self, path_id: usize) -> Result<()> { + let is_server = self.is_server; + + if let Ok(old_active_path) = self.get_active_mut() { + old_active_path.active = false; + } + + let new_active_path = self.get_mut(path_id)?; + new_active_path.active = true; + + if is_server { + if new_active_path.validated() { + let local_addr = new_active_path.local_addr(); + let peer_addr = new_active_path.peer_addr(); + + self.notify_event(PathEvent::PeerMigrated(local_addr, peer_addr)); + } else { + new_active_path.migrating = true; + + // Requests path validation if needed. + if !new_active_path.under_validation() { + new_active_path.request_validation(); + } + } + } + + Ok(()) + } + + /// Handles potential connection migration. + pub fn on_peer_migrated( + &mut self, new_pid: usize, disable_dcid_reuse: bool, + ) -> Result<()> { + let active_path_id = self.get_active_path_id()?; + + if active_path_id == new_pid { + return Ok(()); + } + + self.set_active_path(new_pid)?; + + let no_spare_dcid = self.get_mut(new_pid)?.active_dcid_seq.is_none(); + + if no_spare_dcid && !disable_dcid_reuse { + self.get_mut(new_pid)?.active_dcid_seq = + self.get_mut(active_path_id)?.active_dcid_seq; + } + + Ok(()) + } +} + +/// Statistics about the path of a connection. +/// +/// It is part of the `Stats` structure returned by the [`stats()`] method. +/// +/// [`stats()`]: struct.Connection.html#method.stats +#[derive(Clone)] +pub struct PathStats { + /// The local address of the path. + pub local_addr: SocketAddr, + + /// The peer address of the path. + pub peer_addr: SocketAddr, + + /// The path validation state. + pub validation_state: PathState, + + /// Whether the path is marked as active. + pub active: bool, + + /// The number of QUIC packets received. + pub recv: usize, + + /// The number of QUIC packets sent. + pub sent: usize, + + /// The number of QUIC packets that were lost. + pub lost: usize, + + /// The number of sent QUIC packets with retransmitted data. + pub retrans: usize, + + /// The estimated round-trip time of the connection. + pub rtt: time::Duration, + + /// The minimum round-trip time observed. + pub min_rtt: Option<time::Duration>, + + /// The estimated round-trip time variation in samples using a mean + /// variation. + pub rttvar: time::Duration, + + /// The size of the connection's congestion window in bytes. + pub cwnd: usize, + + /// The number of sent bytes. + pub sent_bytes: u64, + + /// The number of received bytes. + pub recv_bytes: u64, + + /// The number of bytes lost. + pub lost_bytes: u64, + + /// The number of stream bytes retransmitted. + pub stream_retrans_bytes: u64, + + /// The current PMTU for the connection. + pub pmtu: usize, + + /// The most recent data delivery rate estimate in bytes/s. + /// + /// Note that this value could be inaccurate if the application does not + /// respect pacing hints (see [`SendInfo.at`] and [Pacing] for more + /// details). + /// + /// [`SendInfo.at`]: struct.SendInfo.html#structfield.at + /// [Pacing]: index.html#pacing + pub delivery_rate: u64, +} + +impl std::fmt::Debug for PathStats { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "local_addr={:?} peer_addr={:?} ", + self.local_addr, self.peer_addr, + )?; + write!( + f, + "validation_state={:?} active={} ", + self.validation_state, self.active, + )?; + write!( + f, + "recv={} sent={} lost={} retrans={} rtt={:?} min_rtt={:?} rttvar={:?} cwnd={}", + self.recv, self.sent, self.lost, self.retrans, self.rtt, self.min_rtt, self.rttvar, self.cwnd, + )?; + + write!( + f, + " sent_bytes={} recv_bytes={} lost_bytes={}", + self.sent_bytes, self.recv_bytes, self.lost_bytes, + )?; + + write!( + f, + " stream_retrans_bytes={} pmtu={} delivery_rate={}", + self.stream_retrans_bytes, self.pmtu, self.delivery_rate, + ) + } +} + +#[cfg(test)] +mod tests { + use crate::rand; + use crate::MIN_CLIENT_INITIAL_LEN; + + use crate::recovery::RecoveryConfig; + use crate::Config; + + use super::*; + + #[test] + fn path_validation_limited_mtu() { + let client_addr = "127.0.0.1:1234".parse().unwrap(); + let client_addr_2 = "127.0.0.1:5678".parse().unwrap(); + let server_addr = "127.0.0.1:4321".parse().unwrap(); + + let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + let recovery_config = RecoveryConfig::from_config(&config); + + let path = Path::new(client_addr, server_addr, &recovery_config, true); + let mut path_mgr = PathMap::new(path, 2, false); + + let probed_path = + Path::new(client_addr_2, server_addr, &recovery_config, false); + path_mgr.insert_path(probed_path, false).unwrap(); + + let pid = path_mgr + .path_id_from_addrs(&(client_addr_2, server_addr)) + .unwrap(); + path_mgr.get_mut(pid).unwrap().request_validation(); + assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), true); + + // Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN - 1 + // bytes. + let data = rand::rand_u64().to_be_bytes(); + path_mgr.get_mut(pid).unwrap().add_challenge_sent( + data, + MIN_CLIENT_INITIAL_LEN - 1, + time::Instant::now(), + ); + + assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validating); + assert_eq!(path_mgr.pop_event(), None); + + // Receives the response. The path is reachable, but the MTU is not + // validated yet. + path_mgr.on_response_received(data).unwrap(); + + assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), false); + assert_eq!( + path_mgr.get_mut(pid).unwrap().state, + PathState::ValidatingMTU + ); + assert_eq!(path_mgr.pop_event(), None); + + // Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN + // bytes. + let data = rand::rand_u64().to_be_bytes(); + path_mgr.get_mut(pid).unwrap().add_challenge_sent( + data, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + + path_mgr.on_response_received(data).unwrap(); + + assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), false); + assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), true); + assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validated); + assert_eq!( + path_mgr.pop_event(), + Some(PathEvent::Validated(client_addr_2, server_addr)) + ); + } + + #[test] + fn multiple_probes() { + let client_addr = "127.0.0.1:1234".parse().unwrap(); + let server_addr = "127.0.0.1:4321".parse().unwrap(); + + let config = Config::new(crate::PROTOCOL_VERSION).unwrap(); + let recovery_config = RecoveryConfig::from_config(&config); + + let path = Path::new(client_addr, server_addr, &recovery_config, true); + let mut client_path_mgr = PathMap::new(path, 2, false); + let mut server_path = + Path::new(server_addr, client_addr, &recovery_config, false); + + let client_pid = client_path_mgr + .path_id_from_addrs(&(client_addr, server_addr)) + .unwrap(); + + // First probe. + let data = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + + // Second probe. + let data_2 = rand::rand_u64().to_be_bytes(); + + client_path_mgr + .get_mut(client_pid) + .unwrap() + .add_challenge_sent( + data_2, + MIN_CLIENT_INITIAL_LEN, + time::Instant::now(), + ); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 2 + ); + + // If we receive multiple challenges, we can store them. + server_path.on_challenge_received(data); + assert_eq!(server_path.received_challenges.len(), 1); + server_path.on_challenge_received(data_2); + assert_eq!(server_path.received_challenges.len(), 2); + + // Response for first probe. + client_path_mgr.on_response_received(data).unwrap(); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 1 + ); + + // Response for second probe. + client_path_mgr.on_response_received(data_2).unwrap(); + assert_eq!( + client_path_mgr + .get(client_pid) + .unwrap() + .in_flight_challenges + .len(), + 0 + ); + } +} diff --git a/src/ranges.rs b/src/ranges.rs index b10eb35..91ddb90 100644 --- a/src/ranges.rs +++ b/src/ranges.rs @@ -30,7 +30,7 @@ use std::collections::btree_map; use std::collections::BTreeMap; use std::collections::Bound; -#[derive(Clone, PartialEq, PartialOrd)] +#[derive(Clone, PartialEq, Eq, PartialOrd)] pub struct RangeSet { inner: BTreeMap<u64, u64>, @@ -82,9 +82,7 @@ impl RangeSet { } if self.inner.len() >= self.capacity { - if let Some(first) = self.inner.keys().next().copied() { - self.inner.remove(&first); - } + self.inner.pop_first(); } self.inner.insert(start, end); @@ -154,7 +152,7 @@ impl RangeSet { impl Default for RangeSet { fn default() -> Self { - Self::new(std::usize::MAX) + Self::new(usize::MAX) } } @@ -190,7 +188,7 @@ impl std::fmt::Debug for RangeSet { }) .collect(); - write!(f, "{:?}", ranges) + write!(f, "{ranges:?}") } } diff --git a/src/recovery/bbr/init.rs b/src/recovery/bbr/init.rs new file mode 100644 index 0000000..6a3f7ba --- /dev/null +++ b/src/recovery/bbr/init.rs @@ -0,0 +1,93 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use super::*; +use crate::recovery::Recovery; + +use std::time::Duration; +use std::time::Instant; + +// BBR Functions at Initialization. +// + +// 4.3.1. Initialization Steps +pub fn bbr_init(r: &mut Recovery) { + let rtt = r.rtt(); + let bbr = &mut r.bbr_state; + + bbr.rtprop = rtt; + bbr.rtprop_stamp = Instant::now(); + bbr.next_round_delivered = r.delivery_rate.delivered(); + + r.send_quantum = r.max_datagram_size; + + bbr_init_round_counting(r); + bbr_init_full_pipe(r); + bbr_init_pacing_rate(r); + bbr_enter_startup(r); +} + +// 4.1.1.3. Tracking Time for the BBR.BtlBw Max Filter +fn bbr_init_round_counting(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + bbr.next_round_delivered = 0; + bbr.round_start = false; + bbr.round_count = 0; +} + +// 4.2.1. Pacing Rate +fn bbr_init_pacing_rate(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + let srtt = r + .smoothed_rtt + .unwrap_or_else(|| Duration::from_millis(1)) + .as_secs_f64(); + + // At init, cwnd is initcwnd. + let nominal_bandwidth = r.congestion_window as f64 / srtt; + + bbr.pacing_rate = (bbr.pacing_gain * nominal_bandwidth) as u64; +} + +// 4.3.2.1. Startup Dynamics +pub fn bbr_enter_startup(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + bbr.state = BBRStateMachine::Startup; + bbr.pacing_gain = BBR_HIGH_GAIN; + bbr.cwnd_gain = BBR_HIGH_GAIN; +} + +// 4.3.2.2. Estimating When Startup has Filled the Pipe +fn bbr_init_full_pipe(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + bbr.filled_pipe = false; + bbr.full_bw = 0; + bbr.full_bw_count = 0; +} diff --git a/src/recovery/bbr/mod.rs b/src/recovery/bbr/mod.rs new file mode 100644 index 0000000..742cfc7 --- /dev/null +++ b/src/recovery/bbr/mod.rs @@ -0,0 +1,840 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! BBR Congestion Control +//! +//! This implementation is based on the following draft: +//! <https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00> + +use crate::minmax::Minmax; +use crate::packet; +use crate::recovery::*; + +use std::time::Duration; +use std::time::Instant; + +pub static BBR: CongestionControlOps = CongestionControlOps { + on_init, + reset, + on_packet_sent, + on_packets_acked, + congestion_event, + collapse_cwnd, + checkpoint, + rollback, + has_custom_pacing, + debug_fmt, +}; + +/// A constant specifying the length of the BBR.BtlBw max filter window for +/// BBR.BtlBwFilter, BtlBwFilterLen is 10 packet-timed round trips. +const BTLBW_FILTER_LEN: Duration = Duration::from_secs(10); + +/// A constant specifying the minimum time interval between ProbeRTT states: 10 +/// secs. +const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(10); + +/// A constant specifying the length of the RTProp min filter window. +const RTPROP_FILTER_LEN: Duration = PROBE_RTT_INTERVAL; + +/// A constant specifying the minimum gain value that will allow the sending +/// rate to double each round (2/ln(2) ~= 2.89), used in Startup mode for both +/// BBR.pacing_gain and BBR.cwnd_gain. +const BBR_HIGH_GAIN: f64 = 2.89; + +/// The minimal cwnd value BBR tries to target using: 4 packets, or 4 * SMSS +const BBR_MIN_PIPE_CWND_PKTS: usize = 4; + +/// The number of phases in the BBR ProbeBW gain cycle: 8. +const BBR_GAIN_CYCLE_LEN: usize = 8; + +/// A constant specifying the minimum duration for which ProbeRTT state holds +/// inflight to BBRMinPipeCwnd or fewer packets: 200 ms. +const PROBE_RTT_DURATION: Duration = Duration::from_millis(200); + +/// Pacing Gain Cycle. +const PACING_GAIN_CYCLE: [f64; BBR_GAIN_CYCLE_LEN] = + [5.0 / 4.0, 3.0 / 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; + +/// A constant to check BBR.BtlBW is still growing. +const BTLBW_GROWTH_TARGET: f64 = 1.25; + +/// BBR Internal State Machine. +#[derive(Debug, PartialEq, Eq)] +enum BBRStateMachine { + Startup, + Drain, + ProbeBW, + ProbeRTT, +} + +/// BBR Specific State Variables. +pub struct State { + // The current state of a BBR flow in the BBR state machine. + state: BBRStateMachine, + + // The current pacing rate for a BBR flow, which controls inter-packet + // spacing. + pacing_rate: u64, + + // BBR's estimated bottleneck bandwidth available to the transport flow, + // estimated from the maximum delivery rate sample in a sliding window. + btlbw: u64, + + // The max filter used to estimate BBR.BtlBw. + btlbwfilter: Minmax<u64>, + + // BBR's estimated two-way round-trip propagation delay of the path, + // estimated from the windowed minimum recent round-trip delay sample. + rtprop: Duration, + + // The wall clock time at which the current BBR.RTProp sample was obtained. + rtprop_stamp: Instant, + + // A boolean recording whether the BBR.RTprop has expired and is due for a + // refresh with an application idle period or a transition into ProbeRTT + // state. + rtprop_expired: bool, + + // The dynamic gain factor used to scale BBR.BtlBw to produce + // BBR.pacing_rate. + pacing_gain: f64, + + // The dynamic gain factor used to scale the estimated BDP to produce a + // congestion window (cwnd). + cwnd_gain: f64, + + // A boolean that records whether BBR estimates that it has ever fully + // utilized its available bandwidth ("filled the pipe"). + filled_pipe: bool, + + // Count of packet-timed round trips elapsed so far. + round_count: u64, + + // A boolean that BBR sets to true once per packet-timed round trip, + // on ACKs that advance BBR.round_count. + round_start: bool, + + // packet.delivered value denoting the end of a packet-timed round trip. + next_round_delivered: usize, + + // Timestamp when ProbeRTT state ends. + probe_rtt_done_stamp: Option<Instant>, + + // Checking if a roundtrip in ProbeRTT state ends. + probe_rtt_round_done: bool, + + // Checking if in the packet conservation mode during recovery. + packet_conservation: bool, + + // Saved cwnd before loss recovery. + prior_cwnd: usize, + + // Checking if restarting from idle. + idle_restart: bool, + + // Baseline level delivery rate for full pipe estimator. + full_bw: u64, + + // The number of round for full pipe estimator without much growth. + full_bw_count: usize, + + // Last time cycle_index is updated. + cycle_stamp: Instant, + + // Current index of pacing_gain_cycle[]. + cycle_index: usize, + + // The upper bound on the volume of data BBR allows in flight. + target_cwnd: usize, + + // Whether in the recovery episode. + in_recovery: bool, + + // Start time of the connection. + start_time: Instant, + + // Newly marked lost data size in bytes. + newly_lost_bytes: usize, + + // Newly acked data size in bytes. + newly_acked_bytes: usize, + + // bytes_in_flight before processing this ACK. + prior_bytes_in_flight: usize, +} + +impl State { + pub fn new() -> Self { + let now = Instant::now(); + + State { + state: BBRStateMachine::Startup, + + pacing_rate: 0, + + btlbw: 0, + + btlbwfilter: Minmax::new(0), + + rtprop: Duration::ZERO, + + rtprop_stamp: now, + + rtprop_expired: false, + + pacing_gain: 0.0, + + cwnd_gain: 0.0, + + filled_pipe: false, + + round_count: 0, + + round_start: false, + + next_round_delivered: 0, + + probe_rtt_done_stamp: None, + + probe_rtt_round_done: false, + + packet_conservation: false, + + prior_cwnd: 0, + + idle_restart: false, + + full_bw: 0, + + full_bw_count: 0, + + cycle_stamp: now, + + cycle_index: 0, + + target_cwnd: 0, + + in_recovery: false, + + start_time: now, + + newly_lost_bytes: 0, + + newly_acked_bytes: 0, + + prior_bytes_in_flight: 0, + } + } +} + +// When entering the recovery episode. +fn bbr_enter_recovery(r: &mut Recovery, now: Instant) { + r.bbr_state.prior_cwnd = per_ack::bbr_save_cwnd(r); + + r.congestion_window = r.bytes_in_flight + + r.bbr_state.newly_acked_bytes.max(r.max_datagram_size); + r.congestion_recovery_start_time = Some(now); + + r.bbr_state.packet_conservation = true; + r.bbr_state.in_recovery = true; + + // Start round now. + r.bbr_state.next_round_delivered = r.delivery_rate.delivered(); +} + +// When exiting the recovery episode. +fn bbr_exit_recovery(r: &mut Recovery) { + r.congestion_recovery_start_time = None; + + r.bbr_state.packet_conservation = false; + r.bbr_state.in_recovery = false; + + per_ack::bbr_restore_cwnd(r); +} + +// Congestion Control Hooks. +// +fn on_init(r: &mut Recovery) { + init::bbr_init(r); +} + +fn reset(r: &mut Recovery) { + r.bbr_state = State::new(); + + init::bbr_init(r); +} + +fn on_packet_sent(r: &mut Recovery, sent_bytes: usize, _now: Instant) { + r.bytes_in_flight += sent_bytes; + + per_transmit::bbr_on_transmit(r); +} + +fn on_packets_acked( + r: &mut Recovery, packets: &[Acked], _epoch: packet::Epoch, now: Instant, +) { + r.bbr_state.newly_acked_bytes = packets.iter().fold(0, |acked_bytes, p| { + r.bbr_state.prior_bytes_in_flight = r.bytes_in_flight; + + per_ack::bbr_update_model_and_state(r, p, now); + + r.bytes_in_flight = r.bytes_in_flight.saturating_sub(p.size); + + acked_bytes + p.size + }); + + if let Some(pkt) = packets.last() { + if !r.in_congestion_recovery(pkt.time_sent) { + // Upon exiting loss recovery. + bbr_exit_recovery(r); + } + } + + per_ack::bbr_update_control_parameters(r, now); + + r.bbr_state.newly_lost_bytes = 0; +} + +fn congestion_event( + r: &mut Recovery, lost_bytes: usize, time_sent: Instant, + _epoch: packet::Epoch, now: Instant, +) { + r.bbr_state.newly_lost_bytes = lost_bytes; + + // Upon entering Fast Recovery. + if !r.in_congestion_recovery(time_sent) { + // Upon entering Fast Recovery. + bbr_enter_recovery(r, now); + } +} + +fn collapse_cwnd(r: &mut Recovery) { + r.bbr_state.prior_cwnd = per_ack::bbr_save_cwnd(r); + + reno::collapse_cwnd(r); +} + +fn checkpoint(_r: &mut Recovery) {} + +fn rollback(_r: &mut Recovery) -> bool { + false +} + +fn has_custom_pacing() -> bool { + true +} + +fn debug_fmt(r: &Recovery, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let bbr = &r.bbr_state; + + write!( + f, + "bbr={{ state={:?} btlbw={} rtprop={:?} pacing_rate={} pacing_gain={} cwnd_gain={} target_cwnd={} send_quantum={} filled_pipe={} round_count={} }}", + bbr.state, bbr.btlbw, bbr.rtprop, bbr.pacing_rate, bbr.pacing_gain, bbr.cwnd_gain, bbr.target_cwnd, r.send_quantum(), bbr.filled_pipe, bbr.round_count + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::recovery; + + use smallvec::smallvec; + + #[test] + fn bbr_init() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + + // on_init() is called in Connection::new(), so it need to be + // called manually here. + r.on_init(); + + assert_eq!(r.cwnd(), r.max_datagram_size * INITIAL_WINDOW_PACKETS); + assert_eq!(r.bytes_in_flight, 0); + + assert_eq!(r.bbr_state.state, BBRStateMachine::Startup); + } + + #[test] + fn bbr_send() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + + r.on_init(); + r.on_packet_sent_cc(1000, now); + + assert_eq!(r.bytes_in_flight, 1000); + } + + #[test] + fn bbr_startup() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + let mss = r.max_datagram_size; + + r.on_init(); + + // Send 5 packets. + for pn in 0..5 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: 0, + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + } + + let rtt = Duration::from_millis(50); + let now = now + rtt; + let cwnd_prev = r.cwnd(); + + let mut acked = ranges::RangeSet::default(); + acked.insert(0..5); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + + assert_eq!(r.bbr_state.state, BBRStateMachine::Startup); + assert_eq!(r.cwnd(), cwnd_prev + mss * 5); + assert_eq!(r.bytes_in_flight, 0); + assert_eq!( + r.delivery_rate(), + ((mss * 5) as f64 / rtt.as_secs_f64()) as u64 + ); + assert_eq!(r.bbr_state.btlbw, r.delivery_rate()); + } + + #[test] + fn bbr_congestion_event() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + let mss = r.max_datagram_size; + + r.on_init(); + + // Send 5 packets. + for pn in 0..5 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: 0, + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + } + + let rtt = Duration::from_millis(50); + let now = now + rtt; + + // Make a packet loss to trigger a congestion event. + let mut acked = ranges::RangeSet::default(); + acked.insert(4..5); + + // 2 acked, 2 x MSS lost. + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((2, 2400)), + ); + + // Sent: 0, 1, 2, 3, 4, Acked 4. + assert_eq!(r.cwnd(), mss * 4); + // Stil in flight: 2, 3. + assert_eq!(r.bytes_in_flight, mss * 2); + } + + #[test] + fn bbr_drain() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + let mss = r.max_datagram_size; + + r.on_init(); + + let mut pn = 0; + + // Stop right before filled_pipe=true. + for _ in 0..3 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: r.delivery_rate.delivered(), + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + pn += 1; + + let rtt = Duration::from_millis(50); + + let now = now + rtt; + + let mut acked = ranges::RangeSet::default(); + acked.insert(0..pn); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + } + + // Stop at right before filled_pipe=true. + for _ in 0..5 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: r.delivery_rate.delivered(), + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + pn += 1; + } + + let rtt = Duration::from_millis(50); + let now = now + rtt; + + let mut acked = ranges::RangeSet::default(); + + // We sent 5 packets, but ack only one, to stay + // in Drain state. + acked.insert(0..pn - 4); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + + // Now we are in Drain state. + assert_eq!(r.bbr_state.filled_pipe, true); + assert_eq!(r.bbr_state.state, BBRStateMachine::Drain); + assert!(r.bbr_state.pacing_gain < 1.0); + } + + #[test] + fn bbr_probe_bw() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + let mss = r.max_datagram_size; + + r.on_init(); + + let mut pn = 0; + + // At 4th roundtrip, filled_pipe=true and switch to Drain, + // but move to ProbeBW immediately because bytes_in_flight is + // smaller than BBRInFlight(1). + for _ in 0..4 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: r.delivery_rate.delivered(), + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + pn += 1; + + let rtt = Duration::from_millis(50); + let now = now + rtt; + + let mut acked = ranges::RangeSet::default(); + acked.insert(0..pn); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + } + + // Now we are in ProbeBW state. + assert_eq!(r.bbr_state.filled_pipe, true); + assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeBW); + + // In the first ProbeBW cycle, pacing_gain should be >= 1.0. + assert!(r.bbr_state.pacing_gain >= 1.0); + } + + #[test] + fn bbr_probe_rtt() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::BBR); + + let mut r = Recovery::new(&cfg); + let now = Instant::now(); + let mss = r.max_datagram_size; + + r.on_init(); + + let mut pn = 0; + + // At 4th roundtrip, filled_pipe=true and switch to Drain, + // but move to ProbeBW immediately because bytes_in_flight is + // smaller than BBRInFlight(1). + for _ in 0..4 { + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: r.delivery_rate.delivered(), + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + pn += 1; + + let rtt = Duration::from_millis(50); + let now = now + rtt; + + let mut acked = ranges::RangeSet::default(); + acked.insert(0..pn); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + } + + // Now we are in ProbeBW state. + assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeBW); + + // After RTPROP_FILTER_LEN (10s), switch to ProbeRTT. + let now = now + RTPROP_FILTER_LEN; + + let pkt = Sent { + pkt_num: pn, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: mss, + ack_eliciting: true, + in_flight: true, + delivered: r.delivery_rate.delivered(), + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + pkt, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + pn += 1; + + // Don't update rtprop by giving larger rtt than before. + // If rtprop is updated, rtprop expiry check is reset. + let rtt = Duration::from_millis(100); + let now = now + rtt; + + let mut acked = ranges::RangeSet::default(); + acked.insert(0..pn); + + assert_eq!( + r.on_ack_received( + &acked, + 25, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ), + Ok((0, 0)), + ); + + assert_eq!(r.bbr_state.state, BBRStateMachine::ProbeRTT); + assert_eq!(r.bbr_state.pacing_gain, 1.0); + } +} + +mod init; +mod pacing; +mod per_ack; +mod per_transmit; diff --git a/src/recovery/bbr/pacing.rs b/src/recovery/bbr/pacing.rs new file mode 100644 index 0000000..e5e21dd --- /dev/null +++ b/src/recovery/bbr/pacing.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::recovery::Recovery; + +// BBR Transmit Packet Pacing Functions +// + +// 4.2.1. Pacing Rate +pub fn bbr_set_pacing_rate_with_gain(r: &mut Recovery, pacing_gain: f64) { + let rate = (pacing_gain * r.bbr_state.btlbw as f64) as u64; + + if r.bbr_state.filled_pipe || rate > r.bbr_state.pacing_rate { + r.bbr_state.pacing_rate = rate; + } +} + +pub fn bbr_set_pacing_rate(r: &mut Recovery) { + bbr_set_pacing_rate_with_gain(r, r.bbr_state.pacing_gain); +} diff --git a/src/recovery/bbr/per_ack.rs b/src/recovery/bbr/per_ack.rs new file mode 100644 index 0000000..6fe5651 --- /dev/null +++ b/src/recovery/bbr/per_ack.rs @@ -0,0 +1,380 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use super::*; +use crate::rand; +use crate::recovery; + +use std::cmp; +use std::time::Instant; + +/// 1.2Mbps in bytes/sec +const PACING_RATE_1_2MBPS: u64 = 1200 * 1000 / 8; + +/// 24Mbps in bytes/sec +const PACING_RATE_24MBPS: u64 = 24 * 1000 * 1000 / 8; + +/// The minimal cwnd value BBR tries to target, in bytes +#[inline] +fn bbr_min_pipe_cwnd(r: &mut Recovery) -> usize { + BBR_MIN_PIPE_CWND_PKTS * r.max_datagram_size +} + +// BBR Functions when ACK is received. +// +pub fn bbr_update_model_and_state( + r: &mut Recovery, packet: &Acked, now: Instant, +) { + bbr_update_btlbw(r, packet); + bbr_check_cycle_phase(r, now); + bbr_check_full_pipe(r); + bbr_check_drain(r, now); + bbr_update_rtprop(r, now); + bbr_check_probe_rtt(r, now); +} + +pub fn bbr_update_control_parameters(r: &mut Recovery, now: Instant) { + pacing::bbr_set_pacing_rate(r); + bbr_set_send_quantum(r); + + // Set outgoing packet pacing rate + // It is called here because send_quantum may be updated too. + r.set_pacing_rate(r.bbr_state.pacing_rate, now); + + bbr_set_cwnd(r); +} + +// BBR Functions while processing ACKs. +// + +// 4.1.1.5. Updating the BBR.BtlBw Max Filter +fn bbr_update_btlbw(r: &mut Recovery, packet: &Acked) { + bbr_update_round(r, packet); + + if r.delivery_rate() >= r.bbr_state.btlbw || + !r.delivery_rate.sample_is_app_limited() + { + // Since minmax filter is based on time, + // start_time + (round_count as seconds) is used instead. + r.bbr_state.btlbw = r.bbr_state.btlbwfilter.running_max( + BTLBW_FILTER_LEN, + r.bbr_state.start_time + Duration::from_secs(r.bbr_state.round_count), + r.delivery_rate(), + ); + } +} + +// 4.1.1.3 Tracking Time for the BBR.BtlBw Max Filter +fn bbr_update_round(r: &mut Recovery, packet: &Acked) { + let bbr = &mut r.bbr_state; + + if packet.delivered >= bbr.next_round_delivered { + bbr.next_round_delivered = r.delivery_rate.delivered(); + bbr.round_count += 1; + bbr.round_start = true; + bbr.packet_conservation = false; + } else { + bbr.round_start = false; + } +} + +// 4.1.2.3. Updating the BBR.RTprop Min Filter +fn bbr_update_rtprop(r: &mut Recovery, now: Instant) { + let bbr = &mut r.bbr_state; + let rs_rtt = r.delivery_rate.sample_rtt(); + + bbr.rtprop_expired = now > bbr.rtprop_stamp + RTPROP_FILTER_LEN; + + if !rs_rtt.is_zero() && (rs_rtt <= bbr.rtprop || bbr.rtprop_expired) { + bbr.rtprop = rs_rtt; + bbr.rtprop_stamp = now; + } +} + +// 4.2.2 Send Quantum +fn bbr_set_send_quantum(r: &mut Recovery) { + let rate = r.bbr_state.pacing_rate; + + r.send_quantum = match rate { + rate if rate < PACING_RATE_1_2MBPS => r.max_datagram_size, + + rate if rate < PACING_RATE_24MBPS => 2 * r.max_datagram_size, + + _ => cmp::min((rate / 1000_u64) as usize, 64 * 1024), + } +} + +// 4.2.3.2 Target cwnd +fn bbr_inflight(r: &mut Recovery, gain: f64) -> usize { + let bbr = &mut r.bbr_state; + + if bbr.rtprop == Duration::MAX { + return r.max_datagram_size * INITIAL_WINDOW_PACKETS; + } + + let quanta = 3 * r.send_quantum; + let estimated_bdp = bbr.btlbw as f64 * bbr.rtprop.as_secs_f64(); + + (gain * estimated_bdp) as usize + quanta +} + +fn bbr_update_target_cwnd(r: &mut Recovery) { + r.bbr_state.target_cwnd = bbr_inflight(r, r.bbr_state.cwnd_gain); +} + +// 4.2.3.4 Modulating cwnd in Loss Recovery +pub fn bbr_save_cwnd(r: &mut Recovery) -> usize { + if !r.bbr_state.in_recovery && r.bbr_state.state != BBRStateMachine::ProbeRTT + { + r.congestion_window + } else { + r.congestion_window.max(r.bbr_state.prior_cwnd) + } +} + +pub fn bbr_restore_cwnd(r: &mut Recovery) { + r.congestion_window = r.congestion_window.max(r.bbr_state.prior_cwnd); +} + +fn bbr_modulate_cwnd_for_recovery(r: &mut Recovery) { + let acked_bytes = r.bbr_state.newly_acked_bytes; + let lost_bytes = r.bbr_state.newly_lost_bytes; + + if lost_bytes > 0 { + // QUIC mininum cwnd is 2 x MSS. + r.congestion_window = r + .congestion_window + .saturating_sub(lost_bytes) + .max(r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS); + } + + if r.bbr_state.packet_conservation { + r.congestion_window = + r.congestion_window.max(r.bytes_in_flight + acked_bytes); + } +} + +// 4.2.3.5 Modulating cwnd in ProbeRTT +fn bbr_modulate_cwnd_for_probe_rtt(r: &mut Recovery) { + if r.bbr_state.state == BBRStateMachine::ProbeRTT { + r.congestion_window = r.congestion_window.min(bbr_min_pipe_cwnd(r)) + } +} + +// 4.2.3.6 Core cwnd Adjustment Mechanism +fn bbr_set_cwnd(r: &mut Recovery) { + let acked_bytes = r.bbr_state.newly_acked_bytes; + + bbr_update_target_cwnd(r); + bbr_modulate_cwnd_for_recovery(r); + + if !r.bbr_state.packet_conservation { + if r.bbr_state.filled_pipe { + r.congestion_window = cmp::min( + r.congestion_window + acked_bytes, + r.bbr_state.target_cwnd, + ) + } else if r.congestion_window < r.bbr_state.target_cwnd || + r.delivery_rate.delivered() < + r.max_datagram_size * INITIAL_WINDOW_PACKETS + { + r.congestion_window += acked_bytes; + } + + r.congestion_window = r.congestion_window.max(bbr_min_pipe_cwnd(r)) + } + + bbr_modulate_cwnd_for_probe_rtt(r); +} + +// 4.3.2.2. Estimating When Startup has Filled the Pipe +fn bbr_check_full_pipe(r: &mut Recovery) { + // No need to check for a full pipe now. + if r.bbr_state.filled_pipe || + !r.bbr_state.round_start || + r.delivery_rate.sample_is_app_limited() + { + return; + } + + // BBR.BtlBw still growing? + if r.bbr_state.btlbw >= + (r.bbr_state.full_bw as f64 * BTLBW_GROWTH_TARGET) as u64 + { + // record new baseline level + r.bbr_state.full_bw = r.bbr_state.btlbw; + r.bbr_state.full_bw_count = 0; + return; + } + + // another round w/o much growth + r.bbr_state.full_bw_count += 1; + + if r.bbr_state.full_bw_count >= 3 { + r.bbr_state.filled_pipe = true; + } +} + +// 4.3.3. Drain +fn bbr_enter_drain(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + bbr.state = BBRStateMachine::Drain; + + // pace slowly + bbr.pacing_gain = 1.0 / BBR_HIGH_GAIN; + + // maintain cwnd + bbr.cwnd_gain = BBR_HIGH_GAIN; +} + +fn bbr_check_drain(r: &mut Recovery, now: Instant) { + if r.bbr_state.state == BBRStateMachine::Startup && r.bbr_state.filled_pipe { + bbr_enter_drain(r); + } + + if r.bbr_state.state == BBRStateMachine::Drain && + r.bytes_in_flight <= bbr_inflight(r, 1.0) + { + // we estimate queue is drained + bbr_enter_probe_bw(r, now); + } +} + +// 4.3.4.3. Gain Cycling Algorithm +fn bbr_enter_probe_bw(r: &mut Recovery, now: Instant) { + let bbr = &mut r.bbr_state; + + bbr.state = BBRStateMachine::ProbeBW; + bbr.pacing_gain = 1.0; + bbr.cwnd_gain = 2.0; + + // cycle_index will be one of (1, 2, 3, 4, 5, 6, 7). Since + // bbr_advance_cycle_phase() is called right next and it will + // increase cycle_index by 1, the actual cycle_index in the + // beginning of ProbeBW will be one of (2, 3, 4, 5, 6, 7, 0) + // to avoid index 1 (pacing_gain=3/4). See 4.3.4.2 for details. + bbr.cycle_index = BBR_GAIN_CYCLE_LEN - + 1 - + (rand::rand_u64_uniform(BBR_GAIN_CYCLE_LEN as u64 - 1) as usize); + + bbr_advance_cycle_phase(r, now); +} + +fn bbr_check_cycle_phase(r: &mut Recovery, now: Instant) { + let bbr = &mut r.bbr_state; + + if bbr.state == BBRStateMachine::ProbeBW && bbr_is_next_cycle_phase(r, now) { + bbr_advance_cycle_phase(r, now); + } +} + +fn bbr_advance_cycle_phase(r: &mut Recovery, now: Instant) { + let bbr = &mut r.bbr_state; + + bbr.cycle_stamp = now; + bbr.cycle_index = (bbr.cycle_index + 1) % BBR_GAIN_CYCLE_LEN; + bbr.pacing_gain = PACING_GAIN_CYCLE[bbr.cycle_index]; +} + +fn bbr_is_next_cycle_phase(r: &mut Recovery, now: Instant) -> bool { + let bbr = &mut r.bbr_state; + let lost_bytes = bbr.newly_lost_bytes; + let pacing_gain = bbr.pacing_gain; + let prior_in_flight = bbr.prior_bytes_in_flight; + + let is_full_length = (now - bbr.cycle_stamp) > bbr.rtprop; + + // pacing_gain == 1.0 + if (pacing_gain - 1.0).abs() < f64::EPSILON { + return is_full_length; + } + + if pacing_gain > 1.0 { + return is_full_length && + (lost_bytes > 0 || + prior_in_flight >= bbr_inflight(r, pacing_gain)); + } + + is_full_length || prior_in_flight <= bbr_inflight(r, 1.0) +} + +// 4.3.5. ProbeRTT +fn bbr_check_probe_rtt(r: &mut Recovery, now: Instant) { + if r.bbr_state.state != BBRStateMachine::ProbeRTT && + r.bbr_state.rtprop_expired && + !r.bbr_state.idle_restart + { + bbr_enter_probe_rtt(r); + + r.bbr_state.prior_cwnd = bbr_save_cwnd(r); + r.bbr_state.probe_rtt_done_stamp = None; + } + + if r.bbr_state.state == BBRStateMachine::ProbeRTT { + bbr_handle_probe_rtt(r, now); + } + + r.bbr_state.idle_restart = false; +} + +fn bbr_enter_probe_rtt(r: &mut Recovery) { + let bbr = &mut r.bbr_state; + + bbr.state = BBRStateMachine::ProbeRTT; + bbr.pacing_gain = 1.0; + bbr.cwnd_gain = 1.0; +} + +fn bbr_handle_probe_rtt(r: &mut Recovery, now: Instant) { + // Ignore low rate samples during ProbeRTT. + r.delivery_rate.update_app_limited(true); + + if let Some(probe_rtt_done_stamp) = r.bbr_state.probe_rtt_done_stamp { + if r.bbr_state.round_start { + r.bbr_state.probe_rtt_round_done = true; + } + + if r.bbr_state.probe_rtt_round_done && now > probe_rtt_done_stamp { + r.bbr_state.rtprop_stamp = now; + + bbr_restore_cwnd(r); + bbr_exit_probe_rtt(r, now); + } + } else if r.bytes_in_flight <= bbr_min_pipe_cwnd(r) { + r.bbr_state.probe_rtt_done_stamp = Some(now + PROBE_RTT_DURATION); + r.bbr_state.probe_rtt_round_done = false; + r.bbr_state.next_round_delivered = r.delivery_rate.delivered(); + } +} + +fn bbr_exit_probe_rtt(r: &mut Recovery, now: Instant) { + if r.bbr_state.filled_pipe { + bbr_enter_probe_bw(r, now); + } else { + init::bbr_enter_startup(r); + } +} diff --git a/src/recovery/bbr/per_transmit.rs b/src/recovery/bbr/per_transmit.rs new file mode 100644 index 0000000..f454387 --- /dev/null +++ b/src/recovery/bbr/per_transmit.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use super::*; + +use crate::recovery::Recovery; + +// BBR Functions when trasmitting packets. +// +pub fn bbr_on_transmit(r: &mut Recovery) { + bbr_handle_restart_from_idle(r); +} + +// 4.3.4.4. Restarting From Idle +fn bbr_handle_restart_from_idle(r: &mut Recovery) { + if r.bytes_in_flight == 0 && r.delivery_rate.app_limited() { + r.bbr_state.idle_restart = true; + + if r.bbr_state.state == BBRStateMachine::ProbeBW { + pacing::bbr_set_pacing_rate_with_gain(r, 1.0); + } + } +} diff --git a/src/recovery/cubic.rs b/src/recovery/cubic.rs index 090a8f4..62f7a4e 100644 --- a/src/recovery/cubic.rs +++ b/src/recovery/cubic.rs @@ -46,6 +46,7 @@ use crate::recovery::Recovery; pub static CUBIC: CongestionControlOps = CongestionControlOps { on_init, + reset, on_packet_sent, on_packets_acked, congestion_event, @@ -147,6 +148,10 @@ impl State { fn on_init(_r: &mut Recovery) {} +fn reset(r: &mut Recovery) { + r.cubic_state = State::default(); +} + fn collapse_cwnd(r: &mut Recovery) { let cubic = &mut r.cubic_state; @@ -433,6 +438,8 @@ mod tests { use super::*; use crate::recovery::hystart; + use smallvec::smallvec; + #[test] fn cubic_init() { let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); @@ -466,7 +473,7 @@ mod tests { let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -498,7 +505,7 @@ mod tests { rtt: Duration::ZERO, }]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); // Check if cwnd increased by packet size (slow start) assert_eq!(r.cwnd(), cwnd_prev + p.size); @@ -514,7 +521,7 @@ mod tests { let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -568,7 +575,7 @@ mod tests { }, ]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); // Acked 3 packets. assert_eq!(r.cwnd(), cwnd_prev + p.size * 3); @@ -586,7 +593,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -613,7 +620,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -646,7 +653,7 @@ mod tests { rtt: Duration::ZERO, }]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); now += rtt; } @@ -668,7 +675,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -691,7 +698,7 @@ mod tests { rtt: Duration::ZERO, }]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); // Slow start again - cwnd will be increased by 1 MSS assert_eq!( @@ -708,11 +715,11 @@ mod tests { let mut r = Recovery::new(&cfg); let now = Instant::now(); - let epoch = packet::EPOCH_APPLICATION; + let epoch = packet::Epoch::Application; let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -741,7 +748,7 @@ mod tests { r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. let now = now + rtt_1st; for _ in 0..n_rtt_sample { r.update_rtt(rtt_1st, Duration::from_millis(0), now); @@ -775,7 +782,7 @@ mod tests { } r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. // Last ack will cause to exit to CSS. let mut cwnd_prev = r.cwnd(); @@ -818,7 +825,7 @@ mod tests { } r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. // Last ack will cause to exit to SS. for _ in 0..n_rtt_sample { r.update_rtt(rtt_3rd, Duration::from_millis(0), now); @@ -856,11 +863,11 @@ mod tests { let mut r = Recovery::new(&cfg); let now = Instant::now(); - let epoch = packet::EPOCH_APPLICATION; + let epoch = packet::Epoch::Application; let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -889,7 +896,7 @@ mod tests { r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. let now = now + rtt_1st; for _ in 0..n_rtt_sample { r.update_rtt(rtt_1st, Duration::from_millis(0), now); @@ -923,7 +930,7 @@ mod tests { } r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. // Last ack will cause to exit to CSS. let mut cwnd_prev = r.cwnd(); @@ -965,7 +972,7 @@ mod tests { } r.hystart.start_round(send_pn - 1); - // Receving Acks. + // Receiving Acks. for _ in 0..n_rtt_sample { r.update_rtt(rtt_css, Duration::from_millis(0), now); @@ -1007,7 +1014,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -1032,10 +1039,10 @@ mod tests { // Ack more than cwnd bytes with rtt=100ms r.update_rtt(rtt, Duration::from_millis(0), now); - // Trigger detecting sprurious congestion event + // Trigger detecting spurious congestion event r.on_packets_acked( acked, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now + rtt + Duration::from_millis(5), ); @@ -1049,7 +1056,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -1074,10 +1081,10 @@ mod tests { // Ack more than cwnd bytes with rtt=100ms. r.update_rtt(rtt, Duration::from_millis(0), now); - // Trigger detecting sprurious congestion event. + // Trigger detecting spurious congestion event. r.on_packets_acked( acked, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now + rtt + Duration::from_millis(5), ); @@ -1103,7 +1110,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -1135,7 +1142,7 @@ mod tests { rtt: Duration::ZERO, }]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); now += rtt; } @@ -1149,7 +1156,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); diff --git a/src/recovery/delivery_rate.rs b/src/recovery/delivery_rate.rs index dcc3e48..23c2def 100644 --- a/src/recovery/delivery_rate.rs +++ b/src/recovery/delivery_rate.rs @@ -79,13 +79,11 @@ impl Default for Rate { } impl Rate { - pub fn on_packet_sent( - &mut self, pkt: &mut Sent, bytes_in_flight: usize, now: Instant, - ) { - // No packets in flight yet? + pub fn on_packet_sent(&mut self, pkt: &mut Sent, bytes_in_flight: usize) { + // No packets in flight. if bytes_in_flight == 0 { - self.first_sent_time = now; - self.delivered_time = now; + self.first_sent_time = pkt.time_sent; + self.delivered_time = pkt.time_sent; } pkt.first_sent_time = self.first_sent_time; @@ -162,7 +160,7 @@ impl Rate { self.end_of_app_limited != 0 } - pub fn _delivered(&self) -> usize { + pub fn delivered(&self) -> usize { self.delivered } @@ -170,11 +168,11 @@ impl Rate { self.rate_sample.delivery_rate } - pub fn _sample_rtt(&self) -> Duration { + pub fn sample_rtt(&self) -> Duration { self.rate_sample.rtt } - pub fn _sample_is_app_limited(&self) -> bool { + pub fn sample_is_app_limited(&self) -> bool { self.rate_sample.is_app_limited } } @@ -206,6 +204,8 @@ mod tests { use crate::recovery::*; + use smallvec::smallvec; + #[test] fn rate_check() { let config = Config::new(0xbabababa).unwrap(); @@ -218,7 +218,7 @@ mod tests { for pn in 0..2 { let pkt = Sent { pkt_num: pn, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -234,7 +234,7 @@ mod tests { r.on_packet_sent( pkt, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", @@ -253,7 +253,7 @@ mod tests { rtt, delivered: 0, delivered_time: now, - first_sent_time: now - rtt, + first_sent_time: now.checked_sub(rtt).unwrap(), is_app_limited: false, }; @@ -264,7 +264,7 @@ mod tests { r.delivery_rate.generate_rate_sample(rtt); // Bytes acked so far. - assert_eq!(r.delivery_rate._delivered(), 2400); + assert_eq!(r.delivery_rate.delivered(), 2400); // Estimated delivery rate = (1200 x 2) / 0.05s = 48000. assert_eq!(r.delivery_rate(), 48000); @@ -282,7 +282,7 @@ mod tests { for pn in 0..10 { let pkt = Sent { pkt_num: pn, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -298,7 +298,7 @@ mod tests { r.on_packet_sent( pkt, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", @@ -306,7 +306,7 @@ mod tests { } assert_eq!(r.app_limited(), false); - assert_eq!(r.delivery_rate._sample_is_app_limited(), false); + assert_eq!(r.delivery_rate.sample_is_app_limited(), false); } #[test] @@ -321,7 +321,7 @@ mod tests { for pn in 0..5 { let pkt = Sent { pkt_num: pn, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -337,7 +337,7 @@ mod tests { r.on_packet_sent( pkt, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", @@ -354,18 +354,17 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ), - Ok(()), + Ok((0, 0)), ); assert_eq!(r.app_limited(), true); - // Rate sample is not app limited (all acked). - assert_eq!(r.delivery_rate._sample_is_app_limited(), false); - assert_eq!(r.delivery_rate._sample_rtt(), rtt); + assert_eq!(r.delivery_rate.sample_is_app_limited(), false); + assert_eq!(r.delivery_rate.sample_rtt(), rtt); } } diff --git a/src/recovery/hystart.rs b/src/recovery/hystart.rs index 2285951..4d2ead5 100644 --- a/src/recovery/hystart.rs +++ b/src/recovery/hystart.rs @@ -110,7 +110,7 @@ impl Hystart { pub fn in_css(&self, epoch: packet::Epoch) -> bool { self.enabled && - epoch == packet::EPOCH_APPLICATION && + epoch == packet::Epoch::Application && self.css_start_time().is_some() } @@ -131,7 +131,7 @@ impl Hystart { &mut self, epoch: packet::Epoch, packet: &recovery::Acked, rtt: Duration, now: Instant, ) -> bool { - if !(self.enabled && epoch == packet::EPOCH_APPLICATION) { + if !(self.enabled && epoch == packet::Epoch::Application) { return false; } diff --git a/src/recovery/mod.rs b/src/recovery/mod.rs index 7b9bce3..a053a1f 100644 --- a/src/recovery/mod.rs +++ b/src/recovery/mod.rs @@ -34,7 +34,6 @@ use std::time::Instant; use std::collections::VecDeque; use crate::Config; -use crate::Error; use crate::Result; use crate::frame; @@ -45,6 +44,8 @@ use crate::ranges; #[cfg(feature = "qlog")] use qlog::events::EventData; +use smallvec::SmallVec; + // Loss Recovery const INITIAL_PACKET_THRESHOLD: u64 = 3; @@ -71,16 +72,21 @@ const LOSS_REDUCTION_FACTOR: f64 = 0.5; const PACING_MULTIPLIER: f64 = 1.25; +// How many non ACK eliciting packets we send before including a PING to solicit +// an ACK. +pub(super) const MAX_OUTSTANDING_NON_ACK_ELICITING: usize = 24; + pub struct Recovery { loss_detection_timer: Option<Instant>, pto_count: u32, - time_of_last_sent_ack_eliciting_pkt: [Option<Instant>; packet::EPOCH_COUNT], + time_of_last_sent_ack_eliciting_pkt: + [Option<Instant>; packet::Epoch::count()], - largest_acked_pkt: [u64; packet::EPOCH_COUNT], + largest_acked_pkt: [u64; packet::Epoch::count()], - largest_sent_pkt: [u64; packet::EPOCH_COUNT], + largest_sent_pkt: [u64; packet::Epoch::count()], latest_rtt: Duration, @@ -94,21 +100,21 @@ pub struct Recovery { pub max_ack_delay: Duration, - loss_time: [Option<Instant>; packet::EPOCH_COUNT], + loss_time: [Option<Instant>; packet::Epoch::count()], - sent: [VecDeque<Sent>; packet::EPOCH_COUNT], + sent: [VecDeque<Sent>; packet::Epoch::count()], - pub lost: [Vec<frame::Frame>; packet::EPOCH_COUNT], + pub lost: [Vec<frame::Frame>; packet::Epoch::count()], - pub acked: [Vec<frame::Frame>; packet::EPOCH_COUNT], + pub acked: [Vec<frame::Frame>; packet::Epoch::count()], pub lost_count: usize, pub lost_spurious_count: usize, - pub loss_probes: [usize; packet::EPOCH_COUNT], + pub loss_probes: [usize; packet::Epoch::count()], - in_flight_count: [usize; packet::EPOCH_COUNT], + in_flight_count: [usize; packet::Epoch::count()], app_limited: bool, @@ -145,9 +151,7 @@ pub struct Recovery { hystart: hystart::Hystart, // Pacing. - pacing_rate: u64, - - last_packet_scheduled_time: Instant, + pub pacer: pacer::Pacer, // RFC6937 PRR. prr: prr::PRR, @@ -158,20 +162,49 @@ pub struct Recovery { // The maximum size of a data aggregate scheduled and // transmitted together. send_quantum: usize, + + // BBR state. + bbr_state: bbr::State, + + /// How many non-ack-eliciting packets have been sent. + outstanding_non_ack_eliciting: usize, +} + +pub struct RecoveryConfig { + max_send_udp_payload_size: usize, + pub max_ack_delay: Duration, + cc_ops: &'static CongestionControlOps, + hystart: bool, + pacing: bool, +} + +impl RecoveryConfig { + pub fn from_config(config: &Config) -> Self { + Self { + max_send_udp_payload_size: config.max_send_udp_payload_size, + max_ack_delay: Duration::ZERO, + cc_ops: config.cc_algorithm.into(), + hystart: config.hystart, + pacing: config.pacing, + } + } } impl Recovery { - pub fn new(config: &Config) -> Self { + pub fn new_with_config(recovery_config: &RecoveryConfig) -> Self { + let initial_congestion_window = + recovery_config.max_send_udp_payload_size * INITIAL_WINDOW_PACKETS; + Recovery { loss_detection_timer: None, pto_count: 0, - time_of_last_sent_ack_eliciting_pkt: [None; packet::EPOCH_COUNT], + time_of_last_sent_ack_eliciting_pkt: [None; packet::Epoch::count()], - largest_acked_pkt: [std::u64::MAX; packet::EPOCH_COUNT], + largest_acked_pkt: [u64::MAX; packet::Epoch::count()], - largest_sent_pkt: [0; packet::EPOCH_COUNT], + largest_sent_pkt: [0; packet::Epoch::count()], latest_rtt: Duration::ZERO, @@ -187,9 +220,9 @@ impl Recovery { rttvar: INITIAL_RTT / 2, - max_ack_delay: Duration::ZERO, + max_ack_delay: recovery_config.max_ack_delay, - loss_time: [None; packet::EPOCH_COUNT], + loss_time: [None; packet::Epoch::count()], sent: [VecDeque::new(), VecDeque::new(), VecDeque::new()], @@ -200,12 +233,11 @@ impl Recovery { lost_count: 0, lost_spurious_count: 0, - loss_probes: [0; packet::EPOCH_COUNT], + loss_probes: [0; packet::Epoch::count()], - in_flight_count: [0; packet::EPOCH_COUNT], + in_flight_count: [0; packet::Epoch::count()], - congestion_window: config.max_send_udp_payload_size * - INITIAL_WINDOW_PACKETS, + congestion_window: initial_congestion_window, pkt_thresh: INITIAL_PACKET_THRESHOLD, @@ -213,7 +245,7 @@ impl Recovery { bytes_in_flight: 0, - ssthresh: std::usize::MAX, + ssthresh: usize::MAX, bytes_acked_sl: 0, @@ -225,9 +257,9 @@ impl Recovery { congestion_recovery_start_time: None, - max_datagram_size: config.max_send_udp_payload_size, + max_datagram_size: recovery_config.max_send_udp_payload_size, - cc_ops: config.cc_algorithm.into(), + cc_ops: recovery_config.cc_ops, delivery_rate: delivery_rate::Rate::default(), @@ -235,26 +267,54 @@ impl Recovery { app_limited: false, - hystart: hystart::Hystart::new(config.hystart), + hystart: hystart::Hystart::new(recovery_config.hystart), - pacing_rate: 0, - - last_packet_scheduled_time: Instant::now(), + pacer: pacer::Pacer::new( + recovery_config.pacing, + initial_congestion_window, + 0, + recovery_config.max_send_udp_payload_size, + ), prr: prr::PRR::default(), - send_quantum: config.max_send_udp_payload_size * - INITIAL_WINDOW_PACKETS, + send_quantum: initial_congestion_window, #[cfg(feature = "qlog")] qlog_metrics: QlogMetrics::default(), + + bbr_state: bbr::State::new(), + + outstanding_non_ack_eliciting: 0, } } + pub fn new(config: &Config) -> Self { + Self::new_with_config(&RecoveryConfig::from_config(config)) + } + pub fn on_init(&mut self) { (self.cc_ops.on_init)(self); } + pub fn reset(&mut self) { + self.congestion_window = self.max_datagram_size * INITIAL_WINDOW_PACKETS; + self.in_flight_count = [0; packet::Epoch::count()]; + self.congestion_recovery_start_time = None; + self.ssthresh = usize::MAX; + (self.cc_ops.reset)(self); + self.hystart.reset(); + self.prr = prr::PRR::default(); + } + + /// Returns whether or not we should elicit an ACK even if we wouldn't + /// otherwise have constructed an ACK eliciting packet. + pub fn should_elicit_ack(&self, epoch: packet::Epoch) -> bool { + self.loss_probes[epoch] > 0 || + self.outstanding_non_ack_eliciting >= + MAX_OUTSTANDING_NON_ACK_ELICITING + } + pub fn on_packet_sent( &mut self, mut pkt: Sent, epoch: packet::Epoch, handshake_status: HandshakeStatus, now: Instant, trace_id: &str, @@ -264,12 +324,15 @@ impl Recovery { let sent_bytes = pkt.size; let pkt_num = pkt.pkt_num; + if ack_eliciting { + self.outstanding_non_ack_eliciting = 0; + } else { + self.outstanding_non_ack_eliciting += 1; + } + self.largest_sent_pkt[epoch] = cmp::max(self.largest_sent_pkt[epoch], pkt_num); - self.delivery_rate - .on_packet_sent(&mut pkt, self.bytes_in_flight, now); - if in_flight { if ack_eliciting { self.time_of_last_sent_ack_eliciting_pkt[epoch] = Some(now); @@ -290,7 +353,7 @@ impl Recovery { // HyStart++: Start of the round in a slow start. if self.hystart.enabled() && - epoch == packet::EPOCH_APPLICATION && + epoch == packet::Epoch::Application && self.congestion_window < self.ssthresh { self.hystart.start_round(pkt_num); @@ -301,7 +364,7 @@ impl Recovery { if let Some(srtt) = self.smoothed_rtt { let rate = PACING_MULTIPLIER * self.congestion_window as f64 / srtt.as_secs_f64(); - self.set_pacing_rate(rate as u64); + self.set_pacing_rate(rate as u64, now); } } @@ -309,6 +372,10 @@ impl Recovery { pkt.time_sent = self.get_packet_send_time(); + // bytes_in_flight is already updated. Use previous value. + self.delivery_rate + .on_packet_sent(&mut pkt, self.bytes_in_flight - sent_bytes); + self.sent[epoch].push_back(pkt); self.bytes_sent += sent_bytes; @@ -319,60 +386,51 @@ impl Recovery { (self.cc_ops.on_packet_sent)(self, sent_bytes, now); } - pub fn set_pacing_rate(&mut self, rate: u64) { - if rate != 0 { - self.pacing_rate = rate; - } + pub fn set_pacing_rate(&mut self, rate: u64, now: Instant) { + self.pacer.update(self.send_quantum, rate, now); } pub fn get_packet_send_time(&self) -> Instant { - self.last_packet_scheduled_time + self.pacer.next_time() } fn schedule_next_packet( &mut self, epoch: packet::Epoch, now: Instant, packet_size: usize, ) { // Don't pace in any of these cases: - // * Packet epoch is not EPOCH_APPLICATION. - // * Packet contains only ACK frames. - // * The start of the connection. - if epoch != packet::EPOCH_APPLICATION || - packet_size == 0 || - self.bytes_sent <= self.congestion_window || - self.pacing_rate == 0 - { - self.last_packet_scheduled_time = - cmp::max(self.last_packet_scheduled_time, now); + // * Packet contains no data. + // * Packet epoch is not Epoch::Application. + // * The congestion window is within initcwnd. - return; - } + let is_app = epoch == packet::Epoch::Application; - let interval = packet_size as f64 / self.pacing_rate as f64; - let interval = Duration::from_secs_f64(interval); - let next_schedule_time = self.last_packet_scheduled_time + interval; + let in_initcwnd = + self.bytes_sent < self.max_datagram_size * INITIAL_WINDOW_PACKETS; + + let sent_bytes = if !self.pacer.enabled() || !is_app || in_initcwnd { + 0 + } else { + packet_size + }; - self.last_packet_scheduled_time = cmp::max(now, next_schedule_time); + self.pacer.send(sent_bytes, now); } pub fn on_ack_received( &mut self, ranges: &ranges::RangeSet, ack_delay: u64, epoch: packet::Epoch, handshake_status: HandshakeStatus, now: Instant, trace_id: &str, - ) -> Result<()> { + ) -> Result<(usize, usize)> { let largest_acked = ranges.last().unwrap(); - // If the largest packet number acked exceeds any packet number we have - // sent, then the ACK is obviously invalid, so there's no need to - // continue further. - if largest_acked > self.largest_sent_pkt[epoch] { - if cfg!(feature = "fuzzing") { - return Ok(()); - } - - return Err(Error::InvalidPacket); - } + // While quiche used to consider ACK frames acknowledging packet numbers + // larger than the largest sent one as invalid, this is not true anymore + // if we consider a single packet number space and multiple paths. The + // simplest example is the case where the host sends a probing packet on + // a validating path, then receives an acknowledgment for that packet on + // the active one. - if self.largest_acked_pkt[epoch] == std::u64::MAX { + if self.largest_acked_pkt[epoch] == u64::MAX { self.largest_acked_pkt[epoch] = largest_acked; } else { self.largest_acked_pkt[epoch] = @@ -444,7 +502,7 @@ impl Recovery { largest_newly_acked_pkt_num = unacked.pkt_num; largest_newly_acked_sent_time = unacked.time_sent; - self.acked[epoch].append(&mut unacked.frames); + self.acked[epoch].extend(unacked.frames.drain(..)); if unacked.in_flight { self.in_flight_count[epoch] = @@ -479,7 +537,7 @@ impl Recovery { } if newly_acked.is_empty() { - return Ok(()); + return Ok((0, 0)); } if largest_newly_acked_pkt_num == largest_acked && has_ack_eliciting { @@ -488,18 +546,22 @@ impl Recovery { let latest_rtt = now.saturating_duration_since(largest_newly_acked_sent_time); - let ack_delay = if epoch == packet::EPOCH_APPLICATION { + let ack_delay = if epoch == packet::Epoch::Application { Duration::from_micros(ack_delay) } else { Duration::from_micros(0) }; - self.update_rtt(latest_rtt, ack_delay, now); + // Don't update srtt if rtt is zero. + if !latest_rtt.is_zero() { + self.update_rtt(latest_rtt, ack_delay, now); + } } // Detect and mark lost packets without removing them from the sent // packets list. - self.detect_lost_packets(epoch, now, trace_id); + let (lost_packets, lost_bytes) = + self.detect_lost_packets(epoch, now, trace_id); self.on_packets_acked(newly_acked, epoch, now); @@ -509,23 +571,24 @@ impl Recovery { self.drain_packets(epoch, now); - Ok(()) + Ok((lost_packets, lost_bytes)) } pub fn on_loss_detection_timeout( &mut self, handshake_status: HandshakeStatus, now: Instant, trace_id: &str, - ) { + ) -> (usize, usize) { let (earliest_loss_time, epoch) = self.loss_time_and_space(); if earliest_loss_time.is_some() { // Time threshold loss detection. - self.detect_lost_packets(epoch, now, trace_id); + let (lost_packets, lost_bytes) = + self.detect_lost_packets(epoch, now, trace_id); self.set_loss_detection_timer(handshake_status, now); trace!("{} {:?}", trace_id, self); - return; + return (lost_packets, lost_bytes); } let epoch = if self.bytes_in_flight > 0 { @@ -539,9 +602,9 @@ impl Recovery { // more anti-amplification credit, a Handshake packet proves address // ownership. if handshake_status.has_handshake_keys { - packet::EPOCH_HANDSHAKE + packet::Epoch::Handshake } else { - packet::EPOCH_INITIAL + packet::Epoch::Initial } }; @@ -573,6 +636,8 @@ impl Recovery { self.set_loss_detection_timer(handshake_status, now); trace!("{} {:?}", trace_id, self); + + (0, 0) } pub fn on_pkt_num_space_discarded( @@ -611,7 +676,7 @@ impl Recovery { pub fn cwnd_available(&self) -> usize { // Ignore cwnd when sending probe packets. if self.loss_probes.iter().any(|&x| x > 0) { - return std::usize::MAX; + return usize::MAX; } // Open more space (snd_cnt) for PRR when allowed. @@ -623,6 +688,18 @@ impl Recovery { self.smoothed_rtt.unwrap_or(INITIAL_RTT) } + pub fn min_rtt(&self) -> Option<Duration> { + if self.min_rtt == Duration::ZERO { + return None; + } + + Some(self.min_rtt) + } + + pub fn rttvar(&self) -> Duration { + self.rttvar + } + pub fn pto(&self) -> Duration { self.rtt() + cmp::max(self.rttvar * 4, GRANULARITY) } @@ -639,13 +716,20 @@ impl Recovery { let max_datagram_size = cmp::min(self.max_datagram_size, new_max_datagram_size); - // Congestion Window is updated only when it's not updated already. + // Update cwnd if it hasn't been updated yet. if self.congestion_window == self.max_datagram_size * INITIAL_WINDOW_PACKETS { self.congestion_window = max_datagram_size * INITIAL_WINDOW_PACKETS; } + self.pacer = pacer::Pacer::new( + self.pacer.enabled(), + self.congestion_window, + 0, + max_datagram_size, + ); + self.max_datagram_size = max_datagram_size; } @@ -688,11 +772,13 @@ impl Recovery { } fn loss_time_and_space(&self) -> (Option<Instant>, packet::Epoch) { - let mut epoch = packet::EPOCH_INITIAL; + let mut epoch = packet::Epoch::Initial; let mut time = self.loss_time[epoch]; // Iterate over all packet number spaces starting from Handshake. - for e in packet::EPOCH_HANDSHAKE..packet::EPOCH_COUNT { + for &e in packet::Epoch::epochs( + packet::Epoch::Handshake..=packet::Epoch::Application, + ) { let new_time = self.loss_time[e]; if time.is_none() || new_time < time { @@ -712,22 +798,24 @@ impl Recovery { // Arm PTO from now when there are no inflight packets. if self.bytes_in_flight == 0 { if handshake_status.has_handshake_keys { - return (Some(now + duration), packet::EPOCH_HANDSHAKE); + return (Some(now + duration), packet::Epoch::Handshake); } else { - return (Some(now + duration), packet::EPOCH_INITIAL); + return (Some(now + duration), packet::Epoch::Initial); } } let mut pto_timeout = None; - let mut pto_space = packet::EPOCH_INITIAL; + let mut pto_space = packet::Epoch::Initial; // Iterate over all packet number spaces. - for e in packet::EPOCH_INITIAL..packet::EPOCH_COUNT { + for &e in packet::Epoch::epochs( + packet::Epoch::Initial..=packet::Epoch::Application, + ) { if self.in_flight_count[e] == 0 { continue; } - if e == packet::EPOCH_APPLICATION { + if e == packet::Epoch::Application { // Skip Application Data until handshake completes. if !handshake_status.completed { return (pto_timeout, pto_space); @@ -772,7 +860,7 @@ impl Recovery { fn detect_lost_packets( &mut self, epoch: packet::Epoch, now: Instant, trace_id: &str, - ) { + ) -> (usize, usize) { let largest_acked = self.largest_acked_pkt[epoch]; self.loss_time[epoch] = None; @@ -784,8 +872,9 @@ impl Recovery { let loss_delay = cmp::max(loss_delay, GRANULARITY); // Packets sent before this time are deemed lost. - let lost_send_time = now - loss_delay; + let lost_send_time = now.checked_sub(loss_delay).unwrap(); + let mut lost_packets = 0; let mut lost_bytes = 0; let mut largest_lost_pkt = None; @@ -802,7 +891,7 @@ impl Recovery { if unacked.time_sent <= lost_send_time || largest_acked >= unacked.pkt_num + self.pkt_thresh { - self.lost[epoch].append(&mut unacked.frames); + self.lost[epoch].extend(unacked.frames.drain(..)); unacked.time_lost = Some(now); @@ -824,6 +913,7 @@ impl Recovery { ); } + lost_packets += 1; self.lost_count += 1; } else { let loss_time = match self.loss_time[epoch] { @@ -844,6 +934,8 @@ impl Recovery { } self.drain_packets(epoch, now); + + (lost_packets, lost_bytes) } fn drain_packets(&mut self, epoch: packet::Epoch, now: Instant) { @@ -940,7 +1032,7 @@ impl Recovery { self.app_limited = v; } - pub fn app_limited(&mut self) -> bool { + pub fn app_limited(&self) -> bool { self.app_limited } @@ -958,6 +1050,7 @@ impl Recovery { cwnd: self.cwnd() as u64, bytes_in_flight: self.bytes_in_flight as u64, ssthresh: self.ssthresh as u64, + pacing_rate: self.pacer.rate(), }; self.qlog_metrics.maybe_update(qlog_metrics) @@ -972,13 +1065,15 @@ impl Recovery { /// /// This enum provides currently available list of congestion control /// algorithms. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(C)] pub enum CongestionControlAlgorithm { /// Reno congestion control algorithm. `reno` in a string form. Reno = 0, /// CUBIC congestion control algorithm (default). `cubic` in a string form. CUBIC = 1, + /// BBR congestion control algorithm. `bbr` in a string form. + BBR = 2, } impl FromStr for CongestionControlAlgorithm { @@ -991,6 +1086,7 @@ impl FromStr for CongestionControlAlgorithm { match name { "reno" => Ok(CongestionControlAlgorithm::Reno), "cubic" => Ok(CongestionControlAlgorithm::CUBIC), + "bbr" => Ok(CongestionControlAlgorithm::BBR), _ => Err(crate::Error::CongestionControl), } @@ -1000,6 +1096,8 @@ impl FromStr for CongestionControlAlgorithm { pub struct CongestionControlOps { pub on_init: fn(r: &mut Recovery), + pub reset: fn(r: &mut Recovery), + pub on_packet_sent: fn(r: &mut Recovery, sent_bytes: usize, now: Instant), pub on_packets_acked: fn( @@ -1034,6 +1132,7 @@ impl From<CongestionControlAlgorithm> for &'static CongestionControlOps { match algo { CongestionControlAlgorithm::Reno => &reno::RENO, CongestionControlAlgorithm::CUBIC => &cubic::CUBIC, + CongestionControlAlgorithm::BBR => &bbr::BBR, } } } @@ -1046,7 +1145,7 @@ impl std::fmt::Debug for Recovery { if v > now { let d = v.duration_since(now); - write!(f, "timer={:?} ", d)?; + write!(f, "timer={d:?} ")?; } else { write!(f, "timer=exp ")?; } @@ -1073,12 +1172,7 @@ impl std::fmt::Debug for Recovery { self.congestion_recovery_start_time )?; write!(f, "{:?} ", self.delivery_rate)?; - write!(f, "pacing_rate={:?} ", self.pacing_rate)?; - write!( - f, - "last_packet_scheduled_time={:?} ", - self.last_packet_scheduled_time - )?; + write!(f, "pacer={:?} ", self.pacer)?; if self.hystart.enabled() { write!(f, "hystart={:?} ", self.hystart)?; @@ -1095,7 +1189,7 @@ impl std::fmt::Debug for Recovery { pub struct Sent { pub pkt_num: u64, - pub frames: Vec<frame::Frame>, + pub frames: SmallVec<[frame::Frame; 1]>, pub time_sent: Instant, @@ -1123,11 +1217,11 @@ pub struct Sent { impl std::fmt::Debug for Sent { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "pkt_num={:?} ", self.pkt_num)?; - write!(f, "pkt_sent_time={:?} ", self.time_sent.elapsed())?; + write!(f, "pkt_sent_time={:?} ", self.time_sent)?; write!(f, "pkt_size={:?} ", self.size)?; write!(f, "delivered={:?} ", self.delivered)?; - write!(f, "delivered_time={:?} ", self.delivered_time.elapsed())?; - write!(f, "first_sent_time={:?} ", self.first_sent_time.elapsed())?; + write!(f, "delivered_time={:?} ", self.delivered_time)?; + write!(f, "first_sent_time={:?} ", self.first_sent_time)?; write!(f, "is_app_limited={} ", self.is_app_limited)?; write!(f, "has_data={} ", self.has_data)?; @@ -1198,6 +1292,7 @@ struct QlogMetrics { cwnd: u64, bytes_in_flight: u64, ssthresh: u64, + pacing_rate: u64, } #[cfg(feature = "qlog")] @@ -1267,6 +1362,14 @@ impl QlogMetrics { None }; + let new_pacing_rate = if self.pacing_rate != latest.pacing_rate { + self.pacing_rate = latest.pacing_rate; + emit_event = true; + Some(latest.pacing_rate) + } else { + None + }; + if emit_event { // QVis can't use all these fields and they can be large. return Some(EventData::MetricsUpdated( @@ -1280,7 +1383,7 @@ impl QlogMetrics { bytes_in_flight: new_bytes_in_flight, ssthresh: new_ssthresh, packets_in_flight: None, - pacing_rate: None, + pacing_rate: new_pacing_rate, }, )); } @@ -1292,6 +1395,7 @@ impl QlogMetrics { #[cfg(test)] mod tests { use super::*; + use smallvec::smallvec; #[test] fn lookup_cc_algo_ok() { @@ -1303,7 +1407,7 @@ mod tests { fn lookup_cc_algo_bad() { assert_eq!( CongestionControlAlgorithm::from_str("???"), - Err(Error::CongestionControl) + Err(crate::Error::CongestionControl) ); } @@ -1328,12 +1432,12 @@ mod tests { let mut now = Instant::now(); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); // Start by sending a few packets. let p = Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1349,17 +1453,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 1); + assert_eq!(r.sent[packet::Epoch::Application].len(), 1); assert_eq!(r.bytes_in_flight, 1000); let p = Sent { pkt_num: 1, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1375,17 +1479,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 2000); let p = Sent { pkt_num: 2, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1401,17 +1505,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 3); + assert_eq!(r.sent[packet::Epoch::Application].len(), 3); assert_eq!(r.bytes_in_flight, 3000); let p = Sent { pkt_num: 3, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1427,12 +1531,12 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 4000); // Wait for 10ms. @@ -1446,15 +1550,15 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((0, 0)) ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 2000); assert_eq!(r.lost_count, 0); @@ -1463,13 +1567,13 @@ mod tests { // PTO. r.on_loss_detection_timeout(HandshakeStatus::default(), now, ""); - assert_eq!(r.loss_probes[packet::EPOCH_APPLICATION], 1); + assert_eq!(r.loss_probes[packet::Epoch::Application], 1); assert_eq!(r.lost_count, 0); assert_eq!(r.pto_count, 1); let p = Sent { pkt_num: 4, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1485,17 +1589,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 3); + assert_eq!(r.sent[packet::Epoch::Application].len(), 3); assert_eq!(r.bytes_in_flight, 3000); let p = Sent { pkt_num: 5, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1511,12 +1615,12 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 4000); assert_eq!(r.lost_count, 0); @@ -1531,15 +1635,15 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((2, 2000)) ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 0); assert_eq!(r.lost_count, 2); @@ -1547,9 +1651,9 @@ mod tests { // Wait 1 RTT. now += r.rtt(); - r.detect_lost_packets(packet::EPOCH_APPLICATION, now, ""); + r.detect_lost_packets(packet::Epoch::Application, now, ""); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); } #[test] @@ -1561,12 +1665,12 @@ mod tests { let mut now = Instant::now(); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); // Start by sending a few packets. let p = Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1582,17 +1686,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 1); + assert_eq!(r.sent[packet::Epoch::Application].len(), 1); assert_eq!(r.bytes_in_flight, 1000); let p = Sent { pkt_num: 1, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1608,17 +1712,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 2000); let p = Sent { pkt_num: 2, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1634,17 +1738,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 3); + assert_eq!(r.sent[packet::Epoch::Application].len(), 3); assert_eq!(r.bytes_in_flight, 3000); let p = Sent { pkt_num: 3, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1660,12 +1764,12 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 4000); // Wait for 10ms. @@ -1680,15 +1784,15 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((0, 0)) ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 1000); assert_eq!(r.lost_count, 0); @@ -1697,9 +1801,9 @@ mod tests { // Packet is declared lost. r.on_loss_detection_timeout(HandshakeStatus::default(), now, ""); - assert_eq!(r.loss_probes[packet::EPOCH_APPLICATION], 0); + assert_eq!(r.loss_probes[packet::Epoch::Application], 0); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 0); assert_eq!(r.lost_count, 1); @@ -1707,9 +1811,9 @@ mod tests { // Wait 1 RTT. now += r.rtt(); - r.detect_lost_packets(packet::EPOCH_APPLICATION, now, ""); + r.detect_lost_packets(packet::Epoch::Application, now, ""); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); } #[test] @@ -1721,12 +1825,12 @@ mod tests { let mut now = Instant::now(); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); // Start by sending a few packets. let p = Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1742,17 +1846,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 1); + assert_eq!(r.sent[packet::Epoch::Application].len(), 1); assert_eq!(r.bytes_in_flight, 1000); let p = Sent { pkt_num: 1, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1768,17 +1872,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); assert_eq!(r.bytes_in_flight, 2000); let p = Sent { pkt_num: 2, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1794,17 +1898,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 3); + assert_eq!(r.sent[packet::Epoch::Application].len(), 3); assert_eq!(r.bytes_in_flight, 3000); let p = Sent { pkt_num: 3, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -1820,12 +1924,12 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 4000); // Wait for 10ms. @@ -1839,12 +1943,12 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((1, 1000)) ); now += Duration::from_millis(10); @@ -1858,15 +1962,15 @@ mod tests { r.on_ack_received( &acked, 25, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((0, 0)) ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 4); + assert_eq!(r.sent[packet::Epoch::Application].len(), 4); assert_eq!(r.bytes_in_flight, 0); // Spurious loss. @@ -1879,9 +1983,9 @@ mod tests { // Wait 1 RTT. now += r.rtt(); - r.detect_lost_packets(packet::EPOCH_APPLICATION, now, ""); + r.detect_lost_packets(packet::Epoch::Application, now, ""); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); } #[test] @@ -1893,16 +1997,16 @@ mod tests { let mut now = Instant::now(); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); - // send out first packet. + // send out first packet (a full initcwnd). let p = Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, - size: 6500, + size: 12000, ack_eliciting: true, in_flight: true, delivered: 0, @@ -1914,17 +2018,17 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 1); - assert_eq!(r.bytes_in_flight, 6500); + assert_eq!(r.sent[packet::Epoch::Application].len(), 1); + assert_eq!(r.bytes_in_flight, 12000); - // First packet will be sent out immidiately. - assert_eq!(r.pacing_rate, 0); + // First packet will be sent out immediately. + assert_eq!(r.pacer.rate(), 0); assert_eq!(r.get_packet_send_time(), now); // Wait 50ms for ACK. @@ -1937,26 +2041,29 @@ mod tests { r.on_ack_received( &acked, 10, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "" ), - Ok(()) + Ok((0, 0)) ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 0); + assert_eq!(r.sent[packet::Epoch::Application].len(), 0); assert_eq!(r.bytes_in_flight, 0); assert_eq!(r.smoothed_rtt.unwrap(), Duration::from_millis(50)); + // 1 MSS increased. + assert_eq!(r.congestion_window, 12000 + 1200); + // Send out second packet. let p = Sent { pkt_num: 1, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, - size: 6500, + size: 6000, ack_eliciting: true, in_flight: true, delivered: 0, @@ -1968,26 +2075,26 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 1); - assert_eq!(r.bytes_in_flight, 6500); + assert_eq!(r.sent[packet::Epoch::Application].len(), 1); + assert_eq!(r.bytes_in_flight, 6000); - // Pacing is not done during intial phase of connection. + // Pacing is not done during initial phase of connection. assert_eq!(r.get_packet_send_time(), now); // Send the third packet out. let p = Sent { pkt_num: 2, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, - size: 6500, + size: 6000, ack_eliciting: true, in_flight: true, delivered: 0, @@ -1999,29 +2106,60 @@ mod tests { r.on_packet_sent( p, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, HandshakeStatus::default(), now, "", ); - assert_eq!(r.sent[packet::EPOCH_APPLICATION].len(), 2); + assert_eq!(r.sent[packet::Epoch::Application].len(), 2); + assert_eq!(r.bytes_in_flight, 12000); + + // Send the third packet out. + let p = Sent { + pkt_num: 3, + frames: smallvec![], + time_sent: now, + time_acked: None, + time_lost: None, + size: 1000, + ack_eliciting: true, + in_flight: true, + delivered: 0, + delivered_time: now, + first_sent_time: now, + is_app_limited: false, + has_data: false, + }; + + r.on_packet_sent( + p, + packet::Epoch::Application, + HandshakeStatus::default(), + now, + "", + ); + + assert_eq!(r.sent[packet::Epoch::Application].len(), 3); assert_eq!(r.bytes_in_flight, 13000); - assert_eq!(r.smoothed_rtt.unwrap(), Duration::from_millis(50)); // We pace this outgoing packet. as all conditions for pacing // are passed. - let pacing_rate = (12000.0 * PACING_MULTIPLIER / 0.05) as u64; - assert_eq!(r.pacing_rate, pacing_rate); + let pacing_rate = + (r.congestion_window as f64 * PACING_MULTIPLIER / 0.05) as u64; + assert_eq!(r.pacer.rate(), pacing_rate); + assert_eq!( r.get_packet_send_time(), - now + Duration::from_secs_f64(6500.0 / pacing_rate as f64) + now + Duration::from_secs_f64(12000.0 / pacing_rate as f64) ); } } +mod bbr; mod cubic; mod delivery_rate; mod hystart; +mod pacer; mod prr; mod reno; diff --git a/src/recovery/pacer.rs b/src/recovery/pacer.rs new file mode 100644 index 0000000..ab54364 --- /dev/null +++ b/src/recovery/pacer.rs @@ -0,0 +1,250 @@ +// Copyright (C) 2022, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Pacer provides the timestamp for the next packet to be sent based on the +//! current send_quantum, pacing rate and last updated time. +//! +//! It's a kind of leaky bucket algorithm (RFC9002, 7.7 Pacing) but it considers +//! max burst (send_quantum, in bytes) and provide the same timestamp for the +//! same sized packets (except last one) to be GSO friendly, assuming we send +//! packets using multiple sendmsg(), a sendmmsg(), or sendmsg() with GSO +//! without waiting for new I/O events. +//! +//! After sending a burst of packets, the next timestamp will be updated based +//! on the current pacing rate. It will make actual timestamp sent and recorded +//! timestamp (Sent.time_sent) as close as possible. If GSO is not used, it will +//! still try to provide close timestamp if the send burst is implemented. + +use std::time::Duration; +use std::time::Instant; + +#[derive(Debug)] +pub struct Pacer { + /// Whether pacing is enabled. + enabled: bool, + + /// Bucket capacity (bytes). + capacity: usize, + + /// Bucket used (bytes). + used: usize, + + /// Sending pacing rate (bytes/sec). + rate: u64, + + /// Timestamp of the last packet sent time update. + last_update: Instant, + + /// Timestamp of the next packet to be sent. + next_time: Instant, + + /// Current MSS. + max_datagram_size: usize, + + /// Last packet size. + last_packet_size: Option<usize>, + + /// Interval to be added in next burst. + iv: Duration, +} + +impl Pacer { + pub fn new( + enabled: bool, capacity: usize, rate: u64, max_datagram_size: usize, + ) -> Self { + // Round capacity to MSS. + let capacity = capacity / max_datagram_size * max_datagram_size; + + Pacer { + enabled, + + capacity, + + used: 0, + + rate, + + last_update: Instant::now(), + + next_time: Instant::now(), + + max_datagram_size, + + last_packet_size: None, + + iv: Duration::ZERO, + } + } + + /// Returns whether pacing is enabled. + pub fn enabled(&self) -> bool { + self.enabled + } + + /// Returns the current pacing rate. + pub fn rate(&self) -> u64 { + self.rate + } + + /// Updates the bucket capacity or pacing_rate. + pub fn update(&mut self, capacity: usize, rate: u64, now: Instant) { + let capacity = capacity / self.max_datagram_size * self.max_datagram_size; + + if self.capacity != capacity { + self.reset(now); + } + + self.capacity = capacity; + + self.rate = rate; + } + + /// Resets the pacer for the next burst. + pub fn reset(&mut self, now: Instant) { + self.used = 0; + + self.last_update = now; + + self.next_time = self.next_time.max(now); + + self.last_packet_size = None; + + self.iv = Duration::ZERO; + } + + /// Updates the timestamp for the packet to send. + pub fn send(&mut self, packet_size: usize, now: Instant) { + if self.rate == 0 { + self.reset(now); + + return; + } + + if !self.iv.is_zero() { + self.next_time = self.next_time.max(now) + self.iv; + + self.iv = Duration::ZERO; + } + + let interval = + Duration::from_secs_f64(self.capacity as f64 / self.rate as f64); + + let elapsed = now.saturating_duration_since(self.last_update); + + // If too old, reset it. + if elapsed > interval { + self.reset(now); + } + + self.used += packet_size; + + let same_size = if let Some(last_packet_size) = self.last_packet_size { + last_packet_size == packet_size + } else { + true + }; + + self.last_packet_size = Some(packet_size); + + if self.used >= self.capacity || !same_size { + self.iv = + Duration::from_secs_f64(self.used as f64 / self.rate as f64); + + self.used = 0; + + self.last_update = now; + + self.last_packet_size = None; + }; + } + + /// Returns the timestamp for the next packet. + pub fn next_time(&self) -> Instant { + self.next_time + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pacer_update() { + let datagram_size = 1200; + let max_burst = datagram_size * 10; + let pacing_rate = 100_000; + + let mut p = Pacer::new(true, max_burst, pacing_rate, datagram_size); + + let now = Instant::now(); + + // Send 6000 (half of max_burst) -> no timestamp change yet. + p.send(6000, now); + + assert!(now.duration_since(p.next_time()) < Duration::from_millis(1)); + + // Send 6000 bytes -> max_burst filled. + p.send(6000, now); + + assert!(now.duration_since(p.next_time()) < Duration::from_millis(1)); + + // Start of a new burst. + let now = now + Duration::from_millis(5); + + // Send 1000 bytes and next_time is updated. + p.send(1000, now); + + let interval = max_burst as f64 / pacing_rate as f64; + + assert_eq!(p.next_time() - now, Duration::from_secs_f64(interval)); + } + + #[test] + /// Same as pacer_update() but adds some idle time between transfers to + /// trigger a reset. + fn pacer_idle() { + let datagram_size = 1200; + let max_burst = datagram_size * 10; + let pacing_rate = 100_000; + + let mut p = Pacer::new(true, max_burst, pacing_rate, datagram_size); + + let now = Instant::now(); + + // Send 6000 (half of max_burst) -> no timestamp change yet. + p.send(6000, now); + + assert!(now.duration_since(p.next_time()) < Duration::from_millis(1)); + + // Sleep 200ms to reset the idle pacer (at least 120ms). + let now = now + Duration::from_millis(200); + + // Send 6000 bytes -> idle reset and a new burst isstarted. + p.send(6000, now); + + assert_eq!(p.next_time(), now); + } +} diff --git a/src/recovery/reno.rs b/src/recovery/reno.rs index eb8942b..0b4a6c3 100644 --- a/src/recovery/reno.rs +++ b/src/recovery/reno.rs @@ -40,6 +40,7 @@ use crate::recovery::Recovery; pub static RENO: CongestionControlOps = CongestionControlOps { on_init, + reset, on_packet_sent, on_packets_acked, congestion_event, @@ -52,6 +53,8 @@ pub static RENO: CongestionControlOps = CongestionControlOps { pub fn on_init(_r: &mut Recovery) {} +pub fn reset(_r: &mut Recovery) {} + pub fn on_packet_sent(r: &mut Recovery, sent_bytes: usize, _now: Instant) { r.bytes_in_flight += sent_bytes; } @@ -160,6 +163,7 @@ fn debug_fmt(_r: &Recovery, _f: &mut std::fmt::Formatter) -> std::fmt::Result { mod tests { use super::*; + use smallvec::smallvec; use std::time::Duration; #[test] @@ -198,7 +202,7 @@ mod tests { let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -230,7 +234,7 @@ mod tests { rtt: Duration::ZERO, }]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); // Check if cwnd increased by packet size (slow start). assert_eq!(r.cwnd(), cwnd_prev + p.size); @@ -247,7 +251,7 @@ mod tests { let p = recovery::Sent { pkt_num: 0, - frames: vec![], + frames: smallvec![], time_sent: now, time_acked: None, time_lost: None, @@ -301,7 +305,7 @@ mod tests { }, ]; - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now); + r.on_packets_acked(acked, packet::Epoch::Application, now); // Acked 3 packets. assert_eq!(r.cwnd(), cwnd_prev + p.size * 3); @@ -321,7 +325,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -345,7 +349,7 @@ mod tests { r.congestion_event( r.max_datagram_size, now, - packet::EPOCH_APPLICATION, + packet::Epoch::Application, now, ); @@ -371,7 +375,7 @@ mod tests { // Ack more than cwnd bytes with rtt=100ms r.update_rtt(rtt, Duration::from_millis(0), now); - r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now + rtt * 2); + r.on_packets_acked(acked, packet::Epoch::Application, now + rtt * 2); // After acking more than cwnd, expect cwnd increased by MSS assert_eq!(r.cwnd(), cur_cwnd + r.max_datagram_size); diff --git a/src/stream.rs b/src/stream.rs index 80e174f..6e978bb 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -29,7 +29,6 @@ use std::cmp; use std::sync::Arc; use std::collections::hash_map; - use std::collections::BTreeMap; use std::collections::BinaryHeap; use std::collections::HashMap; @@ -38,6 +37,8 @@ use std::collections::VecDeque; use std::time; +use smallvec::SmallVec; + use crate::Error; use crate::Result; @@ -146,13 +147,13 @@ pub struct StreamMap { /// Set of stream IDs corresponding to streams that have outstanding data /// to read. This is used to generate a `StreamIter` of streams without /// having to iterate over the full list of streams. - readable: StreamIdHashSet, + pub readable: StreamIdHashSet, /// Set of stream IDs corresponding to streams that have enough flow control /// capacity to be written to, and is not finished. This is used to generate /// a `StreamIter` of streams without having to iterate over the full list /// of streams. - writable: StreamIdHashSet, + pub writable: StreamIdHashSet, /// Set of stream IDs corresponding to streams that are almost out of flow /// control credit and need to send MAX_STREAM_DATA. This is used to @@ -222,7 +223,7 @@ impl StreamMap { &mut self, id: u64, local_params: &crate::TransportParams, peer_params: &crate::TransportParams, local: bool, is_server: bool, ) -> Result<&mut Stream> { - let stream = match self.streams.entry(id) { + let (stream, is_new_and_writable) = match self.streams.entry(id) { hash_map::Entry::Vacant(v) => { // Stream has already been closed and garbage collected. if self.collected.contains(&id) { @@ -254,46 +255,63 @@ impl StreamMap { (local_params.initial_max_stream_data_uni, 0), }; + // The two least significant bits from a stream id identify the + // type of stream. Truncate those bits to get the sequence for + // that stream type. + let stream_sequence = id >> 2; + // Enforce stream count limits. match (is_local(id, is_server), is_bidi(id)) { (true, true) => { - if self.local_opened_streams_bidi >= - self.peer_max_streams_bidi - { + let n = std::cmp::max( + self.local_opened_streams_bidi, + stream_sequence + 1, + ); + + if n > self.peer_max_streams_bidi { return Err(Error::StreamLimit); } - self.local_opened_streams_bidi += 1; + self.local_opened_streams_bidi = n; }, (true, false) => { - if self.local_opened_streams_uni >= - self.peer_max_streams_uni - { + let n = std::cmp::max( + self.local_opened_streams_uni, + stream_sequence + 1, + ); + + if n > self.peer_max_streams_uni { return Err(Error::StreamLimit); } - self.local_opened_streams_uni += 1; + self.local_opened_streams_uni = n; }, (false, true) => { - if self.peer_opened_streams_bidi >= - self.local_max_streams_bidi - { + let n = std::cmp::max( + self.peer_opened_streams_bidi, + stream_sequence + 1, + ); + + if n > self.local_max_streams_bidi { return Err(Error::StreamLimit); } - self.peer_opened_streams_bidi += 1; + self.peer_opened_streams_bidi = n; }, (false, false) => { - if self.peer_opened_streams_uni >= - self.local_max_streams_uni - { + let n = std::cmp::max( + self.peer_opened_streams_uni, + stream_sequence + 1, + ); + + if n > self.local_max_streams_uni { return Err(Error::StreamLimit); } - self.peer_opened_streams_uni += 1; + self.peer_opened_streams_uni = n; }, }; @@ -304,14 +322,18 @@ impl StreamMap { local, self.max_stream_window, ); - v.insert(s) + + let is_writable = s.is_writable(); + + (v.insert(s), is_writable) }, - hash_map::Entry::Occupied(v) => v.into_mut(), + hash_map::Entry::Occupied(v) => (v.into_mut(), false), }; - // Stream might already be writable due to initial flow control limits. - if stream.is_writable() { + // Newly created stream might already be writable due to initial flow + // control limits. + if is_new_and_writable { self.writable.insert(id); } @@ -344,41 +366,42 @@ impl StreamMap { }; } - /// Removes and returns the first stream ID from the flushable streams - /// queue with the specified urgency. + /// Returns the first stream ID from the flushable streams + /// queue with the highest urgency. /// - /// Note that if the stream is still flushable after sending some of its - /// outstanding data, it needs to be added back to the queue. - pub fn pop_flushable(&mut self) -> Option<u64> { - // Remove the first element from the queue corresponding to the lowest - // urgency that has elements. - let (node, clear) = - if let Some((urgency, queues)) = self.flushable.iter_mut().next() { - let node = if !queues.0.is_empty() { - queues.0.pop().map(|x| x.0) - } else { - queues.1.pop_front() - }; - - let clear = if queues.0.is_empty() && queues.1.is_empty() { - Some(*urgency) + /// Note that if the stream is no longer flushable after sending some of its + /// outstanding data, it needs to be removed from the queue. + pub fn peek_flushable(&mut self) -> Option<u64> { + self.flushable.iter_mut().next().and_then(|(_, queues)| { + queues.0.peek().map(|x| x.0).or_else(|| { + // When peeking incremental streams, make sure to move the current + // stream to the end of the queue so they are pocesses in a round + // robin fashion + if let Some(current_incremental) = queues.1.pop_front() { + queues.1.push_back(current_incremental); + Some(current_incremental) } else { None - }; + } + }) + }) + } - (node, clear) - } else { - (None, None) - }; + /// Remove the last peeked stream + pub fn remove_flushable(&mut self) { + let mut top_urgency = self + .flushable + .first_entry() + .expect("Remove previously peeked stream"); + let queues = top_urgency.get_mut(); + queues.0.pop().map(|x| x.0).or_else(|| queues.1.pop_back()); // Remove the queue from the list of queues if it is now empty, so that // the next time `pop_flushable()` is called the next queue with elements // is used. - if let Some(urgency) = &clear { - self.flushable.remove(urgency); + if queues.0.is_empty() && queues.1.is_empty() { + top_urgency.remove(); } - - node } /// Adds or removes the stream ID to/from the readable streams set. @@ -521,6 +544,9 @@ impl StreamMap { } } + self.mark_readable(stream_id, false); + self.mark_writable(stream_id, false); + self.streams.remove(&stream_id); self.collected.insert(stream_id); } @@ -623,6 +649,8 @@ pub struct Stream { /// Send-side stream buffer. pub send: SendBuf, + pub send_lowat: usize, + /// Whether the stream is bidirectional. pub bidi: bool, @@ -648,6 +676,7 @@ impl Stream { Stream { recv: RecvBuf::new(max_rx_data, max_window), send: SendBuf::new(max_tx_data), + send_lowat: 1, bidi, local, data: None, @@ -666,7 +695,7 @@ impl Stream { pub fn is_writable(&self) -> bool { !self.send.shutdown && !self.send.is_fin() && - self.send.off < self.send.max_data + (self.send.off + self.send_lowat as u64) < self.send.max_data } /// Returns true if the stream has data to send and is allowed to send at @@ -699,6 +728,11 @@ impl Stream { (false, false) => self.recv.is_fin(), } } + + /// Returns true if the stream is not storing incoming data. + pub fn is_draining(&self) -> bool { + self.recv.drain + } } /// Returns true if the stream was created locally. @@ -714,7 +748,7 @@ pub fn is_bidi(stream_id: u64) -> bool { /// An iterator over QUIC streams. #[derive(Default)] pub struct StreamIter { - streams: Vec<u64>, + streams: SmallVec<[u64; 8]>, } impl StreamIter { @@ -751,7 +785,7 @@ impl ExactSizeIterator for StreamIter { pub struct RecvBuf { /// Chunks of data received from the peer that have not yet been read by /// the application, ordered by offset. - data: BinaryHeap<RangeBuf>, + data: BTreeMap<u64, RangeBuf>, /// The lowest data offset that has yet to be read by the application. off: u64, @@ -818,12 +852,6 @@ impl RecvBuf { return Ok(()); } - // No need to process an empty buffer with the fin flag, if we already - // know the final size. - if buf.fin() && buf.is_empty() && self.fin_off.is_some() { - return Ok(()); - } - if buf.fin() { self.fin_off = Some(buf.max_off()); } @@ -866,22 +894,28 @@ impl RecvBuf { // flag set, which is the only kind of empty buffer that should // reach this point). if buf.off() < self.max_off() || buf.is_empty() { - for b in &self.data { + for (_, b) in self.data.range(buf.off()..) { + let off = buf.off(); + + // We are past the current buffer. + if b.off() > buf.max_off() { + break; + } + // New buffer is fully contained in existing buffer. - if buf.off() >= b.off() && buf.max_off() <= b.max_off() { + if off >= b.off() && buf.max_off() <= b.max_off() { continue 'tmp; } // New buffer's start overlaps existing buffer. - if buf.off() >= b.off() && buf.off() < b.max_off() { - buf = buf.split_off((b.max_off() - buf.off()) as usize); + if off >= b.off() && off < b.max_off() { + buf = buf.split_off((b.max_off() - off) as usize); } // New buffer's end overlaps existing buffer. - if buf.off() < b.off() && buf.max_off() > b.off() { - tmp_bufs.push_back( - buf.split_off((b.off() - buf.off()) as usize), - ); + if off < b.off() && buf.max_off() > b.off() { + tmp_bufs + .push_back(buf.split_off((b.off() - off) as usize)); } } } @@ -889,7 +923,7 @@ impl RecvBuf { self.len = cmp::max(self.len, buf.max_off()); if !self.drain { - self.data.push(buf); + self.data.insert(buf.max_off(), buf); } } @@ -919,12 +953,13 @@ impl RecvBuf { } while cap > 0 && self.ready() { - let mut buf = match self.data.peek_mut() { - Some(v) => v, - + let mut entry = match self.data.first_entry() { + Some(entry) => entry, None => break, }; + let buf = entry.get_mut(); + let buf_len = cmp::min(buf.len(), cap); out[len..len + buf_len].copy_from_slice(&buf[..buf_len]); @@ -941,7 +976,7 @@ impl RecvBuf { break; } - std::collections::binary_heap::PeekMut::pop(buf); + entry.remove(); } // Update consumed bytes for flow control. @@ -1056,9 +1091,8 @@ impl RecvBuf { /// Returns true if the stream has data to be read. fn ready(&self) -> bool { - let buf = match self.data.peek() { + let (_, buf) = match self.data.first_key_value() { Some(v) => v, - None => return false, }; @@ -1086,6 +1120,10 @@ pub struct SendBuf { /// The maximum offset of data buffered in the stream. off: u64, + /// The maximum offset of data sent to the peer, regardless of + /// retransmissions. + emit_off: u64, + /// The amount of data currently buffered. len: u64, @@ -1214,8 +1252,7 @@ impl SendBuf { // Copy data to the output buffer. let out_pos = (next_off - out_off) as usize; - (&mut out[out_pos..out_pos + buf_len]) - .copy_from_slice(&buf[..buf_len]); + out[out_pos..out_pos + buf_len].copy_from_slice(&buf[..buf_len]); self.len -= buf_len as u64; @@ -1241,6 +1278,10 @@ impl SendBuf { // propagate the final size. let fin = self.fin_off == Some(next_off); + // Record the largest offset that has been sent so we can accurately + // report final_size + self.emit_off = cmp::max(self.emit_off, next_off); + Ok((out.len() - out_len, fin)) } @@ -1333,13 +1374,15 @@ impl SendBuf { // Split the buffer into 2 if the retransmit range ends before the // buffer's final offset. let new_buf = if buf.off < max_off && max_off < buf.max_off() { - Some(buf.split_off((max_off - buf.off as u64) as usize)) + Some(buf.split_off((max_off - buf.off) as usize)) } else { None }; - // Advance the buffer's position if the retransmit range is past - // the buffer's starting offset. + let prev_pos = buf.pos; + + // Reduce the buffer's position (expand the buffer) if the retransmit + // range is past the buffer's starting offset. buf.pos = if off > buf.off && off <= buf.max_off() { cmp::min(buf.pos, buf.start + (off - buf.off) as usize) } else { @@ -1348,7 +1391,7 @@ impl SendBuf { self.pos = cmp::min(self.pos, i); - self.len += buf.len() as u64; + self.len += (prev_pos - buf.pos) as u64; if let Some(b) = new_buf { self.data.insert(i + 1, b); @@ -1357,9 +1400,9 @@ impl SendBuf { } /// Resets the stream at the current offset and clears all buffered data. - pub fn reset(&mut self) -> Result<(u64, u64)> { - let unsent_off = self.off_front(); - let unsent_len = self.off_back() - unsent_off; + pub fn reset(&mut self) -> (u64, u64) { + let unsent_off = cmp::max(self.off_front(), self.emit_off); + let unsent_len = self.off_back().saturating_sub(unsent_off); self.fin_off = Some(unsent_off); @@ -1373,7 +1416,7 @@ impl SendBuf { self.len = 0; self.off = unsent_off; - Ok((self.fin_off.unwrap(), unsent_len)) + (self.emit_off, unsent_len) } /// Resets the streams and records the received error code. @@ -1384,11 +1427,11 @@ impl SendBuf { return Err(Error::Done); } - let (fin_off, unsent) = self.reset()?; + let (max_off, unsent) = self.reset(); self.error = Some(error_code); - Ok((fin_off, unsent)) + Ok((max_off, unsent)) } /// Shuts down sending data. @@ -1399,7 +1442,7 @@ impl SendBuf { self.shutdown = true; - self.reset() + Ok(self.reset()) } /// Returns the largest offset of data buffered. @@ -1627,7 +1670,7 @@ mod tests { #[test] fn empty_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1693,7 +1736,7 @@ mod tests { #[test] fn ordered_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1730,7 +1773,7 @@ mod tests { #[test] fn split_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1770,7 +1813,7 @@ mod tests { #[test] fn incomplete_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1798,7 +1841,7 @@ mod tests { #[test] fn zero_len_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1826,7 +1869,7 @@ mod tests { #[test] fn past_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1865,7 +1908,7 @@ mod tests { #[test] fn fully_overlapping_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1896,7 +1939,7 @@ mod tests { #[test] fn fully_overlapping_read2() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1927,7 +1970,7 @@ mod tests { #[test] fn fully_overlapping_read3() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1958,7 +2001,7 @@ mod tests { #[test] fn fully_overlapping_read_multi() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -1995,7 +2038,7 @@ mod tests { #[test] fn overlapping_start_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2025,7 +2068,7 @@ mod tests { #[test] fn overlapping_end_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2055,7 +2098,7 @@ mod tests { #[test] fn overlapping_end_twice_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2097,7 +2140,7 @@ mod tests { #[test] fn overlapping_end_twice_and_contained_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2139,7 +2182,7 @@ mod tests { #[test] fn partially_multi_overlapping_reordered_read() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2176,7 +2219,7 @@ mod tests { #[test] fn partially_multi_overlapping_reordered_read2() { - let mut recv = RecvBuf::new(std::u64::MAX, DEFAULT_STREAM_WINDOW); + let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW); assert_eq!(recv.len, 0); let mut buf = [0; 32]; @@ -2233,7 +2276,7 @@ mod tests { fn empty_write() { let mut buf = [0; 5]; - let mut send = SendBuf::new(std::u64::MAX); + let mut send = SendBuf::new(u64::MAX); assert_eq!(send.len, 0); let (written, fin) = send.emit(&mut buf).unwrap(); @@ -2245,7 +2288,7 @@ mod tests { fn multi_write() { let mut buf = [0; 128]; - let mut send = SendBuf::new(std::u64::MAX); + let mut send = SendBuf::new(u64::MAX); assert_eq!(send.len, 0); let first = b"something"; @@ -2268,7 +2311,7 @@ mod tests { fn split_write() { let mut buf = [0; 10]; - let mut send = SendBuf::new(std::u64::MAX); + let mut send = SendBuf::new(u64::MAX); assert_eq!(send.len, 0); let first = b"something"; @@ -2311,7 +2354,7 @@ mod tests { fn resend() { let mut buf = [0; 15]; - let mut send = SendBuf::new(std::u64::MAX); + let mut send = SendBuf::new(u64::MAX); assert_eq!(send.len, 0); assert_eq!(send.off_front(), 0); @@ -2444,7 +2487,7 @@ mod tests { fn zero_len_write() { let mut buf = [0; 10]; - let mut send = SendBuf::new(std::u64::MAX); + let mut send = SendBuf::new(u64::MAX); assert_eq!(send.len, 0); let first = b"something"; @@ -3234,4 +3277,120 @@ mod tests { assert_eq!(&new_new_buf[..], b""); } + + /// RFC9000 2.1: A stream ID that is used out of order results in all + /// streams of that type with lower-numbered stream IDs also being opened. + #[test] + fn stream_limit_auto_open() { + let local_tp = crate::TransportParams::default(); + let peer_tp = crate::TransportParams::default(); + + let mut streams = StreamMap::new(5, 5, 5); + + let stream_id = 500; + assert!(!is_local(stream_id, true), "stream id is peer initiated"); + assert!(is_bidi(stream_id), "stream id is bidirectional"); + assert_eq!( + streams + .get_or_create(stream_id, &local_tp, &peer_tp, false, true) + .err(), + Some(Error::StreamLimit), + "stream limit should be exceeded" + ); + } + + /// Stream limit should be satisfied regardless of what order we open + /// streams + #[test] + fn stream_create_out_of_order() { + let local_tp = crate::TransportParams::default(); + let peer_tp = crate::TransportParams::default(); + + let mut streams = StreamMap::new(5, 5, 5); + + for stream_id in [8, 12, 4] { + assert!(is_local(stream_id, false), "stream id is client initiated"); + assert!(is_bidi(stream_id), "stream id is bidirectional"); + assert!(streams + .get_or_create(stream_id, &local_tp, &peer_tp, false, true) + .is_ok()); + } + } + + /// Check stream limit boundary cases + #[test] + fn stream_limit_edge() { + let local_tp = crate::TransportParams::default(); + let peer_tp = crate::TransportParams::default(); + + let mut streams = StreamMap::new(3, 3, 3); + + // Highest permitted + let stream_id = 8; + assert!(streams + .get_or_create(stream_id, &local_tp, &peer_tp, false, true) + .is_ok()); + + // One more than highest permitted + let stream_id = 12; + assert_eq!( + streams + .get_or_create(stream_id, &local_tp, &peer_tp, false, true) + .err(), + Some(Error::StreamLimit) + ); + } + + /// Check SendBuf::len calculation on a retransmit case + #[test] + fn send_buf_len_on_retransmit() { + let mut buf = [0; 15]; + + let mut send = SendBuf::new(u64::MAX); + assert_eq!(send.len, 0); + assert_eq!(send.off_front(), 0); + + let first = b"something"; + + assert!(send.write(first, false).is_ok()); + assert_eq!(send.off_front(), 0); + + assert_eq!(send.len, 9); + + let (written, fin) = send.emit(&mut buf[..4]).unwrap(); + assert_eq!(written, 4); + assert_eq!(fin, false); + assert_eq!(&buf[..written], b"some"); + assert_eq!(send.len, 5); + assert_eq!(send.off_front(), 4); + + send.retransmit(3, 5); + assert_eq!(send.len, 6); + assert_eq!(send.off_front(), 3); + } + + #[test] + fn send_buf_final_size_retransmit() { + let mut buf = [0; 50]; + let mut send = SendBuf::new(u64::MAX); + + send.write(&buf, false).unwrap(); + assert_eq!(send.off_front(), 0); + + // Emit the whole buffer + let (written, _fin) = send.emit(&mut buf).unwrap(); + assert_eq!(written, buf.len()); + assert_eq!(send.off_front(), buf.len() as u64); + + // Server decides to retransmit the last 10 bytes. It's possible + // it's not actually lost and that the client did receive it. + send.retransmit(40, 10); + + // Server receives STOP_SENDING from client. The final_size we + // send in the RESET_STREAM should be 50. If we send anything less, + // it's a FINAL_SIZE_ERROR. + let (fin_off, unsent) = send.stop(0).unwrap(); + assert_eq!(fin_off, 50); + assert_eq!(unsent, 0); + } } @@ -47,6 +47,7 @@ use crate::packet; const TLS1_3_VERSION: u16 = 0x0304; const TLS_ALERT_ERROR: u64 = 0x100; +const INTERNAL_ERROR: u64 = 0x01; #[allow(non_camel_case_types)] #[repr(transparent)] @@ -122,6 +123,47 @@ struct SSL_QUIC_METHOD { extern fn(ssl: *mut SSL, level: crypto::Level, alert: u8) -> c_int, } +#[cfg(test)] +#[repr(C)] +#[allow(non_camel_case_types)] +#[allow(dead_code)] +enum ssl_private_key_result_t { + ssl_private_key_success, + ssl_private_key_retry, + ssl_private_key_failure, +} + +#[cfg(test)] +#[repr(C)] +#[allow(non_camel_case_types)] +struct SSL_PRIVATE_KEY_METHOD { + sign: extern fn( + ssl: *mut SSL, + out: *mut u8, + out_len: *mut usize, + max_out: usize, + signature_algorithm: u16, + r#in: *const u8, + in_len: usize, + ) -> ssl_private_key_result_t, + + decrypt: extern fn( + ssl: *mut SSL, + out: *mut u8, + out_len: *mut usize, + max_out: usize, + r#in: *const u8, + in_len: usize, + ) -> ssl_private_key_result_t, + + complete: extern fn( + ssl: *mut SSL, + out: *mut u8, + out_len: *mut usize, + max_out: usize, + ) -> ssl_private_key_result_t, +} + lazy_static::lazy_static! { /// BoringSSL Extra Data Index for Quiche Connections pub static ref QUICHE_EX_DATA_INDEX: c_int = unsafe { @@ -167,7 +209,7 @@ impl Context { pub fn new_handshake(&mut self) -> Result<Handshake> { unsafe { let ssl = SSL_new(self.as_mut_ptr()); - Ok(Handshake(ssl)) + Ok(Handshake::new(ssl)) } } @@ -277,11 +319,9 @@ impl Context { } pub fn set_verify(&mut self, verify: bool) { - let mode = if verify { - 0x01 // SSL_VERIFY_PEER - } else { - 0x00 // SSL_VERIFY_NONE - }; + // true -> 0x01 SSL_VERIFY_PEER + // false -> 0x00 SSL_VERIFY_NONE + let mode = i32::from(verify); unsafe { SSL_CTX_set_verify(self.as_mut_ptr(), mode, ptr::null()); @@ -294,12 +334,12 @@ impl Context { } } - pub fn set_alpn(&mut self, v: &[Vec<u8>]) -> Result<()> { + pub fn set_alpn(&mut self, v: &[&[u8]]) -> Result<()> { let mut protos: Vec<u8> = Vec::new(); for proto in v { protos.push(proto.len() as u8); - protos.append(&mut proto.clone()); + protos.extend_from_slice(proto); } // Configure ALPN for servers. @@ -332,7 +372,7 @@ impl Context { } pub fn set_early_data_enabled(&mut self, enabled: bool) { - let enabled = if enabled { 1 } else { 0 }; + let enabled = i32::from(enabled); unsafe { SSL_CTX_set_early_data_enabled(self.as_mut_ptr(), enabled); @@ -358,13 +398,25 @@ impl Drop for Context { } } -pub struct Handshake(*mut SSL); +pub struct Handshake { + /// Raw pointer + ptr: *mut SSL, + /// SSL_process_quic_post_handshake should be called when whenever + /// SSL_provide_quic_data is called to process the provided data. + provided_data_outstanding: bool, +} impl Handshake { #[cfg(feature = "ffi")] pub unsafe fn from_ptr(ssl: *mut c_void) -> Handshake { - let ssl = ssl as *mut SSL; - Handshake(ssl) + Handshake::new(ssl as *mut SSL) + } + + fn new(ptr: *mut SSL) -> Handshake { + Handshake { + ptr, + provided_data_outstanding: false, + } } pub fn get_error(&self, ret_code: c_int) -> c_int { @@ -439,16 +491,14 @@ impl Handshake { } pub fn set_quiet_shutdown(&mut self, mode: bool) { - unsafe { - SSL_set_quiet_shutdown(self.as_mut_ptr(), if mode { 1 } else { 0 }) - } + unsafe { SSL_set_quiet_shutdown(self.as_mut_ptr(), i32::from(mode)) } } pub fn set_host_name(&mut self, name: &str) -> Result<()> { let cstr = ffi::CString::new(name).map_err(|_| Error::TlsFail)?; let rc = unsafe { SSL_set_tlsext_host_name(self.as_mut_ptr(), cstr.as_ptr()) }; - map_result_ssl(self, rc)?; + self.map_result_ssl(rc)?; let param = unsafe { SSL_get0_param(self.as_mut_ptr()) }; @@ -465,14 +515,7 @@ impl Handshake { buf.len(), ) }; - map_result_ssl(self, rc) - } - - #[cfg(test)] - pub fn set_options(&mut self, opts: u32) { - unsafe { - SSL_set_options(self.as_mut_ptr(), opts); - } + self.map_result_ssl(rc) } pub fn quic_transport_params(&self) -> &[u8] { @@ -547,6 +590,7 @@ impl Handshake { pub fn provide_data( &mut self, level: crypto::Level, buf: &[u8], ) -> Result<()> { + self.provided_data_outstanding = true; let rc = unsafe { SSL_provide_quic_data( self.as_mut_ptr(), @@ -555,7 +599,7 @@ impl Handshake { buf.len(), ) }; - map_result_ssl(self, rc) + self.map_result_ssl(rc) } pub fn do_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { @@ -563,15 +607,24 @@ impl Handshake { let rc = unsafe { SSL_do_handshake(self.as_mut_ptr()) }; self.set_ex_data::<Connection>(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; - map_result_ssl(self, rc) + self.set_transport_error(ex_data, rc); + self.map_result_ssl(rc) } pub fn process_post_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { + // If SSL_provide_quic_data hasn't been called since we last called + // SSL_process_quic_post_handshake, then there's nothing to do. + if !self.provided_data_outstanding { + return Ok(()); + } + self.provided_data_outstanding = false; + self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?; let rc = unsafe { SSL_process_quic_post_handshake(self.as_mut_ptr()) }; self.set_ex_data::<Connection>(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; - map_result_ssl(self, rc) + self.set_transport_error(ex_data, rc); + self.map_result_ssl(rc) } pub fn reset_early_data_reject(&mut self) { @@ -625,6 +678,39 @@ impl Handshake { Some(sigalg.to_string()) } + pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> { + let cert_chain = unsafe { + let chain = + map_result_ptr(SSL_get0_peer_certificates(self.as_ptr())).ok()?; + + let num = sk_num(chain); + if num <= 0 { + return None; + } + + let mut cert_chain = vec![]; + for i in 0..num { + let buffer = + map_result_ptr(sk_value(chain, i) as *const CRYPTO_BUFFER) + .ok()?; + + let out_len = CRYPTO_BUFFER_len(buffer); + if out_len == 0 { + return None; + } + + let out = CRYPTO_BUFFER_data(buffer); + let slice = slice::from_raw_parts(out, out_len); + + cert_chain.push(slice); + } + + cert_chain + }; + + Some(cert_chain) + } + pub fn peer_cert(&self) -> Option<&[u8]> { let peer_cert = unsafe { let chain = @@ -643,12 +729,57 @@ impl Handshake { } let out = CRYPTO_BUFFER_data(buffer); - slice::from_raw_parts(out, out_len as usize) + slice::from_raw_parts(out, out_len) }; Some(peer_cert) } + #[cfg(test)] + pub fn set_options(&mut self, opts: u32) { + unsafe { + SSL_set_options(self.as_mut_ptr(), opts); + } + } + + // Only used for testing handling of failure during key signing. + #[cfg(test)] + pub fn set_failing_private_key_method(&mut self) { + extern fn failing_sign( + _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize, + _signature_algorithm: u16, _in: *const u8, _in_len: usize, + ) -> ssl_private_key_result_t { + ssl_private_key_result_t::ssl_private_key_failure + } + + extern fn failing_decrypt( + _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize, + _in: *const u8, _in_len: usize, + ) -> ssl_private_key_result_t { + ssl_private_key_result_t::ssl_private_key_failure + } + + extern fn failing_complete( + _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize, + ) -> ssl_private_key_result_t { + ssl_private_key_result_t::ssl_private_key_failure + } + + static QUICHE_PRIVATE_KEY_METHOD: SSL_PRIVATE_KEY_METHOD = + SSL_PRIVATE_KEY_METHOD { + decrypt: failing_decrypt, + sign: failing_sign, + complete: failing_complete, + }; + + unsafe { + SSL_set_private_key_method( + self.as_mut_ptr(), + &QUICHE_PRIVATE_KEY_METHOD, + ); + } + } + pub fn is_completed(&self) -> bool { unsafe { SSL_in_init(self.as_ptr()) == 0 } } @@ -663,15 +794,84 @@ impl Handshake { pub fn clear(&mut self) -> Result<()> { let rc = unsafe { SSL_clear(self.as_mut_ptr()) }; - map_result_ssl(self, rc) + self.map_result_ssl(rc) } fn as_ptr(&self) -> *const SSL { - self.0 + self.ptr } fn as_mut_ptr(&mut self) -> *mut SSL { - self.0 + self.ptr + } + + fn map_result_ssl(&mut self, bssl_result: c_int) -> Result<()> { + match bssl_result { + 1 => Ok(()), + + _ => { + let ssl_err = self.get_error(bssl_result); + match ssl_err { + // SSL_ERROR_SSL + 1 => { + log_ssl_error(); + + Err(Error::TlsFail) + }, + + // SSL_ERROR_WANT_READ + 2 => Err(Error::Done), + + // SSL_ERROR_WANT_WRITE + 3 => Err(Error::Done), + + // SSL_ERROR_WANT_X509_LOOKUP + 4 => Err(Error::Done), + + // SSL_ERROR_SYSCALL + 5 => Err(Error::TlsFail), + + // SSL_ERROR_PENDING_SESSION + 11 => Err(Error::Done), + + // SSL_ERROR_PENDING_CERTIFICATE + 12 => Err(Error::Done), + + // SSL_ERROR_WANT_PRIVATE_KEY_OPERATION + 13 => Err(Error::Done), + + // SSL_ERROR_PENDING_TICKET + 14 => Err(Error::Done), + + // SSL_ERROR_EARLY_DATA_REJECTED + 15 => { + self.reset_early_data_reject(); + Err(Error::Done) + }, + + // SSL_ERROR_WANT_CERTIFICATE_VERIFY + 16 => Err(Error::Done), + + _ => Err(Error::TlsFail), + } + }, + } + } + + fn set_transport_error(&mut self, ex_data: &mut ExData, bssl_result: c_int) { + // SSL_ERROR_SSL + if self.get_error(bssl_result) == 1 { + // SSL_ERROR_SSL can't be recovered so ensure we set a + // local_error so the connection is closed. + // See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html + if ex_data.local_error.is_none() { + *ex_data.local_error = Some(ConnectionError { + is_app: false, + error_code: INTERNAL_ERROR, + reason: Vec::new(), + }) + } + } } } @@ -692,7 +892,7 @@ impl Drop for Handshake { pub struct ExData<'a> { pub application_protos: &'a Vec<Vec<u8>>, - pub pkt_num_spaces: &'a mut [packet::PktNumSpace; packet::EPOCH_COUNT], + pub pkt_num_spaces: &'a mut [packet::PktNumSpace; packet::Epoch::count()], pub session: &'a mut Option<Vec<u8>>, @@ -740,13 +940,13 @@ extern fn set_read_secret( let space = match level { crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::EPOCH_INITIAL], + &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], crypto::Level::ZeroRTT => - &mut ex_data.pkt_num_spaces[packet::EPOCH_APPLICATION], + &mut ex_data.pkt_num_spaces[packet::Epoch::Application], crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::EPOCH_HANDSHAKE], + &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::EPOCH_APPLICATION], + &mut ex_data.pkt_num_spaces[packet::Epoch::Application], }; let aead = match get_cipher_from_ptr(cipher) { @@ -791,13 +991,13 @@ extern fn set_write_secret( let space = match level { crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::EPOCH_INITIAL], + &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], crypto::Level::ZeroRTT => - &mut ex_data.pkt_num_spaces[packet::EPOCH_APPLICATION], + &mut ex_data.pkt_num_spaces[packet::Epoch::Application], crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::EPOCH_HANDSHAKE], + &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::EPOCH_APPLICATION], + &mut ex_data.pkt_num_spaces[packet::Epoch::Application], }; let aead = match get_cipher_from_ptr(cipher) { @@ -843,12 +1043,12 @@ extern fn add_handshake_data( let space = match level { crypto::Level::Initial => - &mut ex_data.pkt_num_spaces[packet::EPOCH_INITIAL], + &mut ex_data.pkt_num_spaces[packet::Epoch::Initial], crypto::Level::ZeroRTT => unreachable!(), crypto::Level::Handshake => - &mut ex_data.pkt_num_spaces[packet::EPOCH_HANDSHAKE], + &mut ex_data.pkt_num_spaces[packet::Epoch::Handshake], crypto::Level::OneRTT => - &mut ex_data.pkt_num_spaces[packet::EPOCH_APPLICATION], + &mut ex_data.pkt_num_spaces[packet::Epoch::Application], }; if space.crypto_stream.send.write(buf, false).is_err() { @@ -966,7 +1166,7 @@ extern fn new_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int { None => return 0, }; - let handshake = Handshake(ssl); + let handshake = Handshake::new(ssl); let peer_params = handshake.quic_transport_params(); // Serialize session object into buffer. @@ -1040,59 +1240,6 @@ fn map_result_ptr<'a, T>(bssl_result: *const T) -> Result<&'a T> { } } -fn map_result_ssl(ssl: &mut Handshake, bssl_result: c_int) -> Result<()> { - match bssl_result { - 1 => Ok(()), - - _ => { - let ssl_err = ssl.get_error(bssl_result); - match ssl_err { - // SSL_ERROR_SSL - 1 => { - log_ssl_error(); - - Err(Error::TlsFail) - }, - - // SSL_ERROR_WANT_READ - 2 => Err(Error::Done), - - // SSL_ERROR_WANT_WRITE - 3 => Err(Error::Done), - - // SSL_ERROR_WANT_X509_LOOKUP - 4 => Err(Error::Done), - - // SSL_ERROR_SYSCALL - 5 => Err(Error::TlsFail), - - // SSL_ERROR_PENDING_SESSION - 11 => Err(Error::Done), - - // SSL_ERROR_PENDING_CERTIFICATE - 12 => Err(Error::Done), - - // SSL_ERROR_WANT_PRIVATE_KEY_OPERATION - 13 => Err(Error::Done), - - // SSL_ERROR_PENDING_TICKET - 14 => Err(Error::Done), - - // SSL_ERROR_EARLY_DATA_REJECTED - 15 => { - ssl.reset_early_data_reject(); - Err(Error::Done) - }, - - // SSL_ERROR_WANT_CERTIFICATE_VERIFY - 16 => Err(Error::Done), - - _ => Err(Error::TlsFail), - } - }, - } -} - fn log_ssl_error() { let err = [0; 1024]; @@ -1211,9 +1358,6 @@ extern { ssl: *mut SSL, params: *const u8, params_len: usize, ) -> c_int; - #[cfg(test)] - fn SSL_set_options(ssl: *mut SSL, opts: u32) -> u32; - fn SSL_set_quic_method( ssl: *mut SSL, quic_method: *const SSL_QUIC_METHOD, ) -> c_int; @@ -1224,6 +1368,14 @@ extern { ssl: *mut SSL, context: *const u8, context_len: usize, ) -> c_int; + #[cfg(test)] + fn SSL_set_options(ssl: *mut SSL, opts: u32) -> u32; + + #[cfg(test)] + fn SSL_set_private_key_method( + ssl: *mut SSL, key_method: *const SSL_PRIVATE_KEY_METHOD, + ); + fn SSL_get_peer_quic_transport_params( ssl: *const SSL, out_params: *mut *const u8, out_params_len: *mut usize, ); |