Upgrade quiche to 0.17.1

This change is made by the following steps:
  $ rm patches/Initial-stateless-reset-detection.patch patches/Android.bp.patch
  $ ../../../../tools/external_updater/updater.sh update rust/crates/quiche
  $ (add a new Android.bp.patch in patches/)
  $ cargo2android.py --config cargo2android.json
  $ rm -rf cargo.out target.tmp/

Bug: 277315978
Test: cd external/rust/crates/quiche && atest
Test: cd packages/modules/DnsResolver && atest
Change-Id: I87acbb95e943eb41e89c56acf470cf6de6254dbe
Merged-In: I87acbb95e943eb41e89c56acf470cf6de6254dbe
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 @@
         "liblog_rust",
         "liboctets",
         "libring",
+        "libslab",
+        "libsmallvec",
     ],
     prefer_rlib: true,
     // For DnsResolver (Mainline module introduced in Q).
@@ -132,6 +134,8 @@
         "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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
  "proc-macro2",
  "quote",
  "strsim",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -141,14 +141,14 @@
 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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
  "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 @@
 
 [[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 @@
 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 @@
 
 [[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 @@
  "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 @@
 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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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 @@
 
 [[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_msvc"
-version = "0.36.1"
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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 = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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 @@
 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.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 @@
     "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 @@
 keywords = ["quic", "http3"]
 categories = ["network-programming"]
 license = "BSD-2-Clause"
+rust-version = "1.66"
 include = [
     "/*.md",
     "/*.toml",
@@ -57,15 +58,17 @@
 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 @@
   }
   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 @@
 [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 @@
 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 @@
 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 @@
 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 @@
 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 @@
 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 @@
 
     int sock;
 
+    struct sockaddr_storage local_addr;
+    socklen_t local_addr_len;
+
     quiche_conn *conn;
 };
 
@@ -122,8 +125,10 @@
 
         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 @@
 
     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 @@
         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 @@
         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 @@
     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 @@
     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 @@
 
     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 @@
 
             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 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 @@
 
     int sock;
 
+    struct sockaddr_storage local_addr;
+    socklen_t local_addr_len;
+
     quiche_conn *conn;
 
     quiche_h3_conn *http3;
@@ -144,8 +147,10 @@
 
         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 @@
 
     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 @@
         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 @@
         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 @@
     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 @@
 
     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 @@
 
             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 @@
         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 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 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 @@
 
     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 @@
             }
 
             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 @@
         }
 
         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 @@
 
         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 @@
 
     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 @@
     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 @@
     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 @@
 
     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 @@
 
                 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 @@
                 }
             };
 
-            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 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 @@
     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 @@
         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 @@
             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 @@
     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 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 @@
 
     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 @@
             }
 
             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 @@
         }
 
         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 @@
 
         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 @@
 
     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 @@
     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 @@
     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 @@
         .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 @@
 
     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 @@
 
                 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 @@
                 }
             };
 
-            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 @@
 
     // 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 @@
                                 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 @@
 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 @@
 // 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 @@
 // 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 @@
                        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 @@
 
 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 @@
 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 @@
                          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 @@
                          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_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_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);
 
-bool quiche_conn_stream_readable(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);
+
+// 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 @@
                       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 @@
 
 // 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 @@
     // 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 @@
     // 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 @@
 } 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 @@
 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 @@
 
     // 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 @@
 // 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 @@
     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 @@
 // 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 @@
                                         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 @@
          "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 @@
      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 @@
 -        "liblog_rust",
 -        "liboctets",
 -        "libring",
+-        "libslab",
+-        "libsmallvec",
 -    ],
 -    static_libs: [
 +rust_ffi {
@@ -71,7 +75,12 @@
          "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 @@
 -        "liblog_rust",
 -        "liboctets",
 -        "libring",
+-        "libslab",
+-        "libsmallvec",
 +rust_library {
 +    name: "libquiche",
 +    defaults: ["libquiche_defaults"],
@@ -114,7 +125,11 @@
          "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 @@
      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 @@
      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 @@
 /// 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 @@
                 ""
             };
 
-            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 @@
     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!("{}/include", manifest_dir);
+    let include_dir = format!("{manifest_dir}/include");
+
     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 @@
                     .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::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 @@
 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 @@
 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 @@
 
         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 @@
         }
 
         let mask = self
-            .hp_key
+            .header
+            .hpk
             .new_mask(sample)
             .map_err(|_| Error::CryptoFail)?;
 
@@ -228,50 +220,59 @@
     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 @@
             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 @@
         }
 
         let mask = self
-            .hp_key
+            .header
+            .hpk
             .new_mask(sample)
             .map_err(|_| Error::CryptoFail)?;
 
@@ -345,12 +347,85 @@
     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 @@
     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 @@
     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 @@
 /// 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 @@
 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 @@
         }
 
         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 @@
     }
 
     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 @@
     }
 
     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::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::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 @@
 #[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 @@
 }
 
 #[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 @@
 }
 
 #[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 @@
 }
 
 #[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 @@
 #[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 @@
         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 @@
 
 #[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 @@
     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 @@
 #[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 @@
         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 @@
     match Connection::with_tls(
         &scid,
         odcid.as_ref(),
+        local,
         peer,
         config,
         tls,
@@ -632,12 +691,15 @@
 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 @@
 
 #[repr(C)]
 pub struct SendInfo {
+    from: sockaddr_storage,
+    from_len: socklen_t,
     to: sockaddr_storage,
     to_len: socklen_t,
 
@@ -679,6 +743,7 @@
 
     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 @@
 
 #[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 @@
 
 #[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 @@
 }
 
 #[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 @@
 
 #[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 @@
 
 #[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 @@
 
 #[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 @@
 
 #[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 @@
 
 #[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 @@
 
 #[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 @@
 }
 
 #[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 @@
 
 #[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 @@
     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 @@
     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 @@
     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 @@
         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 @@
 }
 
 #[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);
+
+            let out = SocketAddrV4::new(ip_addr, port);
+
+            out.into()
+        },
+
+        AF_INET6 => {
+            assert!(addr_len as usize == std::mem::size_of::<sockaddr_in6>());
+
+            let in6 = unsafe { *(addr as *const _ as *const sockaddr_in6) };
+
+            let ip_addr = Ipv6Addr::from(
+                #[cfg(not(windows))]
+                in6.sin6_addr.s6_addr,
+                #[cfg(windows)]
+                *unsafe { in6.sin6_addr.u.Byte() },
+            );
+
+            let port = u16::from_be(in6.sin6_port);
+
+            #[cfg(not(windows))]
+            let scope_id = in6.sin6_scope_id;
+            #[cfg(windows)]
+            let scope_id = unsafe { *in6.u.sin6_scope_id() };
+
+            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 {
-    unsafe {
-        match addr {
-            SocketAddr::V4(addr) => {
-                let sa_len = std::mem::size_of::<sockaddr_in>();
+    let sin_port = addr.port().to_be();
 
-                let src = addr as *const _ as *const u8;
-                let dst = out as *mut _ as *mut u8;
+    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;
 
-                std::ptr::copy_nonoverlapping(src, dst, sa_len);
+            let s_addr = u32::from_ne_bytes(addr.ip().octets());
 
-                sa_len as socklen_t
-            },
+            #[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 }
+            };
 
-            SocketAddr::V6(addr) => {
-                let sa_len = std::mem::size_of::<sockaddr_in6>();
+            *out_in = sockaddr_in {
+                sin_family: AF_INET as sa_family_t,
 
-                let src = addr as *const _ as *const u8;
-                let dst = out as *mut _ as *mut u8;
+                sin_addr,
 
-                std::ptr::copy_nonoverlapping(src, dst, sa_len);
+                #[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,
 
-                sa_len as socklen_t
-            },
-        }
+                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 @@
     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_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 @@
     ) -> 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 @@
             },
 
             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 @@
             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 @@
 
             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 @@
             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 @@
         )
     }
 
+    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 @@
             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 @@
                 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 @@
             } => 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 @@
                 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 @@
     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 @@
             } => {
                 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 @@
             } => {
                 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 @@
                 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 @@
             },
 
             Frame::CryptoHeader { offset, length } => {
-                write!(f, "CRYPTO off={} len={}", offset, length)?;
+                write!(f, "CRYPTO off={offset} len={length}")?;
             },
 
             Frame::NewToken { .. } => {
@@ -1069,61 +1071,67 @@
             } => {
                 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 @@
             } => {
                 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 @@
             },
 
             Frame::DatagramHeader { length } => {
-                write!(f, "DATAGRAM len={}", length)?;
+                write!(f, "DATAGRAM len={length}")?;
             },
         }
 
@@ -1360,7 +1366,7 @@
         };
 
         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 @@
 }
 
 #[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 @@
 }
 
 #[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 @@
 }
 
 #[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 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 @@
         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 @@
                 max_field_section_size,
                 qpack_max_table_capacity,
                 qpack_blocked_streams,
+                connect_protocol_enabled,
                 h3_datagram,
                 grease,
                 ..
@@ -196,6 +199,11 @@
                     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 @@
 
                 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 @@
                 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 @@
                 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 @@
 
     #[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 @@
                 max_field_section_size,
                 qpack_max_table_capacity,
                 qpack_blocked_streams,
+                connect_protocol_enabled,
                 h3_datagram,
                 grease,
                 ..
@@ -339,6 +355,13 @@
                     });
                 }
 
+                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 @@
                 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 @@
             },
 
             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 @@
                 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 @@
             },
 
             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 @@
             },
 
             Frame::Unknown { raw_type, .. } => {
-                write!(f, "UNKNOWN raw_type={}", raw_type,)?;
+                write!(f, "UNKNOWN raw_type={raw_type}",)?;
             },
         }
 
@@ -491,6 +517,7 @@
     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 @@
                 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 @@
         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 @@
             (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 @@
             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 @@
             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 @@
             (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 @@
             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 @@
             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 @@
     }
 
     #[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 @@
             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 @@
             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 @@
             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 @@
 
 #[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 @@
 #[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 @@
 ///
 /// [`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 @@
 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 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 @@
     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 @@
             max_field_section_size: None,
             qpack_max_table_capacity: None,
             qpack_blocked_streams: None,
+            connect_protocol_enabled: None,
         })
     }
 
@@ -543,6 +554,17 @@
     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 @@
     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 @@
 }
 
 /// 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 @@
 }
 
 /// 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 @@
 /// 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 @@
 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 @@
     ///
     /// 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 @@
             _ => false,
         };
 
-        Ok(Priority::new(urgency as u8, incremental))
+        Ok(Priority::new(urgency, incremental))
     }
 }
 
@@ -766,6 +815,7 @@
     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 @@
                 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 @@
                 qpack_max_table_capacity: None,
                 qpack_blocked_streams: None,
                 h3_datagram: None,
+                connect_protocol_enabled: None,
                 raw: Default::default(),
             },
 
@@ -877,12 +929,23 @@
     /// 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 @@
     /// 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 @@
             },
         };
 
-        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 @@
 
         // 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 @@
             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 @@
             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 @@
     fn process_dgrams(
         &mut self, conn: &mut super::Connection,
     ) -> Result<(u64, Event)> {
-        let mut d = [0; 8];
+        if conn.dgram_recv_queue_len() > 0 {
+            if self.dgram_event_triggered {
+                return Err(Error::Done);
+            }
 
-        match conn.dgram_recv_peek(&mut d, 8) {
-            Ok(_) => {
-                if self.dgram_event_triggered {
-                    return Err(Error::Done);
-                }
+            self.dgram_event_triggered = true;
 
-                self.dgram_event_triggered = true;
-
-                Ok((0, Event::Datagram))
-            },
-
-            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;
-
-                Err(Error::Done)
-            },
-
-            Err(e) => Err(Error::TransportError(e)),
+            return Ok((0, Event::Datagram));
         }
+
+        self.dgram_event_triggered = false;
+
+        Err(Error::Done)
     }
 
     /// Reads request or response body data into the provided buffer.
@@ -1407,6 +1477,108 @@
         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 @@
             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 @@
             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 @@
                     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 @@
 
     /// 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 @@
             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 @@
                 .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 @@
                             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 @@
 
                     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 @@
                     {
                         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 @@
                 max_field_section_size,
                 qpack_max_table_capacity,
                 qpack_blocked_streams,
+                connect_protocol_enabled,
                 h3_datagram,
                 raw,
                 ..
@@ -2296,6 +2488,7 @@
                     max_field_section_size,
                     qpack_max_table_capacity,
                     qpack_blocked_streams,
+                    connect_protocol_enabled,
                     h3_datagram,
                     raw,
                 };
@@ -2330,7 +2523,7 @@
                 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 @@
     }
 
     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 @@
     }
 
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     /// 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 @@
     /// 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 @@
     }
 
     #[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 @@
     /// 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 @@
     /// 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 @@
     #[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 @@
     #[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 @@
     /// 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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     /// 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 @@
     #[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 @@
     #[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 @@
         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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
         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 @@
     /// 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 @@
         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 @@
     #[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 @@
     /// 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 @@
     /// 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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     #[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 @@
     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 @@
     #[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 @@
         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 @@
     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 @@
         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 @@
     #[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 @@
     #[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 @@
     }
 
     #[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 @@
         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 @@
             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 @@
         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 @@
             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 @@
         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 @@
         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 @@
             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 @@
         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 @@
         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 @@
             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 @@
         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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
         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 @@
         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 @@
         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 @@
     /// 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 @@
     #[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 @@
     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 @@
         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 @@
     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 @@
 
     #[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 @@
     #[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 @@
 
                 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)?;
+                    // 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)?;
+                        },
 
-                    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_str(v: &[u8], prefix: usize, b: &mut octets::OctetsMut) -> Result<()> {
-    let len = super::huffman::encode_output_length(v, 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)?;
+        },
 
-    encode_int(len as u64, 0x80, prefix, b)?;
+        Err(super::Error::InflatedHuffmanEncoding) => {
+            encode_int(v.len() as u64, 0, prefix, b)?;
+            b.put_bytes(v)?;
+        },
 
-    super::huffman::encode(v, b, false)?;
+        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 @@
         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 @@
 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 @@
 
 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 @@
         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 @@
         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 @@
         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 @@
 
 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 @@
     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 @@
     }
 }
 
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum State {
     /// Reading the stream's type.
     StreamType,
@@ -354,13 +354,25 @@
         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 @@
     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 @@
     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 @@
 
     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 @@
             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 @@
     }
 
     #[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 @@
             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 @@
     #[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 @@
             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 @@
     #[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 @@
             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 @@
 
     #[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 @@
             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 @@
 
     #[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 @@
 
         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::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 @@
 // 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 @@
 // 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 @@
 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 @@
 
     /// 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 @@
             Error::FlowControl => 0x3,
             Error::StreamLimit => 0x4,
             Error::FinalSize => 0x6,
+            Error::KeyUpdate => 0xe,
             _ => 0xa,
         }
     }
@@ -531,13 +599,16 @@
             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 @@
 }
 
 /// 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 @@
 }
 
 /// 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 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 @@
 
     hystart: bool,
 
+    pacing: bool,
+
     dgram_recv_max_queue_len: usize,
     dgram_send_max_queue_len: usize,
 
@@ -639,6 +719,8 @@
 
     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 @@
             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 @@
 
             max_connection_window: MAX_CONNECTION_WINDOW,
             max_stream_window: stream::MAX_STREAM_WINDOW,
+
+            disable_dcid_reuse: false,
         })
     }
 
@@ -810,9 +895,6 @@
 
     /// 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 @@
     ///
     /// ```
     /// # 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: &[u8]) -> Result<()> {
+    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_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 @@
 
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// (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 @@
     /// 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 @@
     /// 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 @@
         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 @@
         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 @@
     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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
 
     /// 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 @@
 /// ```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 @@
 /// # 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 @@
 /// # 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 @@
 ///
 /// // 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 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 @@
 
             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 @@
 
             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 @@
 
             peer_error: None,
 
-            challenge: None,
-
             blocked_limit: None,
 
             idle_timer: None,
@@ -1587,11 +1794,8 @@
 
             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 @@
 
             handshake_confirmed: false,
 
+            key_phase: false,
+
             ack_eliciting_sent: false,
 
             closed: false,
@@ -1624,6 +1830,8 @@
             ),
 
             emit_dgram: true,
+
+            disable_dcid_reuse: config.disable_dcid_reuse,
         };
 
         if let Some(odcid) = odcid {
@@ -1631,13 +1839,13 @@
                 .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 @@
                 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 @@
         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 @@
     /// # 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 @@
             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 @@
 
         // 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 @@
             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 @@
                 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 @@
 
         // 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 @@
     /// 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 @@
             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 @@
                 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 @@
 
             // 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 @@
             }
 
             // 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 @@
             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 @@
             )?;
 
             // 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 @@
                 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 @@
         };
 
         // 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 @@
         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 @@
             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 @@
         // 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 @@
                 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 @@
         });
 
         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,78 +2774,122 @@
             });
         }
 
-        // 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);
 
-                    // 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);
-                    }
-                },
+                        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,
+                                },
+                            );
 
-                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;
+                            q.add_event_data_with_instant(ev_data, now).ok();
+                        });
 
-                    self.handshake_done_acked = true;
-                },
+                        // 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::ResetStream { stream_id, .. } => {
-                    let stream = match self.streams.get_mut(stream_id) {
-                        Some(v) => v,
+                    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;
 
-                        None => continue,
-                    };
+                        self.handshake_done_acked = true;
+                    },
 
-                    // 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::ResetStream { stream_id, .. } => {
+                        let stream = match self.streams.get_mut(stream_id) {
+                            Some(v) => v,
 
-                _ => (),
+                            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
         // that still needs to be acked, to be used for ACK delay calculation.
         if self.pkt_num_spaces[epoch].recv_pkt_need_ack.last() < Some(pn) {
@@ -2472,6 +2906,24 @@
         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 @@
         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 @@
     ///  * 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 @@
     /// # 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 @@
     /// # 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 @@
         }
 
         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 @@
         // 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 @@
             // 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 @@
             done += pad_len;
         }
 
-        let info = SendInfo {
-            to: self.peer_addr,
+        let send_path = self.paths.get(send_pid)?;
 
-            at: self.recovery.get_packet_send_time(),
+        let info = SendInfo {
+            from: send_path.local_addr(),
+            to: send_path.peer_addr(),
+
+            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 @@
 
         let mut b = octets::OctetsMut::with_slice(out);
 
-        let pkt_type = self.write_pkt_type()?;
+        let pkt_type = self.write_pkt_type(send_pid)?;
+
+        let max_dgram_len = self.dgram_max_writable_len();
 
         let epoch = pkt_type.to_epoch()?;
+        let pkt_space = &mut self.pkt_num_spaces[epoch];
 
-        // 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);
+        // 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);
 
-                    self.stream_retrans_bytes += length as u64;
+                        self.stream_retrans_bytes += length as u64;
+                        p.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,
-
-                        None => continue,
-                    };
-
-                    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;
-
-                    self.retrans_count += 1;
-                },
-
-                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);
+                        self.retrans_count += 1;
+                        p.retrans_count += 1;
                     },
 
-                // 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::StreamHeader {
+                        stream_id,
+                        offset,
+                        length,
+                        fin,
+                    } => {
+                        let stream = match self.streams.get_mut(stream_id) {
+                            Some(v) => v,
 
-                frame::Frame::MaxStreamData { stream_id, .. } => {
-                    if self.streams.get(stream_id).is_some() {
-                        self.streams.mark_almost_full(stream_id, true);
-                    }
-                },
+                            None => continue,
+                        };
 
-                frame::Frame::MaxData { .. } => {
-                    self.almost_full = true;
-                },
+                        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;
+                    },
+
+                    frame::Frame::ACK { .. } => {
+                        pkt_space.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::MaxStreamData { stream_id, .. } => {
+                        if self.streams.get(stream_id).is_some() {
+                            self.streams.mark_almost_full(stream_id, true);
+                        }
+                    },
+
+                    frame::Frame::MaxData { .. } => {
+                        self.almost_full = true;
+                    },
+
+                    frame::Frame::NewConnectionId { seq_num, .. } => {
+                        self.ids.mark_advertise_new_scid_seq(seq_num, true);
+                    },
+
+                    frame::Frame::RetireConnectionId { seq_num } => {
+                        self.ids.mark_retire_dcid_seq(seq_num, true);
+                    },
+
+                    _ => (),
+                }
             }
         }
 
+        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];
+
         let mut left = b.cap();
 
-        // Limit output packet size by congestion window size.
-        left = cmp::min(left, self.recovery.cwnd_available());
-
-        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 @@
             },
 
             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 @@
                 // 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 @@
 
         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 @@
 
             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 @@
                 };
 
                 // 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 @@
 
                     // 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 @@
             }
 
             // 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 @@
                     in_flight = true;
                 }
             }
+
+            // 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 seq_num == dcid_seq {
+                    continue;
+                }
+
+                let frame = frame::Frame::RetireConnectionId { seq_num };
+
+                if push_frame_to_pkt!(b, frames, frame, left) {
+                    self.ids.mark_retire_dcid_seq(seq_num, false);
+
+                    ack_eliciting = true;
+                    in_flight = true;
+                } else {
+                    break;
+                }
+            }
         }
 
-        // 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 {
+        // 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) {
+                            let pto = path.recovery.pto();
+                            self.draining_timer = Some(now + (pto * 3));
+
+                            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) {
-                        self.draining_timer =
-                            Some(now + (self.recovery.pto() * 3));
+                        let pto = path.recovery.pto();
+                        self.draining_timer = Some(now + (pto * 3));
 
                         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) {
-                    self.draining_timer = Some(now + (self.recovery.pto() * 3));
-
-                    ack_eliciting = true;
-                    in_flight = true;
-                }
-            }
-        }
-
-        // Create PATH_RESPONSE frame.
-        if let Some(challenge) = self.challenge {
-            let frame = frame::Frame::PathResponse { data: challenge };
-
-            if push_frame_to_pkt!(b, frames, frame, left) {
-                self.challenge = None;
-
-                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 @@
                     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 @@
         // 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 @@
         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 @@
         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 @@
 
                 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 @@
                     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 @@
         // 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 @@
         }
 
         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 @@
         }
 
         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 @@
         }
 
         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),
-            );
+            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,
+                };
 
-            // 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 send_at_time =
+                    now.duration_since(q.start_time()).as_secs_f32() * 1000.0;
 
-            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 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 @@
             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 @@
     /// 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 @@
     /// # 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 @@
                 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 @@
     /// # 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,21 +4533,7 @@
             self.blocked_limit = Some(self.max_tx_data);
         }
 
-        // 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()) {
-            return Err(Error::Done);
-        }
-
-        let (buf, fin) = if cap < buf.len() {
-            (&buf[..cap], false)
-        } else {
-            (buf, fin)
-        };
 
         // Get existing stream or create a new one.
         let stream = self.get_or_create_stream(stream_id, true)?;
@@ -3788,8 +4541,35 @@
         #[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.
+        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, blocked_by_cap) = if cap < buf.len() {
+            (&buf[..cap], false, true)
+        } else {
+            (buf, fin, false)
+        };
+
         let sent = match stream.send.write(buf, fin) {
             Ok(v) => v,
 
@@ -3831,12 +4611,22 @@
 
         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 @@
                 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 @@
     /// 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 @@
                 // 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 @@
         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 @@
         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 @@
     /// 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 @@
             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 @@
     /// # 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 @@
     /// # 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 @@
     ///     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 @@
     /// # 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 @@
         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 @@
     /// # 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 @@
 
         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 @@
 
         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 @@
     /// # 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 @@
     /// # 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 @@
         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 @@
             .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 @@
             // 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,23 +5533,416 @@
             }
         }
 
-        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);
-
-                self.recovery.on_loss_detection_timeout(
-                    self.handshake_status(),
-                    now,
-                    &self.trace_id,
-                );
-
-                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();
-                    }
-                });
+                // Discard previous key once key update timer expired.
+                let _ = self.pkt_num_spaces[packet::Epoch::Application]
+                    .key_update
+                    .take();
             }
         }
+
+        let handshake_status = self.handshake_status();
+
+        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(),
+        }
     }
 
     /// Closes the connection with the given error and reason.
@@ -4562,6 +5950,13 @@
     /// 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 @@
             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 @@
         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 @@
     /// 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 @@
     /// 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 @@
         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 @@
         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 @@
         }
     }
 
+    /// 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 @@
         {
             // 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 @@
             }
         }
 
-        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 @@
         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 @@
             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 {
-            self.undecryptable_pkts.clear();
-        }
+            // 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;
 
-        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);
+                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);
+        }
 
         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 @@
             .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 @@
             }
 
             // 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 @@
                 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 @@
 
     /// 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 @@
                     ))
                     .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;
-                }
+                let is_app_limited = self.delivery_rate_check_if_app_limited();
 
-                if self.delivery_rate_check_if_app_limited() {
-                    self.recovery.delivery_rate_update_app_limited(true);
-                }
+                for (_, p) in self.paths.iter_mut() {
+                    if is_app_limited {
+                        p.recovery.delivery_rate_update_app_limited(true);
+                    }
 
-                self.recovery.on_ack_received(
-                    &ranges,
-                    ack_delay,
-                    epoch,
-                    self.handshake_status(),
-                    now,
-                    &self.trace_id,
-                )?;
+                    let (lost_packets, lost_bytes) = p.recovery.on_ack_received(
+                        &ranges,
+                        ack_delay,
+                        epoch,
+                        handshake_status,
+                        now,
+                        &self.trace_id,
+                    )?;
 
-                // Once the handshake is confirmed, we can drop Handshake keys.
-                if self.handshake_confirmed {
-                    self.drop_epoch_state(packet::EPOCH_HANDSHAKE, now);
+                    self.lost_count += lost_packets;
+                    self.lost_bytes += lost_bytes as u64;
                 }
             },
 
@@ -5200,6 +6704,9 @@
                     // 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 @@
                     self.handshake.provide_data(level, recv_buf)?;
                 }
 
-                self.do_handshake()?;
+                self.do_handshake(now)?;
             },
 
             frame::Frame::CryptoHeader { .. } => unreachable!(),
@@ -5272,6 +6779,8 @@
 
                 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 @@
                 }
 
                 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 @@
                     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);
+                }
 
-            // TODO: implement connection migration
-            frame::Frame::RetireConnectionId { .. } => (),
+                let retired_path_ids = self.ids.new_dcid(
+                    conn_id.into(),
+                    seq_num,
+                    u128::from_be_bytes(reset_token),
+                    retire_prior_to,
+                )?;
 
-            frame::Frame::PathChallenge { data } => {
-                self.challenge = Some(data);
+                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::PathResponse { .. } => (),
+            frame::Frame::RetireConnectionId { seq_num } => {
+                if self.ids.zero_length_scid() {
+                    return Err(Error::InvalidState);
+                }
+
+                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.paths
+                    .get_mut(recv_path_id)?
+                    .on_challenge_received(data);
+            },
+
+            frame::Frame::PathResponse { data } => {
+                self.paths.on_response_received(data)?;
+            },
 
             frame::Frame::ConnectionClose {
                 error_code, reason, ..
@@ -5382,7 +6967,9 @@
                     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 @@
                     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 @@
                     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 @@
         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 @@
         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 @@
             )
         };
 
+        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 @@
     /// 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 @@
 
     /// 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 @@
         // 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 @@
     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 @@
     /// 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 @@
     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 @@
             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 @@
             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 @@
             self.peer_max_datagram_frame_size,
         )?;
 
-        write!(f, " }}")
+        write!(f, "}}")
     }
 }
 
@@ -5753,7 +7545,7 @@
 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 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 @@
                         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 @@
 
         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 @@
             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 @@
     }
 
     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 @@
             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, 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,
+                )?,
             })
         }
 
@@ -6210,17 +8052,17 @@
             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 @@
                     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 @@
             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 @@
                     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 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 @@
             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 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 @@
         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 @@
         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 @@
         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 @@
     ) -> 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 @@
 
         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 @@
         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 @@
             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 @@
             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 @@
 
         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 @@
             .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 @@
     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 @@
     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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
         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 @@
         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 @@
             .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 @@
             .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 @@
 
         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 @@
 
         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 @@
             .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 @@
 
         // 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 @@
             .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 @@
 
         // 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 @@
         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 @@
             .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 @@
 
         // 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 @@
                 .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 @@
     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 @@
             .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 @@
             .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 @@
 
         // 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 @@
         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 @@
             .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 @@
     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 @@
     }
 
     #[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 @@
     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 @@
     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 @@
                 .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 @@
     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: 0,
+                data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false),
+            },
+            frame::Frame::Stream {
                 stream_id: 4,
                 data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false),
             },
             frame::Frame::Stream {
                 stream_id: 8,
-                data: stream::RangeBuf::from(b"aaaaaaaaaaaaaaa", 0, false),
-            },
-            frame::Frame::Stream {
-                stream_id: 12,
                 data: stream::RangeBuf::from(b"a", 0, false),
             },
         ];
@@ -7331,22 +9399,22 @@
     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 @@
     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 @@
 
         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 @@
         assert_eq!(
             iter.next(),
             Some(&frame::Frame::MaxStreamData {
-                stream_id: 4,
+                stream_id: 0,
                 max: 30
             })
         );
@@ -7412,7 +9480,7 @@
     /// 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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         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 @@
 
         // 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 @@
         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 @@
 
         // 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 @@
     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 @@
     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 @@
     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 @@
     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 @@
 
         // 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 @@
         // 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 @@
         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 @@
 
         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 @@
 
         // Client sends STOP_SENDING.
         let frames = [frame::Frame::StopSending {
-            stream_id: 4,
+            stream_id: 0,
             error_code: 42,
         }];
 
@@ -8184,7 +10253,7 @@
         assert_eq!(
             iter.next(),
             Some(&frame::Frame::ResetStream {
-                stream_id: 4,
+                stream_id: 0,
                 error_code: 42,
                 final_size: 15,
             })
@@ -8192,11 +10261,11 @@
 
         // 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 @@
 
         // 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 @@
 
         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 @@
 
         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 @@
             .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 @@
             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 @@
     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 @@
     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 @@
     }
 
     #[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 @@
             .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 @@
             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 @@
     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 @@
             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 @@
             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 @@
     #[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 @@
 
         // 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 @@
 
         // 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 @@
     #[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 @@
 
         // 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 @@
         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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
         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 @@
     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 @@
     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 @@
             .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 @@
             .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 @@
     /// 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 @@
     /// 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 @@
     /// 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 @@
     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 @@
 
     #[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 @@
     }
 
     #[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 @@
             .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 @@
 
         // 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 @@
             .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 @@
         // 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 @@
             .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 @@
         // 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 @@
 
     #[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 @@
 
     #[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 @@
     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 @@
     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 @@
     #[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 @@
     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 @@
         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 @@
 
         // 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 @@
 
         // 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 @@
 
         // 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 @@
 
         // 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 @@
             .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 @@
 
             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 @@
 
             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 @@
 
             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 @@
                 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 @@
             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 @@
 
             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 @@
             .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 @@
             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 @@
             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 @@
             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 @@
             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 @@
             .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 @@
     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 @@
 
         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 @@
     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 @@
 
         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 @@
 
         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 @@
             .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 @@
     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 @@
         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 @@
 
     #[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 @@
             .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 @@
             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 @@
         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 @@
             .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 @@
             .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 @@
         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 @@
         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 @@
 
         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 @@
 
         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 @@
             .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 @@
             .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 @@
             .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 @@
             .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 @@
     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 @@
             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 @@
     }
 
     #[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 @@
             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 @@
     }
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
         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 @@
             .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 @@
                 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 @@
             .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 @@
         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 @@
                 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 @@
     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 @@
         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 @@
 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 @@
     }
 
     /// 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 @@
         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 @@
         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 @@
         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 @@
         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 @@
 
 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,
+}
 
-/// 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;
+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;
+
+    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 @@
 impl Type {
     pub(crate) fn from_epoch(e: Epoch) -> Type {
         match e {
-            EPOCH_INITIAL => Type::Initial,
+            Epoch::Initial => Type::Initial,
 
-            EPOCH_HANDSHAKE => Type::Handshake,
+            Epoch::Handshake => Type::Handshake,
 
-            EPOCH_APPLICATION => Type::Short,
-
-            _ => 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 @@
     #[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 @@
 }
 
 /// 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 @@
         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 @@
 }
 
 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 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 @@
         .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 ack_elicited: bool,
 
+    pub key_update: Option<KeyUpdate>,
+
     pub crypto_open: Option<crypto::Open>,
     pub crypto_seal: Option<crypto::Seal>,
 
@@ -843,6 +910,8 @@
 
             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 @@
 
             ack_elicited: false,
 
+            key_update: None,
+
             crypto_open: None,
             crypto_seal: None,
 
@@ -858,8 +929,8 @@
             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 @@
 
     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 @@
         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 @@
         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 @@
         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 @@
         .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::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 @@
         }
 
         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 Default for RangeSet {
     fn default() -> Self {
-        Self::new(std::usize::MAX)
+        Self::new(usize::MAX)
     }
 }
 
@@ -190,7 +188,7 @@
             })
             .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 @@
 
 pub static CUBIC: CongestionControlOps = CongestionControlOps {
     on_init,
+    reset,
     on_packet_sent,
     on_packets_acked,
     congestion_event,
@@ -147,6 +148,10 @@
 
 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 @@
     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 @@
 
         let p = recovery::Sent {
             pkt_num: 0,
-            frames: vec![],
+            frames: smallvec![],
             time_sent: now,
             time_acked: None,
             time_lost: None,
@@ -498,7 +505,7 @@
             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 @@
 
         let p = recovery::Sent {
             pkt_num: 0,
-            frames: vec![],
+            frames: smallvec![],
             time_sent: now,
             time_acked: None,
             time_lost: None,
@@ -568,7 +575,7 @@
             },
         ];
 
-        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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -613,7 +620,7 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -646,7 +653,7 @@
                 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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -691,7 +698,7 @@
             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 @@
 
         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 @@
 
         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 @@
         }
         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 @@
         }
         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 @@
 
         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 @@
 
         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 @@
         }
         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 @@
             }
             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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -1032,10 +1039,10 @@
         // 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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -1074,10 +1081,10 @@
         // 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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -1135,7 +1142,7 @@
                 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 @@
         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 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 @@
         self.end_of_app_limited != 0
     }
 
-    pub fn _delivered(&self) -> usize {
+    pub fn delivered(&self) -> usize {
         self.delivered
     }
 
@@ -170,11 +168,11 @@
         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 @@
 
     use crate::recovery::*;
 
+    use smallvec::smallvec;
+
     #[test]
     fn rate_check() {
         let config = Config::new(0xbabababa).unwrap();
@@ -218,7 +218,7 @@
         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 @@
 
             r.on_packet_sent(
                 pkt,
-                packet::EPOCH_APPLICATION,
+                packet::Epoch::Application,
                 HandshakeStatus::default(),
                 now,
                 "",
@@ -253,7 +253,7 @@
                 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 @@
         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 @@
         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 @@
 
             r.on_packet_sent(
                 pkt,
-                packet::EPOCH_APPLICATION,
+                packet::Epoch::Application,
                 HandshakeStatus::default(),
                 now,
                 "",
@@ -306,7 +306,7 @@
         }
 
         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 @@
         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 @@
 
             r.on_packet_sent(
                 pkt,
-                packet::EPOCH_APPLICATION,
+                packet::Epoch::Application,
                 HandshakeStatus::default(),
                 now,
                 "",
@@ -354,18 +354,17 @@
             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 @@
 
     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 @@
         &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::collections::VecDeque;
 
 use crate::Config;
-use crate::Error;
 use crate::Result;
 
 use crate::frame;
@@ -45,6 +44,8 @@
 #[cfg(feature = "qlog")]
 use qlog::events::EventData;
 
+use smallvec::SmallVec;
+
 // Loss Recovery
 const INITIAL_PACKET_THRESHOLD: u64 = 3;
 
@@ -71,16 +72,21 @@
 
 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 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 @@
     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 @@
     // 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 @@
 
             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 @@
             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 @@
 
             bytes_in_flight: 0,
 
-            ssthresh: std::usize::MAX,
+            ssthresh: usize::MAX,
 
             bytes_acked_sl: 0,
 
@@ -225,9 +257,9 @@
 
             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 @@
 
             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 @@
         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 @@
 
         // 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 @@
             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 @@
 
         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 @@
         (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;
 
-        self.last_packet_scheduled_time = cmp::max(now, next_schedule_time);
+        let sent_bytes = if !self.pacer.enabled() || !is_app || in_initcwnd {
+            0
+        } else {
+            packet_size
+        };
+
+        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(());
-            }
+        // 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.
 
-            return Err(Error::InvalidPacket);
-        }
-
-        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 @@
                 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 @@
         }
 
         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 @@
             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 @@
 
         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 @@
             // 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 @@
         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 @@
     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 @@
         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 @@
         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 @@
     }
 
     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 @@
         // 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 @@
 
     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 @@
         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 @@
             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 @@
                     );
                 }
 
+                lost_packets += 1;
                 self.lost_count += 1;
             } else {
                 let loss_time = match self.loss_time[epoch] {
@@ -844,6 +934,8 @@
         }
 
         self.drain_packets(epoch, now);
+
+        (lost_packets, lost_bytes)
     }
 
     fn drain_packets(&mut self, epoch: packet::Epoch, now: Instant) {
@@ -940,7 +1032,7 @@
         self.app_limited = v;
     }
 
-    pub fn app_limited(&mut self) -> bool {
+    pub fn app_limited(&self) -> bool {
         self.app_limited
     }
 
@@ -958,6 +1050,7 @@
             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 @@
 ///
 /// 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 @@
         match name {
             "reno" => Ok(CongestionControlAlgorithm::Reno),
             "cubic" => Ok(CongestionControlAlgorithm::CUBIC),
+            "bbr" => Ok(CongestionControlAlgorithm::BBR),
 
             _ => Err(crate::Error::CongestionControl),
         }
@@ -1000,6 +1096,8 @@
 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 @@
         match algo {
             CongestionControlAlgorithm::Reno => &reno::RENO,
             CongestionControlAlgorithm::CUBIC => &cubic::CUBIC,
+            CongestionControlAlgorithm::BBR => &bbr::BBR,
         }
     }
 }
@@ -1046,7 +1145,7 @@
 
                 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 @@
             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 @@
 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 @@
 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 @@
     cwnd: u64,
     bytes_in_flight: u64,
     ssthresh: u64,
+    pacing_rate: u64,
 }
 
 #[cfg(feature = "qlog")]
@@ -1267,6 +1362,14 @@
             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 @@
                     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 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use smallvec::smallvec;
 
     #[test]
     fn lookup_cc_algo_ok() {
@@ -1303,7 +1407,7 @@
     fn lookup_cc_algo_bad() {
         assert_eq!(
             CongestionControlAlgorithm::from_str("???"),
-            Err(Error::CongestionControl)
+            Err(crate::Error::CongestionControl)
         );
     }
 
@@ -1328,12 +1432,12 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
             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 @@
 
         // 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 @@
 
         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 @@
 
         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 @@
             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 @@
         // 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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
             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 @@
 
         // 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 @@
         // 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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
 
         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 @@
             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 @@
             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 @@
         // 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 @@
 
         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 @@
 
         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 @@
             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 @@
 
         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 @@
 
         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 @@
 
 pub static RENO: CongestionControlOps = CongestionControlOps {
     on_init,
+    reset,
     on_packet_sent,
     on_packets_acked,
     congestion_event,
@@ -52,6 +53,8 @@
 
 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 @@
 mod tests {
     use super::*;
 
+    use smallvec::smallvec;
     use std::time::Duration;
 
     #[test]
@@ -198,7 +202,7 @@
 
         let p = recovery::Sent {
             pkt_num: 0,
-            frames: vec![],
+            frames: smallvec![],
             time_sent: now,
             time_acked: None,
             time_lost: None,
@@ -230,7 +234,7 @@
             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 @@
 
         let p = recovery::Sent {
             pkt_num: 0,
-            frames: vec![],
+            frames: smallvec![],
             time_sent: now,
             time_acked: None,
             time_lost: None,
@@ -301,7 +305,7 @@
             },
         ];
 
-        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 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -345,7 +349,7 @@
         r.congestion_event(
             r.max_datagram_size,
             now,
-            packet::EPOCH_APPLICATION,
+            packet::Epoch::Application,
             now,
         );
 
@@ -371,7 +375,7 @@
 
         // 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::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::time;
 
+use smallvec::SmallVec;
+
 use crate::Error;
 use crate::Result;
 
@@ -146,13 +147,13 @@
     /// 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 @@
         &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 @@
                         (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 @@
                     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 @@
         };
     }
 
-    /// 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 @@
             }
         }
 
+        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 @@
     /// Send-side stream buffer.
     pub send: SendBuf,
 
+    pub send_lowat: usize,
+
     /// Whether the stream is bidirectional.
     pub bidi: bool,
 
@@ -648,6 +676,7 @@
         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 @@
     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 @@
             (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 @@
 /// An iterator over QUIC streams.
 #[derive(Default)]
 pub struct StreamIter {
-    streams: Vec<u64>,
+    streams: SmallVec<[u64; 8]>,
 }
 
 impl StreamIter {
@@ -751,7 +785,7 @@
 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 @@
             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 @@
             // 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 @@
             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 @@
         }
 
         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 @@
                 break;
             }
 
-            std::collections::binary_heap::PeekMut::pop(buf);
+            entry.remove();
         }
 
         // Update consumed bytes for flow control.
@@ -1056,9 +1091,8 @@
 
     /// 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 @@
     /// 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 @@
 
             // 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 @@
         // 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 @@
             // 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 @@
 
             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 @@
     }
 
     /// 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 @@
         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 @@
             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 @@
 
         self.shutdown = true;
 
-        self.reset()
+        Ok(self.reset())
     }
 
     /// Returns the largest offset of data buffered.
@@ -1627,7 +1670,7 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
 
     #[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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
 
         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 @@
 
 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 @@
         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 @@
     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 @@
     }
 
     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 @@
         }
     }
 
-    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 @@
     }
 
     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 @@
     }
 }
 
-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 @@
     }
 
     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 @@
                 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 @@
     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 @@
                 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 @@
         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 @@
         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 @@
             }
 
             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 @@
 
     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 @@
 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 @@
 
     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 @@
 
     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 @@
 
     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 @@
         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_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 @@
         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 @@
         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,
     );