aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-04-26 23:21:21 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-04-26 23:21:21 +0000
commite742d7d18c2184ec7279d4a4c7166ea68660b81e (patch)
treefeb506ec0fba24b579c5cf6b32d62a486128e590
parent39eec158f3044edd3200c1f107e47b49b24b68ba (diff)
parentac8506a25e3bc93c88b54da37b5c5fcbd2d9cfd0 (diff)
downloadquiche-android14-d1-s7-release.tar.gz
Change-Id: Ic29225e4708d7b665cd5f3728bd22c0a501d279a
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp4
-rw-r--r--CODEOWNERS1
-rw-r--r--Cargo.lock285
-rw-r--r--Cargo.toml18
-rw-r--r--Cargo.toml.orig11
-rw-r--r--METADATA16
-rw-r--r--README.md63
-rw-r--r--clippy.toml1
-rw-r--r--examples/client.c35
-rw-r--r--examples/client.rs25
-rw-r--r--examples/http3-client.c35
-rw-r--r--examples/http3-client.rs27
-rw-r--r--examples/http3-server.c29
-rw-r--r--examples/http3-server.rs30
-rw-r--r--examples/qpack-decode.rs10
-rw-r--r--examples/qpack-encode.rs4
-rw-r--r--examples/server.c29
-rw-r--r--examples/server.rs30
-rw-r--r--include/quiche.h245
-rw-r--r--patches/Android.bp.patch35
-rw-r--r--patches/Initial-stateless-reset-detection.patch91
-rw-r--r--rustfmt.toml62
-rw-r--r--src/build.rs36
-rw-r--r--src/cid.rs964
-rw-r--r--src/crypto.rs242
-rw-r--r--src/dgram.rs27
-rw-r--r--src/ffi.rs537
-rw-r--r--src/frame.rs120
-rw-r--r--src/h3/ffi.rs26
-rw-r--r--src/h3/frame.rs152
-rw-r--r--src/h3/mod.rs1097
-rw-r--r--src/h3/qpack/encoder.rs45
-rw-r--r--src/h3/qpack/huffman/mod.rs4
-rw-r--r--src/h3/qpack/mod.rs51
-rw-r--r--src/h3/stream.rs380
-rw-r--r--src/lib.rs6306
-rw-r--r--src/minmax.rs22
-rw-r--r--src/packet.rs145
-rw-r--r--src/path.rs1069
-rw-r--r--src/ranges.rs10
-rw-r--r--src/recovery/bbr/init.rs93
-rw-r--r--src/recovery/bbr/mod.rs840
-rw-r--r--src/recovery/bbr/pacing.rs43
-rw-r--r--src/recovery/bbr/per_ack.rs380
-rw-r--r--src/recovery/bbr/per_transmit.rs46
-rw-r--r--src/recovery/cubic.rs63
-rw-r--r--src/recovery/delivery_rate.rs45
-rw-r--r--src/recovery/hystart.rs4
-rw-r--r--src/recovery/mod.rs542
-rw-r--r--src/recovery/pacer.rs250
-rw-r--r--src/recovery/reno.rs18
-rw-r--r--src/stream.rs375
-rw-r--r--src/tls.rs352
54 files changed, 12860 insertions, 2512 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
diff --git a/Android.bp b/Android.bp
index 135c2dc..ecbb83f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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
diff --git a/Cargo.lock b/Cargo.lock
index 563148c..83b5b35 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index ad466c8..86457f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/METADATA b/METADATA
index 96b080d..95eeae8 100644
--- a/METADATA
+++ b/METADATA
@@ -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
}
}
diff --git a/README.md b/README.md
index 65e5810..ece548c 100644
--- a/README.md
+++ b/README.md
@@ -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/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 {
diff --git a/src/ffi.rs b/src/ffi.rs
index cdc6915..38e82c4 100644
--- a/src/ffi.rs
+++ b/src/ffi.rs
@@ -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)
+ );
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index d590979..97ad10e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
+ }
}
diff --git a/src/tls.rs b/src/tls.rs
index 0031eee..e6e7ddb 100644
--- a/src/tls.rs
+++ b/src/tls.rs
@@ -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,
);