commit da9586a9661510c87964f855f2cb433818aa9337 parent cff6812467590c42d7ffd62ff42c8cbe49624af7 Author: Valentin Gosu <valentin.gosu@gmail.com> Date: Sat, 22 Nov 2025 14:17:31 +0000 Bug 1999659 - vendor neqo 0.19 r=necko-reviewers,supply-chain-reviewers,kershaw,mxinden Differential Revision: https://phabricator.services.mozilla.com/D273460 Diffstat:
90 files changed, 1830 insertions(+), 830 deletions(-)
diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in @@ -30,11 +30,6 @@ git = "https://github.com/beurdouche/nss-gk-api" rev = "e48a946811ffd64abc78de3ee284957d8d1c0d63" replace-with = "vendored-sources" -[source."git+https://github.com/erichdongubler-mozilla/neqo?rev=5bfb65919fb5804ec1dd68cc93e014cb9c830d94"] -git = "https://github.com/erichdongubler-mozilla/neqo" -rev = "5bfb65919fb5804ec1dd68cc93e014cb9c830d94" -replace-with = "vendored-sources" - [source."git+https://github.com/franziskuskiefer/cose-rust?rev=43c22248d136c8b38fe42ea709d08da6355cf04b"] git = "https://github.com/franziskuskiefer/cose-rust" rev = "43c22248d136c8b38fe42ea709d08da6355cf04b" @@ -115,9 +110,9 @@ git = "https://github.com/mozilla/mp4parse-rust" rev = "f955be5d2a04a631c0f1777d6f35370ea1a99e2d" replace-with = "vendored-sources" -[source."git+https://github.com/mozilla/neqo?tag=v0.18.0"] +[source."git+https://github.com/mozilla/neqo?tag=v0.19.0"] git = "https://github.com/mozilla/neqo" -tag = "v0.18.0" +tag = "v0.19.0" replace-with = "vendored-sources" [source."git+https://github.com/rust-lang/rust-bindgen?rev=9366e0af8da529c958b4cd4fcbe492d951c86f5c"] diff --git a/Cargo.lock b/Cargo.lock @@ -4765,9 +4765,9 @@ dependencies = [ [[package]] name = "mtu" version = "0.2.9" -source = "git+https://github.com/erichdongubler-mozilla/neqo?rev=5bfb65919fb5804ec1dd68cc93e014cb9c830d94#5bfb65919fb5804ec1dd68cc93e014cb9c830d94" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ - "bindgen 0.69.999", + "bindgen 0.72.0", "cfg_aliases", "libc", "mozbuild", @@ -4809,8 +4809,8 @@ dependencies = [ [[package]] name = "neqo-bin" -version = "0.18.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.18.0#57ef3bd8f67023a168734a154e0ef273e9ddc7f1" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "clap", "clap-verbosity-flag", @@ -4833,8 +4833,8 @@ dependencies = [ [[package]] name = "neqo-common" -version = "0.18.0" -source = "git+https://github.com/erichdongubler-mozilla/neqo?rev=5bfb65919fb5804ec1dd68cc93e014cb9c830d94#5bfb65919fb5804ec1dd68cc93e014cb9c830d94" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "enum-map", "env_logger", @@ -4847,10 +4847,10 @@ dependencies = [ [[package]] name = "neqo-crypto" -version = "0.18.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.18.0#57ef3bd8f67023a168734a154e0ef273e9ddc7f1" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ - "bindgen 0.69.999", + "bindgen 0.72.0", "enum-map", "log", "mozbuild", @@ -4861,12 +4861,13 @@ dependencies = [ "strum", "thiserror 2.0.12", "toml 0.5.999", + "windows", ] [[package]] name = "neqo-http3" -version = "0.18.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.18.0#57ef3bd8f67023a168734a154e0ef273e9ddc7f1" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "enumset", "log", @@ -4884,8 +4885,8 @@ dependencies = [ [[package]] name = "neqo-qpack" -version = "0.18.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.18.0#57ef3bd8f67023a168734a154e0ef273e9ddc7f1" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "log", "neqo-common", @@ -4898,8 +4899,8 @@ dependencies = [ [[package]] name = "neqo-transport" -version = "0.18.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.18.0#57ef3bd8f67023a168734a154e0ef273e9ddc7f1" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "enum-map", "enumset", @@ -4918,8 +4919,8 @@ dependencies = [ [[package]] name = "neqo-udp" -version = "0.18.0" -source = "git+https://github.com/erichdongubler-mozilla/neqo?rev=5bfb65919fb5804ec1dd68cc93e014cb9c830d94#5bfb65919fb5804ec1dd68cc93e014cb9c830d94" +version = "0.19.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" dependencies = [ "cfg_aliases", "libc", diff --git a/Cargo.toml b/Cargo.toml @@ -292,8 +292,3 @@ zip = { path = "third_party/rust/zip" } # Patch libcrux-traits to avoid unnecessary unused dependencies and conflicts. libcrux-traits = { path = "build/rust/libcrux-traits" } - -[patch."https://github.com/mozilla/neqo"] -neqo-common = { git = "https://github.com/erichdongubler-mozilla/neqo", rev = "5bfb65919fb5804ec1dd68cc93e014cb9c830d94" } -neqo-udp = { git = "https://github.com/erichdongubler-mozilla/neqo", rev = "5bfb65919fb5804ec1dd68cc93e014cb9c830d94" } -mtu = { git = "https://github.com/erichdongubler-mozilla/neqo", rev = "5bfb65919fb5804ec1dd68cc93e014cb9c830d94" } diff --git a/netwerk/socket/neqo_glue/Cargo.toml b/netwerk/socket/neqo_glue/Cargo.toml @@ -10,11 +10,11 @@ name = "neqo_glue" [dependencies] firefox-on-glean = { path = "../../../toolkit/components/glean/api" } -neqo-udp = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-http3 = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-transport = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } -neqo-common = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-qpack = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } +neqo-udp = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-http3 = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-transport = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } +neqo-common = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-qpack = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } nserror = { path = "../../../xpcom/rust/nserror" } nsstring = { path = "../../../xpcom/rust/nsstring" } xpcom = { path = "../../../xpcom/rust/xpcom" } @@ -31,7 +31,7 @@ zlib-rs = "0.4.2" winapi = {version = "0.3", features = ["ws2def"] } [dependencies.neqo-crypto] -tag = "v0.18.0" +tag = "v0.19.0" git = "https://github.com/mozilla/neqo" default-features = false features = ["gecko"] diff --git a/netwerk/test/http3server/Cargo.toml b/netwerk/test/http3server/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" license = "MPL-2.0" [dependencies] -neqo-bin = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-transport = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } -neqo-common = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-http3 = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } -neqo-qpack = { tag = "v0.18.0", git = "https://github.com/mozilla/neqo" } +neqo-bin = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-transport = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } +neqo-common = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-http3 = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } +neqo-qpack = { tag = "v0.19.0", git = "https://github.com/mozilla/neqo" } log = "0.4.0" base64 = "0.22" cfg-if = "1.0" @@ -21,7 +21,7 @@ tokio = { version = "1", features = ["rt-multi-thread"] } mozilla-central-workspace-hack = { version = "0.1", features = ["http3server"], optional = true } [dependencies.neqo-crypto] -tag = "v0.18.0" +tag = "v0.19.0" git = "https://github.com/mozilla/neqo" default-features = false features = ["gecko"] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml @@ -4265,10 +4265,11 @@ criteria = "safe-to-deploy" delta = "0.2.6 -> 0.2.9" [[audits.mtu]] -who = "Erich Gubler <erichdongubler@gmail.com>" +who = "Valentin Gosu <valentin.gosu@gmail.com>" criteria = "safe-to-deploy" -delta = "0.2.9 -> 0.2.9@git:5bfb65919fb5804ec1dd68cc93e014cb9c830d94" +delta = "0.2.9 -> 0.2.9@git:3dbba8ffc2b3c78713161d1925b2858bd2098548" importable = false +notes = "mtu crate is now part of neqo and maintained by Mozilla employees" [[audits.naga]] who = "Dzmitry Malyshau <kvark@fastmail.com>" diff --git a/third_party/rust/mtu/.cargo-checksum.json b/third_party/rust/mtu/.cargo-checksum.json @@ -1 +1 @@ -{"files":{".clippy.toml":"6ab1a673bd5c7ba29bd77e62f42183db3ace327c23d446d5b4b0618f6c39d639","Cargo.toml":"8f555c7d677524f948513c56a81ff6d3d984decc66c586557ff9313e92448a66","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"4ad721b5b6a3d39ca3e2202f403d897c4a1d42896486dd58963a81f8e64ef61d","README.md":"24df0e24154b7f0096570ad221aea02bd53a0f1124a2adafff5730af5443a65c","SECURITY.md":"75455814b6cf997e22a927eb979b4356d788583aa1eb96e90853aaab0f82ad1b","build.rs":"a4bcd0562c80914a8e909e8b10507605bfd6f0f268fad9ef4d79f4c48bdaed6c","src/bsd.rs":"7641b2a905a5e05505507fdf2e3af37e9c901a997d48759258f9f853cd2ab0e5","src/lib.rs":"6e8702d77e0f211d05862820eec77f2aa8cd8db6ec4de2c5278d223fbd96b31d","src/linux.rs":"aecc6acbea2419dd6cc31e916148e438d8cec20cf001758042299ff2ccc81d39","src/routesocket.rs":"be837947e2c3f9301a174499217fe8920ff492918bf85ca5eb281eb7ad2240e1","src/windows.rs":"d7e18d55b3be5462d2041fc22fb22cf1fc163ec30b107a3274f2bd22ad618411"},"package":null} -\ No newline at end of file +{"files":{".clippy.toml":"6ab1a673bd5c7ba29bd77e62f42183db3ace327c23d446d5b4b0618f6c39d639","Cargo.toml":"82280af2af41539ce7ab86a7b98121f50f45f5cf5a1b012ffd2594b35850e61e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"4ad721b5b6a3d39ca3e2202f403d897c4a1d42896486dd58963a81f8e64ef61d","README.md":"24df0e24154b7f0096570ad221aea02bd53a0f1124a2adafff5730af5443a65c","SECURITY.md":"75455814b6cf997e22a927eb979b4356d788583aa1eb96e90853aaab0f82ad1b","build.rs":"a4bcd0562c80914a8e909e8b10507605bfd6f0f268fad9ef4d79f4c48bdaed6c","src/bsd.rs":"f6d472effbdd95f6fd4285dfb39d37a99da66ed7283906862ad29a3c2233fb19","src/lib.rs":"6e8702d77e0f211d05862820eec77f2aa8cd8db6ec4de2c5278d223fbd96b31d","src/linux.rs":"d4d2e42d8e0835d64ac154b4bddb5fe9e9228e5d8c9ccd25d6afa89cfb6b6523","src/routesocket.rs":"be837947e2c3f9301a174499217fe8920ff492918bf85ca5eb281eb7ad2240e1","src/windows.rs":"d7e18d55b3be5462d2041fc22fb22cf1fc163ec30b107a3274f2bd22ad618411"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/mtu/Cargo.toml b/third_party/rust/mtu/Cargo.toml @@ -63,7 +63,7 @@ version = "1.1" default-features = false [build-dependencies.bindgen] -version = "0.69" +version = "0.72" features = ["runtime"] default-features = false diff --git a/third_party/rust/mtu/src/bsd.rs b/third_party/rust/mtu/src/bsd.rs @@ -22,6 +22,7 @@ use static_assertions::{const_assert, const_assert_eq}; #[allow( clippy::allow_attributes, + clippy::allow_attributes_without_reason, non_camel_case_types, non_snake_case, clippy::struct_field_names, diff --git a/third_party/rust/mtu/src/linux.rs b/third_party/rust/mtu/src/linux.rs @@ -23,6 +23,7 @@ use crate::{aligned_by, default_err, routesocket::RouteSocket, unlikely_err}; #[allow( clippy::allow_attributes, + clippy::allow_attributes_without_reason, clippy::struct_field_names, non_camel_case_types, clippy::too_many_lines, diff --git a/third_party/rust/neqo-bin/.cargo-checksum.json b/third_party/rust/neqo-bin/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"0f352c3841d58a8621d52a11ff0d1d26b1c7d5f6b9a3f7a37a7e3f6afcfb2e89","benches/main.rs":"6b7bd05c718020f9180a630a2fe1a13066245ad90ee9b9bf72b7aa9f74709764","src/bin/client.rs":"9df4af3b25159adccfca36d6001443cf295993351fec51f833827d91ebb67fd4","src/bin/server.rs":"f55f26c8f0a34de415ede8c4865b845b3d755c3f5fe4f5574b5ee7f3a3601598","src/client/http09.rs":"7ee588c1a8317f70f8a45cdaf0fdfc49419340dd4b41700de1219e8f5ab6c097","src/client/http3.rs":"0084e2671e761cc46ce18e856f193ebe875386d6bbc4f2dc88f2bd563ca865c9","src/client/mod.rs":"ab34c80769e4996d02f930c136c9c832d130209f435845b68d7ab89fe789fe29","src/lib.rs":"ef20c29297d978a192011371e6af12be26d57063b616d3e21fb3d2750987ce88","src/send_data.rs":"ef8ad949e8b787f77f091a4705672b9801dc79c863d9d54a5296e0839789802e","src/server/http09.rs":"6f8f9bec9c2b8d524f2c331fc0db81c17f71c8c8ac00d50e4b6670c3e226b2b2","src/server/http3.rs":"e090c81154a39a7a77b69321cba782752796953f3db8a9685cb147874205d9de","src/server/mod.rs":"3897044c5b690360cf1a872d90f467ff4e629e9283a56bdf633045c2a1a730b0","src/udp.rs":"a0f456178f353fcd91013b3fab46dac14863f979930305c9d5bd5471d953e144"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"ca01e0cc1658ebcaa9c11915e6c5a257d9e0dfb6fbad9ea898b9fc7c2cf45d2c","benches/main.rs":"39c35b9b22958d1deaeb68a3974022296522c97cd47e86085370d8f246a07ac1","src/bin/client.rs":"9df4af3b25159adccfca36d6001443cf295993351fec51f833827d91ebb67fd4","src/bin/server.rs":"f55f26c8f0a34de415ede8c4865b845b3d755c3f5fe4f5574b5ee7f3a3601598","src/client/http09.rs":"7ee588c1a8317f70f8a45cdaf0fdfc49419340dd4b41700de1219e8f5ab6c097","src/client/http3.rs":"0084e2671e761cc46ce18e856f193ebe875386d6bbc4f2dc88f2bd563ca865c9","src/client/mod.rs":"ab34c80769e4996d02f930c136c9c832d130209f435845b68d7ab89fe789fe29","src/lib.rs":"ef20c29297d978a192011371e6af12be26d57063b616d3e21fb3d2750987ce88","src/send_data.rs":"ef8ad949e8b787f77f091a4705672b9801dc79c863d9d54a5296e0839789802e","src/server/http09.rs":"6f8f9bec9c2b8d524f2c331fc0db81c17f71c8c8ac00d50e4b6670c3e226b2b2","src/server/http3.rs":"e38b375132b2455ff1aad816871fd2f279ca79550e821846c2952d9e1c3a8ec5","src/server/mod.rs":"3897044c5b690360cf1a872d90f467ff4e629e9283a56bdf633045c2a1a730b0","src/udp.rs":"2a97c56e14ff271bb1aff6d62d9594c2314ce2398d1c0f6300e9623cbb7c2676"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-bin/Cargo.toml b/third_party/rust/neqo-bin/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-bin" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false @@ -39,6 +39,9 @@ categories = [ license = "MIT OR Apache-2.0" repository = "https://github.com/mozilla/neqo/" +[package.metadata.bench.main.codspeed] +mode = "walltime" + [package.metadata.cargo-machete] ignored = ["log"] @@ -49,7 +52,10 @@ bench = [ "neqo-transport/bench", "log/release_max_level_info", ] -draft-29 = [] +draft-29 = [ + "neqo-http3/draft-29", + "neqo-transport/draft-29", +] fast-apple-datapath = ["quinn-udp/fast-apple-datapath"] [lib] @@ -158,25 +164,15 @@ features = ["std"] default-features = false [dev-dependencies.criterion] -version = "0.6" -features = [ - "async_tokio", - "cargo_bench_support", -] +version = "4" +features = ["async_tokio"] default-features = false +package = "codspeed-criterion-compat" [dev-dependencies.neqo-bin] path = "." features = ["draft-29"] -[dev-dependencies.neqo-http3] -path = "./../neqo-http3" -features = ["draft-29"] - -[dev-dependencies.neqo-transport] -path = "./../neqo-transport" -features = ["draft-29"] - [dev-dependencies.tokio] version = "1" features = ["sync"] diff --git a/third_party/rust/neqo-bin/benches/main.rs b/third_party/rust/neqo-bin/benches/main.rs @@ -5,6 +5,10 @@ // except according to those terms. #![expect(clippy::unwrap_used, reason = "OK in a bench.")] +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] use std::{env, hint::black_box, net::SocketAddr, path::PathBuf, str::FromStr as _}; diff --git a/third_party/rust/neqo-bin/src/server/http3.rs b/third_party/rust/neqo-bin/src/server/http3.rs @@ -104,10 +104,14 @@ impl super::HttpServer for HttpServer { } => { qdebug!("Headers (request={stream} fin={fin}): {headers:?}"); - if headers.contains_header(":method", "POST") { - let response_size = headers - .find_header(":path") - .and_then(|path| path.value().trim_matches('/').parse::<usize>().ok()); + if headers.contains_header(":method", b"POST") { + let response_size = headers.find_header(":path").and_then(|path| { + path.value_utf8() + .ok()? + .trim_matches('/') + .parse::<usize>() + .ok() + }); self.posts.insert(stream, (0, response_size)); continue; } @@ -120,10 +124,11 @@ impl super::HttpServer for HttpServer { }; let mut response = if self.is_qns_test { - match qns_read_response(path.value()) { + let path_str = path.value_utf8().unwrap_or("/"); + match qns_read_response(path_str) { Ok(data) => SendData::from(data), Err(e) => { - qerror!("Failed to read {}: {e}", path.value()); + qerror!("Failed to read {path_str}: {e}"); stream .send_headers(&[Header::new(":status", "404")]) .unwrap(); @@ -131,10 +136,11 @@ impl super::HttpServer for HttpServer { continue; } } - } else if let Ok(count) = - path.value().trim_matches(|p| p == '/').parse::<usize>() - { - SendData::zeroes(count) + } else if let Ok(path_str) = path.value_utf8() { + path_str + .trim_matches(|p| p == '/') + .parse::<usize>() + .map_or_else(|_| SendData::from(path.value()), SendData::zeroes) } else { SendData::from(path.value()) }; diff --git a/third_party/rust/neqo-bin/src/udp.rs b/third_party/rust/neqo-bin/src/udp.rs @@ -11,9 +11,9 @@ use std::{io, net::SocketAddr}; use neqo_common::{qdebug, DatagramBatch}; use neqo_udp::{DatagramIter, RecvBuf}; -/// Ideally this would live in [`neqo-udp`]. [`neqo-udp`] is used in Firefox. +/// Ideally this would live in [`neqo_udp`]. [`neqo_udp`] is used in Firefox. /// -/// Firefox uses `cargo vet`. [`tokio`] the dependency of [`neqo-udp`] is not +/// Firefox uses `cargo vet`. [`tokio`] the dependency of [`neqo_udp`] is not /// audited as `safe-to-deploy`. `cargo vet` will require `safe-to-deploy` for /// [`tokio`] even when behind a feature flag. /// diff --git a/third_party/rust/neqo-common/.cargo-checksum.json b/third_party/rust/neqo-common/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"c77f1227eb1b4e5057779146ec66124d73fd5395858d541a2ccecd145c1e8db3","benches/decoder.rs":"9a5e780ff68f180d7597e9d56b6d0ae609594a4558e4010babb2f33660ddddbe","build.rs":"d9accad1f92a1d82aff73a588269342db882918173e8d9b2b914c514e42e2839","src/bytes.rs":"b9ce44977af8d0731b51798fa9bd752fa4be437603a08446eb55889c2348281c","src/codec.rs":"1d5a036147a0bd4789eb4caa545f82ee0d18eca25ad1b6bdfd2fad58d99ae29e","src/datagram.rs":"2ad1a6e1f8a157a0361b7b4e7c161d62c7bf742c4247190507b9d050d113a923","src/event.rs":"289cf8e265c33e7cded58820ac81e5b575e3f84dd52fa18b0761f4094fb361c0","src/fuzz.rs":"9e0f2dca1832ef49b93b214e8d5f1ca2f5f8cb84a856fead344f62a722c370db","src/header.rs":"7f5d82577a5e1020ff237143e3aaa7e671403466a5a87f633b4c75f9d4e90aa9","src/hrtime.rs":"fd1fbf9ddd38c77e92abe25d7ab9e62872c1cd62ffae8743835bf94f76b6ddc8","src/incrdecoder.rs":"62f61d2600dafb1eec7d6cc85b3c7b07aba0ccd1149892b1dfa1a441f30927a3","src/lib.rs":"2bb6289a73dc07edfd2bc5bccda9542d403066656f41042116ed31f4fc4725ca","src/log.rs":"61a9b24bf6bf1493da67082bcf7fef8fe55f0a23d7f2a9ad13748982c54c85e2","src/qlog.rs":"2c072bb9ad31aad99c1f41421f162fbc48fbd4a17f4e554187b41267afef144b","src/tos.rs":"e09a69a20d54178a4c74b63596c607dbe8ace4ae0758a65f9878ea63d40e3c80","tests/log.rs":"c73187e390ee1a7c4a72266cb7ce5c326e862803dbcf86c2b9a892462fa22356"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"a8737d287cd3c69ebeb0a6c9db1fcaadd4f5fe41c70bd303295e9a9ccaf3071c","benches/decoder.rs":"c59e667951e2ada510ca20850bf2710bc22d91e5caa7d4987541284b0650391a","build.rs":"d9accad1f92a1d82aff73a588269342db882918173e8d9b2b914c514e42e2839","src/bytes.rs":"b9ce44977af8d0731b51798fa9bd752fa4be437603a08446eb55889c2348281c","src/codec.rs":"4861ca281690afb525a848e6ec3281cc18e35c2ee4fcea115a7a22097f3b47b4","src/datagram.rs":"2ad1a6e1f8a157a0361b7b4e7c161d62c7bf742c4247190507b9d050d113a923","src/event.rs":"289cf8e265c33e7cded58820ac81e5b575e3f84dd52fa18b0761f4094fb361c0","src/fuzz.rs":"9e0f2dca1832ef49b93b214e8d5f1ca2f5f8cb84a856fead344f62a722c370db","src/header.rs":"3d68c721614f86a9de018b53b3fdd918551b0247439c2ff6308f2f7bd35795c6","src/hrtime.rs":"fd1fbf9ddd38c77e92abe25d7ab9e62872c1cd62ffae8743835bf94f76b6ddc8","src/incrdecoder.rs":"62f61d2600dafb1eec7d6cc85b3c7b07aba0ccd1149892b1dfa1a441f30927a3","src/lib.rs":"2bb6289a73dc07edfd2bc5bccda9542d403066656f41042116ed31f4fc4725ca","src/log.rs":"61a9b24bf6bf1493da67082bcf7fef8fe55f0a23d7f2a9ad13748982c54c85e2","src/qlog.rs":"2c072bb9ad31aad99c1f41421f162fbc48fbd4a17f4e554187b41267afef144b","src/tos.rs":"e09a69a20d54178a4c74b63596c607dbe8ace4ae0758a65f9878ea63d40e3c80","tests/log.rs":"c73187e390ee1a7c4a72266cb7ce5c326e862803dbcf86c2b9a892462fa22356"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-common/Cargo.toml b/third_party/rust/neqo-common/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-common" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -95,9 +95,9 @@ version = "2.0.12" default-features = false [dev-dependencies.criterion] -version = "0.6" -features = ["cargo_bench_support"] +version = "4" default-features = false +package = "codspeed-criterion-compat" [dev-dependencies.neqo-crypto] path = "../neqo-crypto" diff --git a/third_party/rust/neqo-common/benches/decoder.rs b/third_party/rust/neqo-common/benches/decoder.rs @@ -5,6 +5,10 @@ // except according to those terms. #![expect(clippy::unwrap_used, reason = "OK in a bench.")] +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] use std::hint::black_box; diff --git a/third_party/rust/neqo-common/src/codec.rs b/third_party/rust/neqo-common/src/codec.rs @@ -239,7 +239,7 @@ impl<B: Buffer> Encoder<B> { /// Note: for a view of a slice, use `Decoder::new(&enc[s..e])` #[must_use] pub fn as_decoder(&self) -> Decoder<'_> { - Decoder::new(self.buf.as_slice()) + Decoder::new(self.as_ref()) } /// Generic encode routine for arbitrary data. @@ -247,9 +247,9 @@ impl<B: Buffer> Encoder<B> { /// # Panics /// /// When writing to the underlying buffer fails. - pub fn encode(&mut self, data: &[u8]) -> &mut Self { + pub fn encode(&mut self, data: impl AsRef<[u8]>) -> &mut Self { self.buf - .write_all(data) + .write_all(data.as_ref()) .expect("Buffer has enough capacity."); self } @@ -292,9 +292,9 @@ impl<B: Buffer> Encoder<B> { #[expect(clippy::cast_possible_truncation, reason = "This is intentional.")] match () { () if v < (1 << 6) => self.encode_byte(v as u8), - () if v < (1 << 14) => self.encode(&((v as u16 | (1 << 14)).to_be_bytes())), - () if v < (1 << 30) => self.encode(&((v as u32 | (2 << 30)).to_be_bytes())), - () if v < (1 << 62) => self.encode(&(v | (3 << 62)).to_be_bytes()), + () if v < (1 << 14) => self.encode((v as u16 | (1 << 14)).to_be_bytes()), + () if v < (1 << 30) => self.encode((v as u32 | (2 << 30)).to_be_bytes()), + () if v < (1 << 62) => self.encode((v | (3 << 62)).to_be_bytes()), () => panic!("Varint value too large"), } } @@ -927,7 +927,7 @@ mod tests { #[test] fn encode() { let mut enc = Encoder::default(); - enc.encode(&[1, 2, 3]); + enc.encode([1, 2, 3]); assert_eq!(enc, Encoder::from_hex("010203")); } @@ -1036,7 +1036,7 @@ mod tests { fn encode_vec_with_overflow() { let mut enc = Encoder::default(); enc.encode_vec_with(1, |enc_inner| { - enc_inner.encode(&[0xb0; 256]); + enc_inner.encode([0xb0; 256]); }); } @@ -1060,7 +1060,7 @@ mod tests { fn encode_vvec_with_longer() { let mut enc = Encoder::default(); enc.encode_vvec_with(|enc_inner| { - enc_inner.encode(&[0xa5; 65]); + enc_inner.encode([0xa5; 65]); }); let v: Vec<u8> = enc.into(); assert_eq!(&v[..3], &[0x40, 0x41, 0xa5]); @@ -1194,4 +1194,18 @@ mod tests { let buf = Cursor::new(&mut a[..]); assert_eq!(Buffer::position(&buf), 0); } + + /// [`Encoder::as_decoder`] should only expose the bytes actively encoded through this + /// [`Encoder`], not all bytes of the underlying [`Buffer`]. + #[test] + fn as_decoder_exposes_encoded_bytes_only_not_whole_buffer() { + let mut buffer = vec![1, 2, 3, 4]; + let mut enc = Encoder::new_borrowed_vec(&mut buffer); + enc.encode([5, 6, 7]); + + let decoder = enc.as_decoder(); + assert_eq!(decoder.as_ref().len(), 3); + assert_eq!(decoder.as_ref(), &[5, 6, 7]); + assert_eq!(buffer, &[1, 2, 3, 4, 5, 6, 7]); + } } diff --git a/third_party/rust/neqo-common/src/header.rs b/third_party/rust/neqo-common/src/header.rs @@ -11,14 +11,21 @@ use thiserror::Error; #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)] pub struct Header { name: String, - value: String, + /// The raw header field value as bytes. + /// + /// HTTP allows field values to contain any visible ASCII characters and + /// arbitrary 0x80–0xFF bytes (`obs-text`). Unlike field *names*, field + /// values are not guaranteed to be valid UTF-8. + /// + /// See also <https://www.rfc-editor.org/rfc/rfc9110#section-5.5>. + value: Vec<u8>, } impl Header { pub fn new<N, V>(name: N, value: V) -> Self where N: Into<String>, - V: Into<String>, + V: Into<Vec<u8>>, { Self { name: name.into(), @@ -56,19 +63,28 @@ impl Header { reason = "False positive on 1.86, remove when MSRV is higher." )] #[must_use] - pub fn value(&self) -> &str { + pub fn value(&self) -> &[u8] { &self.value } + + /// Try to interpret the header value as UTF-8. + /// + /// # Errors + /// + /// Returns an error if the value contains invalid UTF-8. + pub fn value_utf8(&self) -> Result<&str, std::str::Utf8Error> { + std::str::from_utf8(&self.value) + } } -impl<T: AsRef<str>, U: AsRef<str>> PartialEq<(T, U)> for Header { +impl<T: AsRef<str>, U: AsRef<[u8]>> PartialEq<(T, U)> for Header { fn eq(&self, other: &(T, U)) -> bool { self.name == other.0.as_ref() && self.value == other.1.as_ref() } } pub trait HeadersExt<'h> { - fn contains_header<T: AsRef<str>, U: AsRef<str>>(self, name: T, value: U) -> bool; + fn contains_header<T: AsRef<str>, U: AsRef<[u8]>>(self, name: T, value: U) -> bool; fn find_header<T: AsRef<str> + 'h>(self, name: T) -> Option<&'h Header>; } @@ -76,7 +92,7 @@ impl<'h, H> HeadersExt<'h> for H where H: IntoIterator<Item = &'h Header> + 'h, { - fn contains_header<T: AsRef<str>, U: AsRef<str>>(self, name: T, value: U) -> bool { + fn contains_header<T: AsRef<str>, U: AsRef<[u8]>>(self, name: T, value: U) -> bool { let (name, value) = (name.as_ref(), value.as_ref()); self.into_iter().any(|h| h == &(name, value)) } @@ -119,31 +135,33 @@ impl FromStr for Header { #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { + use std::str::from_utf8; + use super::*; #[test] fn from_str_valid() { let header = Header::from_str("Content-Type: text/html").unwrap(); assert_eq!(header.name(), "content-type"); - assert_eq!(header.value(), "text/html"); + assert_eq!(header.value(), b"text/html"); let header = Header::from_str("Content-Type:").unwrap(); assert_eq!(header.name(), "content-type"); - assert_eq!(header.value(), ""); + assert_eq!(header.value(), b""); } #[test] fn from_str_pseudo_header() { let header = Header::from_str(":scheme: https").unwrap(); assert_eq!(header.name(), ":scheme"); - assert_eq!(header.value(), "https"); + assert_eq!(header.value(), b"https"); } #[test] fn from_str_pseudo_header_with_value_with_colon() { let header = Header::from_str(":some: he:ader").unwrap(); assert_eq!(header.name(), ":some"); - assert_eq!(header.value(), "he:ader"); + assert_eq!(header.value(), b"he:ader"); } #[test] @@ -154,4 +172,58 @@ mod tests { Some(FromStrError::MissingName) ); } + + #[test] + fn non_utf8_header_value() { + // Create a header with non-UTF-8 bytes in the value + let non_utf8_bytes: Vec<u8> = vec![0xFF, 0xFE, 0xFD, 0x80, 0x81]; + let header = Header::new("custom-header", non_utf8_bytes.as_slice()); + + assert_eq!(header.name(), "custom-header"); + assert_eq!(header.value(), non_utf8_bytes.as_slice()); + + // Verify that the value is indeed not valid UTF-8 + assert!(from_utf8(header.value()).is_err()); + // Test the value_utf8() helper method + assert!(header.value_utf8().is_err()); + } + + #[test] + fn non_ascii_header_value() { + // Create a header with non-ASCII but valid UTF-8 bytes (emoji: rocket + star) + let emoji_bytes = b"\xF0\x9F\x9A\x80\xF0\x9F\x8C\x9F"; + let header = Header::new("emoji-header", emoji_bytes.as_slice()); + + assert_eq!(header.name(), "emoji-header"); + assert_eq!(header.value(), emoji_bytes.as_slice()); + + // Verify we can convert back to UTF-8 + let emoji_str = from_utf8(header.value()).unwrap(); + assert_eq!(emoji_str, from_utf8(emoji_bytes).unwrap()); + // Test the value_utf8() helper method + assert_eq!(header.value_utf8().unwrap(), emoji_str); + } + + #[test] + fn value_utf8_method() { + // Test value_utf8() with valid UTF-8 + let header = Header::new("content-type", "text/html"); + assert_eq!(header.value_utf8().unwrap(), "text/html"); + + // Test value_utf8() with bytes + let header2 = Header::new("test", b"value"); + assert_eq!(header2.value_utf8().unwrap(), "value"); + } + + #[test] + fn header_comparison_with_bytes() { + let header = Header::new("test", b"value"); + + // Test PartialEq with byte slice + assert_eq!(header, ("test", b"value".as_ref())); + + // Test with string (converted to bytes) + let header2 = Header::new("test2", "string_value"); + assert_eq!(header2, ("test2", b"string_value".as_ref())); + } } diff --git a/third_party/rust/neqo-crypto/.cargo-checksum.json b/third_party/rust/neqo-crypto/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"cb62a951b24da13ea265aeb73a74066c71e2464f2e153805ed4a28280784c84d","bindings/bindings.toml":"edffd81bae5081805f92fd527fd1fb474abf07a96c7b1536629ed0b2a328b638","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"7b121a109b0c2e32ecb04768d6d6d2f8a04a081aaafce765c4632253239ce44a","min_version.txt":"0f9ddf9ddaeb5137a5ab3d238d06286822f9579b1f46ba76312a8c6d76176500","src/aead.rs":"7f627f7dcb08444891b4898a8ab8c0bc4984c035212572547323046ec46e4bb1","src/aead_null.rs":"e8946edbff657763885dd52ccc5516726f316286b6e0c84671458d02a3c7e44a","src/agent.rs":"69e2d99c47c12bf24d4659e723fb85096d6286d134ced65054c40631e88c7c0c","src/agentio.rs":"eb13376f2aed4b0b822784d43d341709b3a33f6ba52560ff48ca3e339d1e86da","src/auth.rs":"bbba836237b0c5d079f1348a96bc46b5bb6fb3cd34ca568581c9f7f8800444d1","src/cert.rs":"afecc277b918e9123d6099fc2b7f5a4ef58c9c3c1b3ca9d4790bda0a46665fe3","src/constants.rs":"83606aeb646b2833a8094f9d980c266ecc3e8cb40c93a4820da221988319dd1a","src/ech.rs":"cf6670ce7ceaaa67c8b0f93b5063cf4a0b92a0b176bbbb664b0a58f1b922b710","src/err.rs":"40658d015ac45cdd29b3bc34540c93b80a20baf5d470e0c59754fc45ce6af204","src/exp.rs":"70549c53ce8df99d62d3343697abd2a177d67ff56703a3d26048bdcdc8b87a0d","src/ext.rs":"7082cd7b44ba97275a8aefe0c31c2419d750f9621486c9c017864c82a7580423","src/hkdf.rs":"76c5abc8b2d6ee12d8a86cd730af2cf47a59b2fbfd3b8a635a1826636156794d","src/hp.rs":"04a461676c02d308f1f851b975846f83daa50ee08de9e573b4136ce4d54b4473","src/lib.rs":"42bdd28c9cd22178e2a0ab1736a0ea49cb240c78cc924d26296086d469a1f2fe","src/min_version.rs":"c6e1f98b9f56db0622ac38c1be131c55acf4a0f09ed0d6283f4d6308e2d1301a","src/p11.rs":"dda7025c61987caffbb9acd38c3e4169a45692a96c3f23025612a4ef4a035157","src/prio.rs":"1858088afd2668e8fbff56959765b7d4df09342371b9282ade27bb4d7bd6ce69","src/replay.rs":"7bf84ce1964658e69d81a810f3b8d71d36d5a7fc336d83c04fb585a6a98e6d33","src/result.rs":"27067d9aba61e8162fb92bca03f9a462cf4fe2f0a259d52696b63e1f6a959a5c","src/secrets.rs":"b021c91b9c1b63373474c39e817a7d9083681be13b5466c4d2b776db9a65b9f8","src/selfencrypt.rs":"2cdca9ec879057ef76bbef168fea0750c34eeaea8dd370e8c192469b377168ad","src/ssl.rs":"49f4339e665959bd3a0fbd0e192611928fdeab986c4f539a4be2ab9cb6d60b8b","src/time.rs":"c4c9987bfe273f19a2f5ef09920ccfe384ab1c1eaf2b2281eb4b02aa8d3b9970","tests/aead.rs":"2e99fba2f155aa8442709c4847f171f0cdfc179b2a7cd2afd853b550d02f7792","tests/agent.rs":"81266b780a40f1d8d31edbe1f43a37fd641f2cb44f75365c67b068c0d3442bb3","tests/ext.rs":"40e3bb0e5ea00fe411cfaf1a006fd4b11a22503f66d3738423361a8b7f80fe13","tests/handshake.rs":"7c6dbdf1b2ae74d15f0a3242d9969abf04ea9839eddcf1aae73379142f33a433","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"dab2631fb5a4f47227e05f508eaca4b4aa225bafced60e703e6fd1c329ac6ab1","tests/init.rs":"3cfe8411ca31ad7dfb23822bb1570e1a5b2b334857173bdd7df086b65b81d95a","tests/selfencrypt.rs":"2e0b548fc84f388b0b2367fb8d9e3e0bd25c4814a1e997b13b7849a54a529703"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"7606e1ce9b7284aeda3ba79e3492ca6072edc2a9f4fef6a33ee64b60ebd1f145","bindings/bindings.toml":"edffd81bae5081805f92fd527fd1fb474abf07a96c7b1536629ed0b2a328b638","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"37e36650c067e04fecddca49b6e32a2b9dae14118e066a2baa676ad1ae6d33d3","min_version.txt":"0f9ddf9ddaeb5137a5ab3d238d06286822f9579b1f46ba76312a8c6d76176500","src/aead.rs":"7f627f7dcb08444891b4898a8ab8c0bc4984c035212572547323046ec46e4bb1","src/aead_null.rs":"e8946edbff657763885dd52ccc5516726f316286b6e0c84671458d02a3c7e44a","src/agent.rs":"69e2d99c47c12bf24d4659e723fb85096d6286d134ced65054c40631e88c7c0c","src/agentio.rs":"eb13376f2aed4b0b822784d43d341709b3a33f6ba52560ff48ca3e339d1e86da","src/auth.rs":"bbba836237b0c5d079f1348a96bc46b5bb6fb3cd34ca568581c9f7f8800444d1","src/cert.rs":"afecc277b918e9123d6099fc2b7f5a4ef58c9c3c1b3ca9d4790bda0a46665fe3","src/constants.rs":"83606aeb646b2833a8094f9d980c266ecc3e8cb40c93a4820da221988319dd1a","src/ech.rs":"cf6670ce7ceaaa67c8b0f93b5063cf4a0b92a0b176bbbb664b0a58f1b922b710","src/err.rs":"40658d015ac45cdd29b3bc34540c93b80a20baf5d470e0c59754fc45ce6af204","src/exp.rs":"70549c53ce8df99d62d3343697abd2a177d67ff56703a3d26048bdcdc8b87a0d","src/ext.rs":"7082cd7b44ba97275a8aefe0c31c2419d750f9621486c9c017864c82a7580423","src/hkdf.rs":"76c5abc8b2d6ee12d8a86cd730af2cf47a59b2fbfd3b8a635a1826636156794d","src/hp.rs":"04a461676c02d308f1f851b975846f83daa50ee08de9e573b4136ce4d54b4473","src/lib.rs":"42bdd28c9cd22178e2a0ab1736a0ea49cb240c78cc924d26296086d469a1f2fe","src/min_version.rs":"c6e1f98b9f56db0622ac38c1be131c55acf4a0f09ed0d6283f4d6308e2d1301a","src/p11.rs":"d46cb6c19f5c7b6fd91ce9488538475c817f62cce03f3275acca5bf6f1ec7e61","src/prio.rs":"198475faf39ffa3fe3857dff8a75a6ab0d3d54a6be7e496f008868b30653b924","src/replay.rs":"7bf84ce1964658e69d81a810f3b8d71d36d5a7fc336d83c04fb585a6a98e6d33","src/result.rs":"27067d9aba61e8162fb92bca03f9a462cf4fe2f0a259d52696b63e1f6a959a5c","src/secrets.rs":"b021c91b9c1b63373474c39e817a7d9083681be13b5466c4d2b776db9a65b9f8","src/selfencrypt.rs":"7eb5e815b2efcec42f6b4cab432a33a0679a3b1657b342971b0d0383bff16d1a","src/ssl.rs":"0a63ed08047a370f64efb314ee5686c3c47e29b3307c0f552d6cd8346ae06c03","src/time.rs":"c4c9987bfe273f19a2f5ef09920ccfe384ab1c1eaf2b2281eb4b02aa8d3b9970","tests/aead.rs":"2e99fba2f155aa8442709c4847f171f0cdfc179b2a7cd2afd853b550d02f7792","tests/agent.rs":"81266b780a40f1d8d31edbe1f43a37fd641f2cb44f75365c67b068c0d3442bb3","tests/ext.rs":"40e3bb0e5ea00fe411cfaf1a006fd4b11a22503f66d3738423361a8b7f80fe13","tests/handshake.rs":"7c6dbdf1b2ae74d15f0a3242d9969abf04ea9839eddcf1aae73379142f33a433","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"dab2631fb5a4f47227e05f508eaca4b4aa225bafced60e703e6fd1c329ac6ab1","tests/init.rs":"3cfe8411ca31ad7dfb23822bb1570e1a5b2b334857173bdd7df086b65b81d95a","tests/selfencrypt.rs":"2e0b548fc84f388b0b2367fb8d9e3e0bd25c4814a1e997b13b7849a54a529703"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-crypto/Cargo.toml b/third_party/rust/neqo-crypto/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-crypto" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -46,6 +46,7 @@ ignored = [ "serde", "serde_derive", "toml", + "windows", ] [features] @@ -115,7 +116,7 @@ default-features = false path = "../test-fixture" [build-dependencies.bindgen] -version = "0.69" +version = "0.72" features = ["runtime"] default-features = false @@ -140,6 +141,11 @@ default-features = false version = "0.5" default-features = false +[target."cfg(windows)".dependencies.windows] +version = ">=0.60,<0.63" +features = ["Win32_Security_Authentication_Identity"] +default-features = false + [lints.clippy] allow_attributes = "warn" allow_attributes_without_reason = "warn" diff --git a/third_party/rust/neqo-crypto/build.rs b/third_party/rust/neqo-crypto/build.rs @@ -108,7 +108,7 @@ fn get_bash() -> PathBuf { ) } -fn build_nss(dir: PathBuf, nsstarget: &str) { +fn build_nss(dir: PathBuf) { let mut build_nss = vec![ String::from("./build.sh"), String::from("-Ddisable_tests=1"), @@ -116,16 +116,10 @@ fn build_nss(dir: PathBuf, nsstarget: &str) { String::from("-Ddisable_libpkix=1"), String::from("-Ddisable_ckbi=1"), String::from("-Ddisable_fips=1"), + String::from("--opt"), // Generate static libraries in addition to shared libraries. String::from("--static"), ]; - if nsstarget == "Release" { - build_nss.push(String::from("-o")); - } - if let Ok(d) = env::var("NSS_JOBS") { - build_nss.push(String::from("-j")); - build_nss.push(d); - } let target = env::var("TARGET").unwrap(); if target.strip_prefix("aarch64-").is_some() { build_nss.push(String::from("--target=arm64")); @@ -357,15 +351,14 @@ fn setup_standalone(nss: &str) -> Vec<String> { // $NSS_DIR/../dist/ let nssdist = nss.parent().unwrap().join("dist"); println!("cargo:rerun-if-env-changed=NSS_TARGET"); - let nsstarget = env::var("NSS_TARGET") - .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap()); + let nsstarget = "Release"; // If NSS_PREBUILT is set, we assume that the NSS libraries are already built. if env::var("NSS_PREBUILT").is_err() { - build_nss(nss, &nsstarget); + build_nss(nss); } - let nsstarget = nssdist.join(nsstarget.trim()); + let nsstarget = nssdist.join(nsstarget); let includes = get_includes(&nsstarget, &nssdist); let nsslibdir = nsstarget.join("lib"); diff --git a/third_party/rust/neqo-crypto/src/p11.rs b/third_party/rust/neqo-crypto/src/p11.rs @@ -20,12 +20,17 @@ use crate::{ null_safe_slice, }; -#[expect( +#[allow( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + clippy::needless_raw_strings, + clippy::derive_partial_eq_without_eq, dead_code, non_snake_case, non_upper_case_globals, non_camel_case_types, clippy::unreadable_literal, + clippy::use_self, reason = "For included bindgen code." )] mod nss_p11 { diff --git a/third_party/rust/neqo-crypto/src/prio.rs b/third_party/rust/neqo-crypto/src/prio.rs @@ -8,9 +8,12 @@ dead_code, non_upper_case_globals, non_snake_case, + clippy::allow_attributes, + clippy::allow_attributes_without_reason, clippy::cognitive_complexity, clippy::too_many_lines, clippy::used_underscore_binding, + clippy::use_self, reason = "For included bindgen code." )] diff --git a/third_party/rust/neqo-crypto/src/selfencrypt.rs b/third_party/rust/neqo-crypto/src/selfencrypt.rs @@ -90,7 +90,7 @@ impl SelfEncrypt { let mut enc = Encoder::with_capacity(encoded_len); enc.encode_byte(Self::VERSION); enc.encode_byte(self.key_id); - enc.encode(&salt); + enc.encode(salt); let mut extended_aad = enc.clone(); extended_aad.encode(aad); diff --git a/third_party/rust/neqo-crypto/src/ssl.rs b/third_party/rust/neqo-crypto/src/ssl.rs @@ -6,11 +6,15 @@ #![allow( clippy::allow_attributes, + clippy::allow_attributes_without_reason, + clippy::needless_raw_strings, + clippy::derive_partial_eq_without_eq, dead_code, non_upper_case_globals, non_snake_case, clippy::cognitive_complexity, clippy::too_many_lines, + clippy::use_self, reason = "For included bindgen code." )] diff --git a/third_party/rust/neqo-http3/.cargo-checksum.json b/third_party/rust/neqo-http3/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"c622a836613a865409f5786464dd0e45dab98460a7231f2915253c16d993fca6","benches/streams.rs":"99109260f4fbca61b4ac624a4162ba305f476f4fa8de7c3c4a54edf0935f3125","src/buffered_send_stream.rs":"3eb0520b3a207597d6e7e232df8a7fc2c7bce65997af5bf92dbac2f6350d06ca","src/client_events.rs":"6e4b5a3e3a7038ca8a1cae33bf6e050f402c1e415cd53670058d6d99ae9e1b26","src/conn_params.rs":"3994bc99dc2ef77c01cc37e59b7ca91c463b228603958612497313251021a9fa","src/connection.rs":"3a61818868f518e66044b8840c6d7a0bb71236e757f36f8a6e5b1bc3af85e52d","src/connection_client.rs":"8a3be6b03f0496ddf4d6c9d9b7532ca0f141acb09decf583ad06937dbfa3d52b","src/connection_server.rs":"7c49dd96095770258e281a680e1c34af3e1eb46510d0a0b15013de626076bd8b","src/control_stream_local.rs":"df0f7b272897f30dd2fcec9d13895cb8e8b1b453a96e8983fef05a8c878e7bc1","src/control_stream_remote.rs":"652e2bfcc3e020f7a9f4e3a107557e291757a7fc2e30cf9fe95c966d2be8c122","src/features/extended_connect/connect_udp_session.rs":"4a72424b06972f0ef265f33ad93cb008af16746a700c56bca4d95099a8dab26c","src/features/extended_connect/mod.rs":"edb2e04806052a899adb316b06596f1d23a40c8fa847dd2d931bc40332b505b2","src/features/extended_connect/session.rs":"36f3af4b38a18198aacdf81e4dd3d99c98abdfe900211ac3a991083d097be70b","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"16a69b41aaada5339b85153b7194d2c1e9151ce9f25b29e02b0f24bb9500b331","src/features/extended_connect/tests/webtransport/mod.rs":"235101fed8d5c3fddd3e797f724c3013752e02462733f12298d7c9a82f666e3b","src/features/extended_connect/tests/webtransport/negotiation.rs":"b0083f8737bdea9bc0de1940c627d497fee8b79ebc218bbcea0a562ae530527f","src/features/extended_connect/tests/webtransport/sessions.rs":"7bd9fdf099cbe794ed438dc3c85f254c975e319ed2d984214c6a0c29829136d5","src/features/extended_connect/tests/webtransport/streams.rs":"e84374071268ecec302bc1c3e825bc2b7219dc11a7b88043061565f256c48542","src/features/extended_connect/webtransport_session.rs":"de19ee1daa77e83ad7ac3b858602967dcad01acca55cf6de59cc650664fa2423","src/features/extended_connect/webtransport_streams.rs":"4704ab559df3c0dad0602cd887d4cb13a17d812bf2005202ed57bfd4a8f96f8b","src/features/mod.rs":"7424e5f1939324953ed6acce76c5774f2bdfae3d1dfbdd938a4eded8a94d5c9e","src/frames/connect_udp_frame.rs":"112a8b1f848b7f0b1fc0d54aaf3e35560cd54e1ffdc1f1bc01028d798fbd45df","src/frames/hframe.rs":"8fb1e83571df12860e05b47d3e59e4c7815da56186ce6c52fb7a11c58012c513","src/frames/mod.rs":"109b49747bcb9676e8186adc770388abc3c32181430479197172ff8ca6f572b7","src/frames/reader.rs":"468a2f3199b22feda9f8ae937d17c99c89123beb0f7e48b9bb1950e8a61e34b6","src/frames/tests/hframe.rs":"43a7735fc859692633e7f3c031710a9fb635611756cb4b9f387bac0a38c0fa09","src/frames/tests/mod.rs":"3ee262c649cd0ea0120da78943dfcab5b9a08064f433076d70ac399ccf489325","src/frames/tests/reader.rs":"060e2a746fc8bb473da6dca68108048ee428d19b1502ed6c819f5969363f9281","src/frames/tests/wtframe.rs":"c6598d24f5e12972f02de6e1394362671633982db637a07e1c0bb9b56d93ea2a","src/frames/wtframe.rs":"1d6dd2d1270346992cacd72e458bc7c5fe13ba7760d360287842d7e63493fdfe","src/headers_checks.rs":"1ea31e81a501d08f6efdd9ee34a290215166af43c82c6eedb89077ab58672808","src/lib.rs":"814e61abffe9d32c88ed5d63941970bcb6802b02a7b64742aa6d0fe4a7523ae9","src/priority.rs":"5fa28fe1a235c4ffb0ab9a4506e979d7bd1a7d0136f2d525ca083bb81733db96","src/push_controller.rs":"cd05db6143b39b8913ca871bbcd00bb43271b9c9dd8ef53610313b929bbae80a","src/push_id.rs":"bf931466d0490cbe8977cd7a732d1d4970e16220f331899f5e7dab8873ece5de","src/qlog.rs":"9ae732d611f74b99fee124faed5d883ec130b1bd991c4af38608bc5bff274cc6","src/qpack_decoder_receiver.rs":"6f6ce0cf5946688f9811bc09ea69a6c02d7b173ba3a64cac31b4daa970f3004b","src/qpack_encoder_receiver.rs":"db30ea43d4cdb5f0fde2dc49e7d8b8ba12e38acbcb8b4417fe68d2551cefa2ea","src/recv_message.rs":"d008459fc7e75b39f23ef63c5c88bd194c784fbc8902e6dd66bb5439f77fcfe4","src/request_target.rs":"01f05026ea7ad47085ffe462df08401ccd162737e3b7a995e8dece31dd46ada6","src/send_message.rs":"916d93bcf4b38f68ea5fb5dfaea7555aa43a82f946124c85423caf67f74ee3b5","src/server.rs":"3448df84f6af734356e81f5896d2501a399195c85246884499ef3452fc23f68d","src/server_connection_events.rs":"22de5284446d663940d97bbcffb031ea616d6125680eaee8b5bf49b1280e1b26","src/server_events.rs":"8814a8ea3cb68218d05903eb64da7dea1fa5a7f4932ef887daae956b45a9d041","src/settings.rs":"88616d45069d942e08fe0a8ea0e52d5eff7f91998df67aa12d9484bb6f50ec5d","src/stream_type_reader.rs":"5ac672dc9d1faa2781b718016732d2bd9c7d8850989fc2756ae32c9bf29ee186","tests/classic_connect.rs":"bdbc3dece193778f30e8a4df2492eb0efc3ac066ffb18b1d5cb96671dc45da82","tests/connect_udp.rs":"9c45bd7dc96201b42b4fe2bfd6c394d10a599f2162104898d639d7920be2004d","tests/httpconn.rs":"bbf72898d2b06ded382fd5f48e1d48a2f6724a44d752929980910a6ce844b0b6","tests/priority.rs":"b641c791e95e21713697c511a0f15d69ee141f017e1ffc7d9b46caa5ed47737c","tests/send_message.rs":"eabb424f1d068e84c5d53a8e0c04a019720e4ac6e969c175de7d8aba3dbd6ae5","tests/webtransport.rs":"b503cce443ec5212c79ed273b31eee7bf01bfd81ab41c9ac99f3846ad54bcfec"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"73585f63f2a4e0955b82e303f6fc7bc3b10a23fe7f2e7efc8f5a3cc47fb0a797","benches/streams.rs":"2ec9282e63d9e5974367aab13b3c774e54e4d41ec4473d2ff0ec08c9596bc95b","src/buffered_send_stream.rs":"3eb0520b3a207597d6e7e232df8a7fc2c7bce65997af5bf92dbac2f6350d06ca","src/client_events.rs":"6e4b5a3e3a7038ca8a1cae33bf6e050f402c1e415cd53670058d6d99ae9e1b26","src/conn_params.rs":"3994bc99dc2ef77c01cc37e59b7ca91c463b228603958612497313251021a9fa","src/connection.rs":"3a61818868f518e66044b8840c6d7a0bb71236e757f36f8a6e5b1bc3af85e52d","src/connection_client.rs":"087214b7666f832524b8455f64fff9607163a7d6385aa002f6b2c5a64de23ee1","src/connection_server.rs":"7c49dd96095770258e281a680e1c34af3e1eb46510d0a0b15013de626076bd8b","src/control_stream_local.rs":"df0f7b272897f30dd2fcec9d13895cb8e8b1b453a96e8983fef05a8c878e7bc1","src/control_stream_remote.rs":"652e2bfcc3e020f7a9f4e3a107557e291757a7fc2e30cf9fe95c966d2be8c122","src/features/extended_connect/connect_udp_session.rs":"4a72424b06972f0ef265f33ad93cb008af16746a700c56bca4d95099a8dab26c","src/features/extended_connect/mod.rs":"edb2e04806052a899adb316b06596f1d23a40c8fa847dd2d931bc40332b505b2","src/features/extended_connect/session.rs":"d265dc67106a74cc5542b41f809836ae52b2d2223287198a94ca211b4a2b69b0","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"16a69b41aaada5339b85153b7194d2c1e9151ce9f25b29e02b0f24bb9500b331","src/features/extended_connect/tests/webtransport/mod.rs":"235101fed8d5c3fddd3e797f724c3013752e02462733f12298d7c9a82f666e3b","src/features/extended_connect/tests/webtransport/negotiation.rs":"b0083f8737bdea9bc0de1940c627d497fee8b79ebc218bbcea0a562ae530527f","src/features/extended_connect/tests/webtransport/sessions.rs":"7bd9fdf099cbe794ed438dc3c85f254c975e319ed2d984214c6a0c29829136d5","src/features/extended_connect/tests/webtransport/streams.rs":"e84374071268ecec302bc1c3e825bc2b7219dc11a7b88043061565f256c48542","src/features/extended_connect/webtransport_session.rs":"de19ee1daa77e83ad7ac3b858602967dcad01acca55cf6de59cc650664fa2423","src/features/extended_connect/webtransport_streams.rs":"4704ab559df3c0dad0602cd887d4cb13a17d812bf2005202ed57bfd4a8f96f8b","src/features/mod.rs":"7424e5f1939324953ed6acce76c5774f2bdfae3d1dfbdd938a4eded8a94d5c9e","src/frames/connect_udp_frame.rs":"112a8b1f848b7f0b1fc0d54aaf3e35560cd54e1ffdc1f1bc01028d798fbd45df","src/frames/hframe.rs":"6f0162e9bb8bacbff14f5b0f47d53b85b7c584bdb8a9151df60e834ae21fcbb1","src/frames/mod.rs":"3fb83b0f836de5d1cb00377d5d8ba874a70422fa1f02c28728945848a7ec61c4","src/frames/reader.rs":"468a2f3199b22feda9f8ae937d17c99c89123beb0f7e48b9bb1950e8a61e34b6","src/frames/tests/hframe.rs":"43a7735fc859692633e7f3c031710a9fb635611756cb4b9f387bac0a38c0fa09","src/frames/tests/mod.rs":"3ee262c649cd0ea0120da78943dfcab5b9a08064f433076d70ac399ccf489325","src/frames/tests/reader.rs":"b75cd92553238db3accae5256557e35fcba4d5d1204b4833b934236fae5c2c5d","src/frames/tests/wtframe.rs":"c6598d24f5e12972f02de6e1394362671633982db637a07e1c0bb9b56d93ea2a","src/frames/wtframe.rs":"19120bc6d42aa2738c9650d4bafaf20b760ef9266173f348bcc62e42c1925111","src/headers_checks.rs":"4cf29e5d12a1d427346f35a225b629867f336dbef0211045af9040a9ad258a0c","src/lib.rs":"814e61abffe9d32c88ed5d63941970bcb6802b02a7b64742aa6d0fe4a7523ae9","src/priority.rs":"5fa28fe1a235c4ffb0ab9a4506e979d7bd1a7d0136f2d525ca083bb81733db96","src/push_controller.rs":"cd05db6143b39b8913ca871bbcd00bb43271b9c9dd8ef53610313b929bbae80a","src/push_id.rs":"bf931466d0490cbe8977cd7a732d1d4970e16220f331899f5e7dab8873ece5de","src/qlog.rs":"9ae732d611f74b99fee124faed5d883ec130b1bd991c4af38608bc5bff274cc6","src/qpack_decoder_receiver.rs":"6f6ce0cf5946688f9811bc09ea69a6c02d7b173ba3a64cac31b4daa970f3004b","src/qpack_encoder_receiver.rs":"db30ea43d4cdb5f0fde2dc49e7d8b8ba12e38acbcb8b4417fe68d2551cefa2ea","src/recv_message.rs":"d008459fc7e75b39f23ef63c5c88bd194c784fbc8902e6dd66bb5439f77fcfe4","src/request_target.rs":"01f05026ea7ad47085ffe462df08401ccd162737e3b7a995e8dece31dd46ada6","src/send_message.rs":"916d93bcf4b38f68ea5fb5dfaea7555aa43a82f946124c85423caf67f74ee3b5","src/server.rs":"3448df84f6af734356e81f5896d2501a399195c85246884499ef3452fc23f68d","src/server_connection_events.rs":"4fa828e071ec595d6be554190a8d2115c3110bd0e7a69d767b0a6795daa8c9fe","src/server_events.rs":"8814a8ea3cb68218d05903eb64da7dea1fa5a7f4932ef887daae956b45a9d041","src/settings.rs":"88616d45069d942e08fe0a8ea0e52d5eff7f91998df67aa12d9484bb6f50ec5d","src/stream_type_reader.rs":"bf60d820146e20657ee76ec9fc06fc51f53c9cbe0fd83f7fbeacb9089f65d4e9","tests/classic_connect.rs":"9c9efe4929a236231ef825858c0fb6133bc2f47ed48f424859ade096d511f792","tests/connect_udp.rs":"9c45bd7dc96201b42b4fe2bfd6c394d10a599f2162104898d639d7920be2004d","tests/httpconn.rs":"bbf72898d2b06ded382fd5f48e1d48a2f6724a44d752929980910a6ce844b0b6","tests/non_ascii_headers.rs":"ca5846ae08bc1c797d18c98930951442e06d8fd78895a95469d475f6915ab2ec","tests/priority.rs":"b641c791e95e21713697c511a0f15d69ee141f017e1ffc7d9b46caa5ed47737c","tests/send_message.rs":"eabb424f1d068e84c5d53a8e0c04a019720e4ac6e969c175de7d8aba3dbd6ae5","tests/webtransport.rs":"b503cce443ec5212c79ed273b31eee7bf01bfd81ab41c9ac99f3846ad54bcfec"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-http3/Cargo.toml b/third_party/rust/neqo-http3/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-http3" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false @@ -39,6 +39,9 @@ categories = [ license = "MIT OR Apache-2.0" repository = "https://github.com/mozilla/neqo/" +[package.metadata.bench.streams.codspeed] +mode = "walltime" + [package.metadata.cargo-machete] ignored = [ "criterion", @@ -57,7 +60,7 @@ disable-encryption = [ "neqo-transport/disable-encryption", "neqo-crypto/disable-encryption", ] -draft-29 = [] +draft-29 = ["neqo-transport/draft-29"] [lib] name = "neqo_http3" @@ -77,6 +80,10 @@ name = "httpconn" path = "tests/httpconn.rs" [[test]] +name = "non_ascii_headers" +path = "tests/non_ascii_headers.rs" + +[[test]] name = "priority" path = "tests/priority.rs" @@ -143,18 +150,14 @@ features = ["std"] default-features = false [dev-dependencies.criterion] -version = "0.6" -features = ["cargo_bench_support"] +version = "4" default-features = false +package = "codspeed-criterion-compat" [dev-dependencies.neqo-http3] path = "." features = ["draft-29"] -[dev-dependencies.neqo-transport] -path = "./../neqo-transport" -features = ["draft-29"] - [dev-dependencies.test-fixture] path = "../test-fixture" diff --git a/third_party/rust/neqo-http3/benches/streams.rs b/third_party/rust/neqo-http3/benches/streams.rs @@ -4,6 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] + use std::{hint::black_box, time::Duration}; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; @@ -62,8 +67,6 @@ fn criterion_benchmark(c: &mut Criterion) { group.finish(); } - - Criterion::default().configure_from_args().final_summary(); } criterion_group!(benches, criterion_benchmark); diff --git a/third_party/rust/neqo-http3/src/connection_client.rs b/third_party/rust/neqo-http3/src/connection_client.rs @@ -1019,9 +1019,9 @@ impl Http3Client { /// library, but instead must be driven by the application). /// /// [`Http3Client::process_multiple_output`] can return: - /// - a [`OutputBatch::Datagram(Datagram)`]: data that should be sent as a UDP payload, - /// - a [`OutputBatch::Callback(Duration)`]: the duration of a timer. `process_output` should - /// be called at least after the time expires, + /// - a [`OutputBatch::DatagramBatch`]: data that should be sent as a UDP payload, + /// - a [`OutputBatch::Callback`]: the duration of a timer. `process_output` should be called + /// at least after the time expires, /// - [`OutputBatch::None`]: this is returned when `Http3Client` is done and can be destroyed. /// /// The application should call this function repeatedly until a timer value or None is @@ -1383,7 +1383,7 @@ mod tests { use neqo_qpack as qpack; use neqo_transport::{ CloseReason, ConnectionEvent, ConnectionParameters, Output, State, StreamId, StreamType, - Version, INITIAL_RECV_WINDOW_SIZE, MIN_INITIAL_PACKET_SIZE, + Version, INITIAL_LOCAL_MAX_STREAM_DATA, MIN_INITIAL_PACKET_SIZE, }; use test_fixture::{ anti_replay, default_server_h3, fixture_init, new_server, now, @@ -1397,7 +1397,7 @@ mod tests { Http3Parameters, Http3State, Rc, RefCell, }; use crate::{ - frames::{HFrame, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES}, + frames::{HFrame, HFrameType}, qpack_encoder_receiver::EncoderRecvStream, settings::{HSetting, HSettingType, H3_RESERVED_SETTINGS}, Http3Server, Priority, PushId, RecvStream as _, @@ -2848,7 +2848,7 @@ mod tests { if let ConnectionEvent::RecvStreamReadable { stream_id } = e { if stream_id == request_stream_id { // Read the DATA frame. - let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE]; + let mut buf = vec![1_u8; INITIAL_LOCAL_MAX_STREAM_DATA]; let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap(); assert!(fin); assert_eq!( @@ -2923,7 +2923,7 @@ mod tests { // The second frame cannot fit. let sent = client.send_data( request_stream_id, - &vec![0_u8; INITIAL_RECV_WINDOW_SIZE], + &vec![0_u8; INITIAL_LOCAL_MAX_STREAM_DATA], now(), ); assert_eq!(sent, Ok(expected_second_data_frame.len())); @@ -2934,7 +2934,7 @@ mod tests { let mut out = client.process_output(now()); // We need to loop a bit until all data has been sent. Once for every 1K // of data. - for _i in 0..INITIAL_RECV_WINDOW_SIZE / 1000 { + for _i in 0..INITIAL_LOCAL_MAX_STREAM_DATA / 1000 { out = server.conn.process(out.dgram(), now()); out = client.process(out.dgram(), now()); } @@ -2944,7 +2944,7 @@ mod tests { if let ConnectionEvent::RecvStreamReadable { stream_id } = e { if stream_id == request_stream_id { // Read DATA frames. - let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE]; + let mut buf = vec![1_u8; INITIAL_LOCAL_MAX_STREAM_DATA]; let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap(); assert!(fin); assert_eq!( @@ -2997,7 +2997,7 @@ mod tests { // After the first frame there is exactly 63+2 bytes left in the send buffer. #[test] fn fetch_two_data_frame_second_63bytes() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 88); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 88); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]); } @@ -3006,7 +3006,7 @@ mod tests { // but we can only send 63 bytes. #[test] fn fetch_two_data_frame_second_63bytes_place_for_66() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 89); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 89); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]); } @@ -3015,7 +3015,7 @@ mod tests { // but we can only send 64 bytes. #[test] fn fetch_two_data_frame_second_64bytes_place_for_67() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 90); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 90); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x40, 0x40], &[0_u8; 64]); } @@ -3023,7 +3023,7 @@ mod tests { // After the first frame there is exactly 16383+3 bytes left in the send buffer. #[test] fn fetch_two_data_frame_second_16383bytes() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16409); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 16409); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); } @@ -3032,7 +3032,7 @@ mod tests { // send 16383 bytes. #[test] fn fetch_two_data_frame_second_16383bytes_place_for_16387() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16410); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 16410); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); } @@ -3041,7 +3041,7 @@ mod tests { // send 16383 bytes. #[test] fn fetch_two_data_frame_second_16383bytes_place_for_16388() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16411); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 16411); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); } @@ -3050,7 +3050,7 @@ mod tests { // 16384 bytes. #[test] fn fetch_two_data_frame_second_16384bytes_place_for_16389() { - let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16412); + let (buf, hdr) = alloc_buffer(INITIAL_LOCAL_MAX_STREAM_DATA - 16412); fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x80, 0x0, 0x40, 0x0], &[0_u8; 16384]); } @@ -4132,7 +4132,7 @@ mod tests { hframe.encode(&mut d); let d_frame = HFrame::Data { len: 3 }; d_frame.encode(&mut d); - d.encode(&[0x61, 0x62, 0x63]); + d.encode([0x61, 0x62, 0x63]); server_send_response_and_exchange_packet( &mut client, &mut server, @@ -5134,7 +5134,7 @@ mod tests { hframe.encode(&mut d); let d_frame = HFrame::Data { len: 3 }; d_frame.encode(&mut d); - d.encode(&[0x61, 0x62, 0x63]); + d.encode([0x61, 0x62, 0x63]); _ = server .conn .stream_send(request_stream_id, d.as_ref()) @@ -6886,7 +6886,7 @@ mod tests { #[test] fn reserved_frames() { - for f in H3_RESERVED_FRAME_TYPES { + for f in HFrameType::RESERVED { let mut enc = Encoder::default(); enc.encode_varint(*f); test_wrong_frame_on_control_stream(enc.as_ref()); @@ -6907,7 +6907,7 @@ mod tests { .unwrap(); // Create a settings frame of length 2. let mut enc = Encoder::default(); - enc.encode_varint(H3_FRAME_TYPE_SETTINGS); + enc.encode_varint(HFrameType::SETTINGS); enc.encode_varint(2_u64); // The settings frame contains a reserved settings type and some value (0x1). enc.encode_varint(*s); diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/session.rs b/third_party/rust/neqo-http3/src/features/extended_connect/session.rs @@ -9,6 +9,7 @@ use std::{ collections::HashSet, fmt::{self, Debug, Display, Formatter}, rc::Rc, + str::from_utf8, time::Instant, }; @@ -261,7 +262,7 @@ impl Session { .iter() .find_map(|h| { if h.name() == ":status" { - h.value().parse::<u16>().ok() + from_utf8(h.value()).ok()?.parse::<u16>().ok() } else { None } diff --git a/third_party/rust/neqo-http3/src/frames/hframe.rs b/third_party/rust/neqo-http3/src/frames/hframe.rs @@ -15,22 +15,20 @@ use crate::{frames::reader::FrameDecoder, settings::HSettings, Error, Priority, #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HFrameType(pub u64); -pub const H3_FRAME_TYPE_DATA: HFrameType = HFrameType(0x0); -pub const H3_FRAME_TYPE_HEADERS: HFrameType = HFrameType(0x1); -pub const H3_FRAME_TYPE_CANCEL_PUSH: HFrameType = HFrameType(0x3); -pub const H3_FRAME_TYPE_SETTINGS: HFrameType = HFrameType(0x4); -pub const H3_FRAME_TYPE_PUSH_PROMISE: HFrameType = HFrameType(0x5); -pub const H3_FRAME_TYPE_GOAWAY: HFrameType = HFrameType(0x7); -pub const H3_FRAME_TYPE_MAX_PUSH_ID: HFrameType = HFrameType(0xd); -pub const H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST: HFrameType = HFrameType(0xf0700); -pub const H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH: HFrameType = HFrameType(0xf0701); +impl HFrameType { + pub const DATA: Self = Self(0x0); + pub const HEADERS: Self = Self(0x1); + pub const CANCEL_PUSH: Self = Self(0x3); + pub const SETTINGS: Self = Self(0x4); + pub const PUSH_PROMISE: Self = Self(0x5); + pub const GOAWAY: Self = Self(0x7); + pub const MAX_PUSH_ID: Self = Self(0xd); + pub const PRIORITY_UPDATE_REQUEST: Self = Self(0xf0700); + pub const PRIORITY_UPDATE_PUSH: Self = Self(0xf0701); -pub const H3_RESERVED_FRAME_TYPES: &[HFrameType] = &[ - HFrameType(0x2), - HFrameType(0x6), - HFrameType(0x8), - HFrameType(0x9), -]; + /// See <https://www.rfc-editor.org/rfc/rfc9114.html#section-11.2.1> for these reserved types. + pub const RESERVED: &[Self] = &[Self(0x2), Self(0x6), Self(0x8), Self(0x9)]; +} impl From<HFrameType> for u64 { fn from(t: HFrameType) -> Self { @@ -77,15 +75,15 @@ pub enum HFrame { impl HFrame { fn get_type(&self) -> HFrameType { match self { - Self::Data { .. } => H3_FRAME_TYPE_DATA, - Self::Headers { .. } => H3_FRAME_TYPE_HEADERS, - Self::CancelPush { .. } => H3_FRAME_TYPE_CANCEL_PUSH, - Self::Settings { .. } => H3_FRAME_TYPE_SETTINGS, - Self::PushPromise { .. } => H3_FRAME_TYPE_PUSH_PROMISE, - Self::Goaway { .. } => H3_FRAME_TYPE_GOAWAY, - Self::MaxPushId { .. } => H3_FRAME_TYPE_MAX_PUSH_ID, - Self::PriorityUpdateRequest { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST, - Self::PriorityUpdatePush { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH, + Self::Data { .. } => HFrameType::DATA, + Self::Headers { .. } => HFrameType::HEADERS, + Self::CancelPush { .. } => HFrameType::CANCEL_PUSH, + Self::Settings { .. } => HFrameType::SETTINGS, + Self::PushPromise { .. } => HFrameType::PUSH_PROMISE, + Self::Goaway { .. } => HFrameType::GOAWAY, + Self::MaxPushId { .. } => HFrameType::MAX_PUSH_ID, + Self::PriorityUpdateRequest { .. } => HFrameType::PRIORITY_UPDATE_REQUEST, + Self::PriorityUpdatePush { .. } => HFrameType::PRIORITY_UPDATE_PUSH, Self::Grease => { let r = u64::from_ne_bytes(random::<8>()); // Zero out the top 7 bits: 2 for being a varint; 5 to account for the *0x1f. @@ -157,26 +155,26 @@ impl HFrame { impl FrameDecoder<Self> for HFrame { fn frame_type_allowed(frame_type: HFrameType) -> Res<()> { - if H3_RESERVED_FRAME_TYPES.contains(&frame_type) { + if HFrameType::RESERVED.contains(&frame_type) { return Err(Error::HttpFrameUnexpected); } Ok(()) } fn decode(frame_type: HFrameType, frame_len: u64, data: Option<&[u8]>) -> Res<Option<Self>> { - if frame_type == H3_FRAME_TYPE_DATA { + if frame_type == HFrameType::DATA { Ok(Some(Self::Data { len: frame_len })) } else if let Some(payload) = data { let mut dec = Decoder::from(payload); Ok(match frame_type { - H3_FRAME_TYPE_DATA => unreachable!("DATA frame has been handled already"), - H3_FRAME_TYPE_HEADERS => Some(Self::Headers { + HFrameType::DATA => unreachable!("DATA frame has been handled already"), + HFrameType::HEADERS => Some(Self::Headers { header_block: dec.decode_remainder().to_vec(), }), - H3_FRAME_TYPE_CANCEL_PUSH => Some(Self::CancelPush { + HFrameType::CANCEL_PUSH => Some(Self::CancelPush { push_id: dec.decode_varint().ok_or(Error::HttpFrame)?.into(), }), - H3_FRAME_TYPE_SETTINGS => { + HFrameType::SETTINGS => { let mut settings = HSettings::default(); settings.decode_frame_contents(&mut dec).map_err(|e| { if e == Error::HttpSettings { @@ -187,21 +185,21 @@ impl FrameDecoder<Self> for HFrame { })?; Some(Self::Settings { settings }) } - H3_FRAME_TYPE_PUSH_PROMISE => Some(Self::PushPromise { + HFrameType::PUSH_PROMISE => Some(Self::PushPromise { push_id: dec.decode_varint().ok_or(Error::HttpFrame)?.into(), header_block: dec.decode_remainder().to_vec(), }), - H3_FRAME_TYPE_GOAWAY => Some(Self::Goaway { + HFrameType::GOAWAY => Some(Self::Goaway { stream_id: StreamId::new(dec.decode_varint().ok_or(Error::HttpFrame)?), }), - H3_FRAME_TYPE_MAX_PUSH_ID => Some(Self::MaxPushId { + HFrameType::MAX_PUSH_ID => Some(Self::MaxPushId { push_id: dec.decode_varint().ok_or(Error::HttpFrame)?.into(), }), - H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST | H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH => { + HFrameType::PRIORITY_UPDATE_REQUEST | HFrameType::PRIORITY_UPDATE_PUSH => { let element_id = dec.decode_varint().ok_or(Error::HttpFrame)?; let priority = dec.decode_remainder(); let priority = Priority::from_bytes(priority)?; - if frame_type == H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST { + if frame_type == HFrameType::PRIORITY_UPDATE_REQUEST { Some(Self::PriorityUpdateRequest { element_id, priority, @@ -223,15 +221,15 @@ impl FrameDecoder<Self> for HFrame { fn is_known_type(frame_type: HFrameType) -> bool { matches!( frame_type, - H3_FRAME_TYPE_DATA - | H3_FRAME_TYPE_HEADERS - | H3_FRAME_TYPE_CANCEL_PUSH - | H3_FRAME_TYPE_SETTINGS - | H3_FRAME_TYPE_PUSH_PROMISE - | H3_FRAME_TYPE_GOAWAY - | H3_FRAME_TYPE_MAX_PUSH_ID - | H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST - | H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH + HFrameType::DATA + | HFrameType::HEADERS + | HFrameType::CANCEL_PUSH + | HFrameType::SETTINGS + | HFrameType::PUSH_PROMISE + | HFrameType::GOAWAY + | HFrameType::MAX_PUSH_ID + | HFrameType::PRIORITY_UPDATE_REQUEST + | HFrameType::PRIORITY_UPDATE_PUSH ) } } diff --git a/third_party/rust/neqo-http3/src/frames/mod.rs b/third_party/rust/neqo-http3/src/frames/mod.rs @@ -15,7 +15,7 @@ pub use connect_udp_frame::Frame as ConnectUdpFrame; unused_imports, reason = "These are exported." )] -pub use hframe::{HFrame, H3_FRAME_TYPE_HEADERS, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES}; +pub use hframe::{HFrame, HFrameType}; pub use reader::{FrameReader, StreamReaderConnectionWrapper, StreamReaderRecvStreamWrapper}; pub use wtframe::WebTransportFrame; diff --git a/third_party/rust/neqo-http3/src/frames/tests/reader.rs b/third_party/rust/neqo-http3/src/frames/tests/reader.rs @@ -389,14 +389,14 @@ fn complete_and_incomplete_frames() { const FRAME_LEN: usize = 10; const HEADER_BLOCK: &[u8] = &[0x01, 0x02, 0x03, 0x04]; - // H3_FRAME_TYPE_DATA len=0 + // HFrameType::DATA len=0 let f = HFrame::Data { len: 0 }; let mut enc = Encoder::with_capacity(2); f.encode(&mut enc); let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, 2); - // H3_FRAME_TYPE_DATA len=FRAME_LEN + // HFrameType::DATA len=FRAME_LEN let f = HFrame::Data { len: FRAME_LEN as u64, }; @@ -406,7 +406,7 @@ fn complete_and_incomplete_frames() { buf.resize(FRAME_LEN + buf.len(), 0); test_complete_and_incomplete_frame::<HFrame>(&buf, 2); - // H3_FRAME_TYPE_HEADERS empty header block + // HFrameType::HEADERS empty header block let f = HFrame::Headers { header_block: Vec::new(), }; @@ -415,7 +415,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, 2); - // H3_FRAME_TYPE_HEADERS + // HFrameType::HEADERS let f = HFrame::Headers { header_block: HEADER_BLOCK.to_vec(), }; @@ -424,7 +424,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); - // H3_FRAME_TYPE_CANCEL_PUSH + // HFrameType::CANCEL_PUSH let f = HFrame::CancelPush { push_id: PushId::new(5), }; @@ -433,7 +433,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); - // H3_FRAME_TYPE_SETTINGS + // HFrameType::SETTINGS let f = HFrame::Settings { settings: HSettings::new(&[HSetting::new(HSettingType::MaxHeaderListSize, 4)]), }; @@ -442,7 +442,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); - // H3_FRAME_TYPE_PUSH_PROMISE + // HFrameType::PUSH_PROMISE let f = HFrame::PushPromise { push_id: PushId::new(4), header_block: HEADER_BLOCK.to_vec(), @@ -452,7 +452,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); - // H3_FRAME_TYPE_GOAWAY + // HFrameType::GOAWAY let f = HFrame::Goaway { stream_id: StreamId::new(5), }; @@ -461,7 +461,7 @@ fn complete_and_incomplete_frames() { let buf: Vec<_> = enc.into(); test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); - // H3_FRAME_TYPE_MAX_PUSH_ID + // HFrameType::MAX_PUSH_ID let f = HFrame::MaxPushId { push_id: PushId::new(5), }; @@ -473,7 +473,7 @@ fn complete_and_incomplete_frames() { #[test] fn complete_and_incomplete_wt_frames() { - // H3_FRAME_TYPE_MAX_PUSH_ID + // HFrameType::MAX_PUSH_ID let f = WebTransportFrame::CloseSession { error: 5, message: "Hello".to_string(), diff --git a/third_party/rust/neqo-http3/src/frames/wtframe.rs b/third_party/rust/neqo-http3/src/frames/wtframe.rs @@ -11,17 +11,24 @@ use crate::{frames::reader::FrameDecoder, Error, Res}; pub type WebTransportFrameType = u64; -const WT_FRAME_CLOSE_SESSION: WebTransportFrameType = 0x2843; -const WT_FRAME_CLOSE_MAX_MESSAGE_SIZE: u64 = 1024; - #[derive(PartialEq, Eq, Debug)] pub enum WebTransportFrame { CloseSession { error: u32, message: String }, } impl WebTransportFrame { + /// The frame type for WebTransport `CLOSE_SESSION`, as defined in + /// [WebTransport over HTTP/3 (RFC 9297, Section 4.6)](https://datatracker.ietf.org/doc/html/rfc9297#section-4.6). + /// The value 0x2843 is assigned for `CLOSE_SESSION`. + const CLOSE_SESSION: WebTransportFrameType = 0x2843; + + /// The maximum allowed message size for `CLOSE_SESSION` messages, as recommended + /// in [WebTransport over HTTP/3 (RFC 9297, Section 4.6)](https://datatracker.ietf.org/doc/html/rfc9297#section-4.6). + /// The value 1024 is used to limit the message size for security and interoperability. + const CLOSE_MAX_MESSAGE_SIZE: u64 = 1024; + pub fn encode(&self, enc: &mut Encoder) { - enc.encode_varint(WT_FRAME_CLOSE_SESSION); + enc.encode_varint(Self::CLOSE_SESSION); let Self::CloseSession { error, message } = &self; enc.encode_varint(4 + message.len() as u64); enc.encode_uint(4, *error); @@ -33,8 +40,8 @@ impl FrameDecoder<Self> for WebTransportFrame { fn decode(frame_type: HFrameType, frame_len: u64, data: Option<&[u8]>) -> Res<Option<Self>> { if let Some(payload) = data { let mut dec = Decoder::from(payload); - if frame_type == HFrameType(WT_FRAME_CLOSE_SESSION) { - if frame_len > WT_FRAME_CLOSE_MAX_MESSAGE_SIZE + 4 { + if frame_type == HFrameType(Self::CLOSE_SESSION) { + if frame_len > Self::CLOSE_MAX_MESSAGE_SIZE + 4 { return Err(Error::HttpMessage); } let error = dec.decode_uint().ok_or(Error::HttpMessage)?; @@ -51,6 +58,6 @@ impl FrameDecoder<Self> for WebTransportFrame { } fn is_known_type(frame_type: HFrameType) -> bool { - frame_type == HFrameType(WT_FRAME_CLOSE_SESSION) + frame_type == HFrameType(Self::CLOSE_SESSION) } } diff --git a/third_party/rust/neqo-http3/src/headers_checks.rs b/third_party/rust/neqo-http3/src/headers_checks.rs @@ -50,7 +50,10 @@ impl TryFrom<(MessageType, &str)> for PseudoHeaderState { /// a status header or if the value of the header is 101 or cannot be parsed. pub fn is_interim(headers: &[Header]) -> Res<bool> { if let Some(h) = headers.iter().take(1).find_header(":status") { - let status_code = h.value().parse::<u16>().map_err(|_| Error::InvalidHeader)?; + let status_code = std::str::from_utf8(h.value()) + .map_err(|_| Error::InvalidHeader)? + .parse::<u16>() + .map_err(|_| Error::InvalidHeader)?; if status_code == 101 { // https://datatracker.ietf.org/doc/html/draft-ietf-quic-http#section-4.3 Err(Error::InvalidHeader) @@ -92,9 +95,9 @@ fn track_pseudo( /// /// Returns an error if headers are not well formed. pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> { - let mut method_value: Option<&str> = None; - let mut protocol_value: Option<&str> = None; - let mut scheme_value: Option<&str> = None; + let mut method_value: Option<&[u8]> = None; + let mut protocol_value: Option<&[u8]> = None; + let mut scheme_value: Option<&[u8]> = None; let mut pseudo_state = EnumSet::new(); for header in headers { let is_pseudo = track_pseudo(header.name(), &mut pseudo_state, message_type)?; @@ -120,11 +123,11 @@ pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> { let pseudo_header_mask = match message_type { MessageType::Response => enum_set!(PseudoHeaderState::Status), MessageType::Request => { - if method_value == Some("CONNECT") { + if method_value == Some(b"CONNECT".as_ref()) { let connect_mask = PseudoHeaderState::Method | PseudoHeaderState::Authority; if let Some(protocol) = protocol_value { // For a webtransport CONNECT, the :scheme field must be set to https. - if protocol == "webtransport" && scheme_value != Some("https") { + if protocol == b"webtransport" && scheme_value != Some(b"https".as_ref()) { return Err(Error::InvalidHeader); } // The CONNECT request for with :protocol included must have the scheme, @@ -141,7 +144,7 @@ pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> { if (MessageType::Request == message_type) && pseudo_state.contains(PseudoHeaderState::Protocol) - && method_value != Some("CONNECT") + && method_value != Some(b"CONNECT".as_ref()) { return Err(Error::InvalidHeader); } @@ -173,7 +176,7 @@ pub fn trailers_valid(headers: &[Header]) -> Res<()> { mod tests { use neqo_common::Header; - use super::headers_valid; + use super::{headers_valid, is_interim}; use crate::MessageType; fn create_connect_headers() -> Vec<Header> { @@ -237,4 +240,19 @@ mod tests { ) .is_err()); } + + #[test] + fn is_interim_invalid_utf8() { + // Create a header with invalid UTF-8 bytes in the status value + let invalid_utf8_bytes = vec![0xFF, 0xFE, 0xFD]; + let header = Header::new(":status", invalid_utf8_bytes.as_slice()); + let headers = vec![header]; + assert!(is_interim(&headers).is_err()); + } + + #[test] + fn is_interim_not_a_number() { + let headers = vec![Header::new(":status", "not-a-number")]; + assert!(is_interim(&headers).is_err()); + } } diff --git a/third_party/rust/neqo-http3/src/server_connection_events.rs b/third_party/rust/neqo-http3/src/server_connection_events.rs @@ -148,12 +148,12 @@ impl HttpRecvStreamEvents for Http3ServerConnEvents { fn extended_connect_new_session(&self, stream_id: StreamId, headers: Vec<Header>) { match headers.find_header(":protocol").map(Header::value) { - Some("webtransport") => { + Some(b"webtransport") => { self.insert(Http3ServerConnEvent::WebTransport( WebTransportEvent::Session { stream_id, headers }, )); } - Some("connect-udp") => { + Some(b"connect-udp") => { self.insert(Http3ServerConnEvent::ConnectUdp(ConnectUdpEvent::Session { stream_id, headers, diff --git a/third_party/rust/neqo-http3/src/stream_type_reader.rs b/third_party/rust/neqo-http3/src/stream_type_reader.rs @@ -12,7 +12,7 @@ use neqo_transport::{Connection, StreamId, StreamType}; use crate::{ control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL, - frames::{hframe::HFrameType, reader::FrameDecoder, HFrame, H3_FRAME_TYPE_HEADERS}, + frames::{hframe::HFrameType, reader::FrameDecoder, HFrame}, CloseType, Error, Http3StreamType, PushId, ReceiveOutput, RecvStream, Res, Stream, }; @@ -57,7 +57,7 @@ impl NewStreamType { // WEBTRANSPORT_STREAM (above), and HEADERS, and we have to ignore unknown types, // but any other frame type is bad if we know about it. if <HFrame as FrameDecoder<HFrame>>::is_known_type(HFrameType(stream_type)) - && HFrameType(stream_type) != H3_FRAME_TYPE_HEADERS + && HFrameType(stream_type) != HFrameType::HEADERS { Err(Error::HttpFrame) } else { @@ -254,9 +254,8 @@ mod tests { WEBTRANSPORT_UNI_STREAM, }; use crate::{ - control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL, - frames::{H3_FRAME_TYPE_HEADERS, H3_FRAME_TYPE_SETTINGS}, - CloseType, Error, NewStreamType, PushId, ReceiveOutput, RecvStream as _, Res, + control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL, frames::HFrameType, CloseType, Error, + NewStreamType, PushId, ReceiveOutput, RecvStream as _, Res, }; struct Test { @@ -399,10 +398,10 @@ mod tests { fn decode_stream_http() { let mut t = Test::new(StreamType::BiDi, Role::Server); t.decode( - &[u64::from(H3_FRAME_TYPE_HEADERS)], + &[u64::from(HFrameType::HEADERS)], false, &Ok(( - ReceiveOutput::NewStream(NewStreamType::Http(u64::from(H3_FRAME_TYPE_HEADERS))), + ReceiveOutput::NewStream(NewStreamType::Http(u64::from(HFrameType::HEADERS))), true, )), true, @@ -410,9 +409,9 @@ mod tests { let mut t = Test::new(StreamType::UniDi, Role::Server); t.decode( - &[u64::from(H3_FRAME_TYPE_HEADERS)], /* this is the same as a - * HTTP3_UNI_STREAM_TYPE_PUSH which - * is not aallowed on the server side. */ + &[u64::from(HFrameType::HEADERS)], /* this is the same as a + * HTTP3_UNI_STREAM_TYPE_PUSH which + * is not aallowed on the server side. */ false, &Err(Error::HttpStreamCreation), true, @@ -420,7 +419,7 @@ mod tests { let mut t = Test::new(StreamType::BiDi, Role::Client); t.decode( - &[u64::from(H3_FRAME_TYPE_HEADERS)], + &[u64::from(HFrameType::HEADERS)], false, &Err(Error::HttpStreamCreation), true, @@ -428,8 +427,8 @@ mod tests { let mut t = Test::new(StreamType::UniDi, Role::Client); t.decode( - &[u64::from(H3_FRAME_TYPE_HEADERS), 0xaaaa_aaaa], /* this is the same as a - * HTTP3_UNI_STREAM_TYPE_PUSH */ + &[u64::from(HFrameType::HEADERS), 0xaaaa_aaaa], /* this is the same as a + * HTTP3_UNI_STREAM_TYPE_PUSH */ false, &Ok(( ReceiveOutput::NewStream(NewStreamType::Push(PushId::new(0xaaaa_aaaa))), @@ -440,7 +439,7 @@ mod tests { let mut t = Test::new(StreamType::BiDi, Role::Server); t.decode( - &[H3_FRAME_TYPE_SETTINGS.into()], + &[HFrameType::SETTINGS.into()], true, &Err(Error::HttpFrame), true, diff --git a/third_party/rust/neqo-http3/tests/classic_connect.rs b/third_party/rust/neqo-http3/tests/classic_connect.rs @@ -34,11 +34,11 @@ fn classic_connect() { }; assert_eq!( headers.find_header(":method").map(Header::value), - Some("CONNECT") + Some(b"CONNECT".as_ref()) ); assert_eq!( headers.find_header(":authority").map(Header::value), - Some(AUTHORITY) + Some(AUTHORITY.as_bytes()) ); // > The :scheme and :path pseudo-header fields are omitted // @@ -83,7 +83,7 @@ fn classic_connect() { assert_eq!(stream_id, stream.stream_id()); assert_eq!( headers.find_header(":status").map(Header::value), - Some("200") + Some(b"200".as_ref()) ); let Some(Http3ClientEvent::DataReadable { stream_id }) = client.next_event() else { diff --git a/third_party/rust/neqo-http3/tests/non_ascii_headers.rs b/third_party/rust/neqo-http3/tests/non_ascii_headers.rs @@ -0,0 +1,103 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use neqo_common::{event::Provider as _, header::HeadersExt as _}; +use neqo_http3::{Header, Http3ClientEvent, Http3ServerEvent, Priority}; +use test_fixture::{default_http3_client, default_http3_server, exchange_packets, now}; + +fn echo_header(request_header_name: &str, response_header_name: &str, test_data: &[u8]) { + // Connect a client and a server. + let mut client = default_http3_client(); + let mut server = default_http3_server(); + let out = test_fixture::connect_peers(&mut client, &mut server); + assert_eq!(server.process(out, now()).dgram(), None); + + // Ignore all events so far. + drop(server.events()); + drop(client.events()); + + // Create a header with the test data + let custom_header = Header::new(request_header_name, test_data); + + // Have client send a GET request with the custom header + let stream_id = client + .fetch( + now(), + "GET", + ("https", "something.com", "/"), + &[custom_header], + Priority::default(), + ) + .unwrap(); + client.stream_close_send(stream_id, now()).unwrap(); + exchange_packets(&mut client, &mut server, false, None); + + // Server receives the request - loop through events to find Headers + let mut received_stream = None; + let mut received_headers = None; + while let Some(event) = server.next_event() { + if let Http3ServerEvent::Headers { + stream, + headers, + fin: _, + } = event + { + received_stream = Some(stream); + received_headers = Some(headers); + break; + } + } + + let stream = received_stream.expect("No Headers event received from server"); + let headers = received_headers.expect("No headers received"); + + // Verify the server received the header correctly + let received_header = headers + .find_header(request_header_name) + .expect("Custom header not found"); + assert_eq!(received_header.value(), test_data); + + // Server echoes the value back in a different header + stream + .send_headers(&[ + Header::new(":status", "200"), + Header::new(response_header_name, received_header.value()), + ]) + .unwrap(); + stream.stream_close_send(now()).unwrap(); + exchange_packets(&mut client, &mut server, false, None); + + // Client receives the response + let mut response_headers = None; + while let Some(event) = client.next_event() { + if let Http3ClientEvent::HeaderReady { headers, .. } = event { + response_headers = Some(headers); + break; + } + } + + let headers = response_headers.expect("No response headers received"); + + // Verify the echoed header contains the original data + let echoed_header = headers + .find_header(response_header_name) + .expect("Echoed header not found"); + assert_eq!(echoed_header.value(), test_data); +} + +#[test] +fn extended_ascii_non_utf8_header_echo() { + // Create a header with binary data + let test_bytes: Vec<u8> = vec![0xE4]; // "ä" in extended ASCII (ISO-8859-1), invalid UTF-8 + echo_header("x-custom-data", "x-echoed-data", &test_bytes); +} + +#[test] +fn non_ascii_emoji_header_echo() { + // Create a header with non-ASCII but valid UTF-8 (emojis: rocket + star + laptop) + let emoji_data = b"\xF0\x9F\x9A\x80\xF0\x9F\x8C\x9F\xF0\x9F\x92\xBB"; + echo_header("x-emoji-data", "x-echoed-emoji", emoji_data); +} diff --git a/third_party/rust/neqo-qpack/.cargo-checksum.json b/third_party/rust/neqo-qpack/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"8c35ea662ab6be4f72bb6d7e1c6e4a22611736ff9e95b4f765b5c9fc7f7046b5","src/decoder.rs":"1c5321b14c250bb53dc5435f0cb1b6e3c27029f3bcb149f00e1146702bc1e9ca","src/decoder_instructions.rs":"6b36eea01fdf92088ddac6b6988a239c28ddeb3cc7ecb16abf302f5d1ca8191a","src/encoder.rs":"f3e8002fdf690e820a850013fe288ba9c3279aa0034992a3547e060af8bd4f17","src/encoder_instructions.rs":"1cf1ba5ab2bbfc8f77ecfbc2bc59e40f77e12f85af5c10d0db2652000a8ff102","src/header_block.rs":"6c25b488a72864d8e0ad756af56607020b80bade8e660f701b3271d2e9d4a75f","src/huffman.rs":"c3740084c71580a5270c73cae4b7c5035fae913f533474f4a9cbc39b1f29adb7","src/huffman_decode_helper.rs":"c799b85c7738cdf6a1f6ea039062d2ea5ce0b4f08789d64e90a8712d57040d2b","src/huffman_table.rs":"aaa9ee17b8bceb47877d41fdf12fd29d49662a12db183acdb6b06c6e2ad182d9","src/lib.rs":"7b357e2ac248c392415f19b916373979272c33fa082d9158be4dd1ef5240cc1a","src/prefix.rs":"31bfb11d334a6df619bcc2720621e44a656be2514fad9033531a712d47dbe672","src/qlog.rs":"1ca9bdbc974024b32515af6b6529f5a69e80eae3f7d74445af304dc341a0eda1","src/qpack_send_buf.rs":"cec9b34cc0f2cd3a38eb15111c5f0418e31875d3ee20ecc1ed14f076da80979d","src/reader.rs":"6d698e21260d94c742b929c4112151b2ef6f2250f13564586e0ca0b9082115b5","src/static_table.rs":"6e5ec26e2b6bd63375d2d77e72748151d430d1629a8e497ec0d0ea21c078524a","src/stats.rs":"cb01723249f60e15a5cd7efd9cbab409fddc588d1df655ed06ba8c80e3d5d28e","src/table.rs":"f19b3016bffee54f8e3f52034e2eb36fc8f83a04b203074a8d4cec65367d3c32"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"cf649720866351d858b791fd9e91a17a0dee0c52a2b184a7a800e881f8fff02d","src/decoder.rs":"1c5321b14c250bb53dc5435f0cb1b6e3c27029f3bcb149f00e1146702bc1e9ca","src/decoder_instructions.rs":"6b36eea01fdf92088ddac6b6988a239c28ddeb3cc7ecb16abf302f5d1ca8191a","src/encoder.rs":"634d1f0548c6c8e601d0ee901927738c57753be8c76a84d16e69245674a8aee8","src/encoder_instructions.rs":"1cf1ba5ab2bbfc8f77ecfbc2bc59e40f77e12f85af5c10d0db2652000a8ff102","src/header_block.rs":"6ed65adefdd251178bdfac0601db4ee8bbd00221de89947e52dc9335c0ac149b","src/huffman.rs":"c3740084c71580a5270c73cae4b7c5035fae913f533474f4a9cbc39b1f29adb7","src/huffman_decode_helper.rs":"3b983bafc69f58810ae93890f101f82148d5d6dbbce8bc232f58bcb50f3946a1","src/huffman_table.rs":"aaa9ee17b8bceb47877d41fdf12fd29d49662a12db183acdb6b06c6e2ad182d9","src/lib.rs":"7b357e2ac248c392415f19b916373979272c33fa082d9158be4dd1ef5240cc1a","src/prefix.rs":"31bfb11d334a6df619bcc2720621e44a656be2514fad9033531a712d47dbe672","src/qlog.rs":"1ca9bdbc974024b32515af6b6529f5a69e80eae3f7d74445af304dc341a0eda1","src/qpack_send_buf.rs":"cec9b34cc0f2cd3a38eb15111c5f0418e31875d3ee20ecc1ed14f076da80979d","src/reader.rs":"146c4d1963390ffad2491fb74d9c588e316893c3062d9a7663d40233d800b937","src/static_table.rs":"6e5ec26e2b6bd63375d2d77e72748151d430d1629a8e497ec0d0ea21c078524a","src/stats.rs":"cb01723249f60e15a5cd7efd9cbab409fddc588d1df655ed06ba8c80e3d5d28e","src/table.rs":"f19b3016bffee54f8e3f52034e2eb36fc8f83a04b203074a8d4cec65367d3c32"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-qpack/Cargo.toml b/third_party/rust/neqo-qpack/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-qpack" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false diff --git a/third_party/rust/neqo-qpack/src/encoder.rs b/third_party/rust/neqo-qpack/src/encoder.rs @@ -418,14 +418,14 @@ impl Encoder { for iter in h { let name = iter.name().as_bytes().to_vec(); - let value = iter.value().as_bytes().to_vec(); + let value = iter.value(); qtrace!("encoding {name:x?} {value:x?}"); if let Some(LookupResult { index, static_table, value_matches, - }) = self.table.lookup(&name, &value, can_block) + }) = self.table.lookup(&name, value, can_block) { qtrace!( "[{self}] found a {} entry, value-match={value_matches}", @@ -438,7 +438,7 @@ impl Encoder { encoded_h.encode_indexed_dynamic(index); } } else { - encoded_h.encode_literal_with_name_ref(static_table, index, &value); + encoded_h.encode_literal_with_name_ref(static_table, index, value); } if !static_table && ref_entries.insert(index) { self.table.add_ref(index); @@ -447,7 +447,7 @@ impl Encoder { // Insert using an InsertWithNameLiteral instruction. This entry name does not match // any name in the tables therefore we cannot use any other // instruction. - if let Ok(index) = self.send_and_insert(conn, &name, &value) { + if let Ok(index) = self.send_and_insert(conn, &name, value) { encoded_h.encode_indexed_dynamic(index); ref_entries.insert(index); self.table.add_ref(index); @@ -466,10 +466,10 @@ impl Encoder { // As soon as one of the instructions cannot be written or the table is full, do // not try again. encoder_blocked = true; - encoded_h.encode_literal_with_name_literal(&name, &value); + encoded_h.encode_literal_with_name_literal(&name, value); } } else { - encoded_h.encode_literal_with_name_literal(&name, &value); + encoded_h.encode_literal_with_name_literal(&name, value); } } diff --git a/third_party/rust/neqo-qpack/src/header_block.rs b/third_party/rust/neqo-qpack/src/header_block.rs @@ -313,10 +313,7 @@ impl<'a> HeaderDecoder<'a> { .read_prefixed_int(HEADER_FIELD_INDEX_STATIC.len())?; qtrace!("[{self}] decoder static indexed {index}"); let entry = HeaderTable::get_static(index)?; - Ok(Header::new( - parse_utf8(entry.name())?, - parse_utf8(entry.value())?, - )) + Ok(Header::new(parse_utf8(entry.name())?, entry.value())) } fn read_indexed_dynamic(&mut self, table: &HeaderTable) -> Res<Header> { @@ -325,10 +322,7 @@ impl<'a> HeaderDecoder<'a> { .read_prefixed_int(HEADER_FIELD_INDEX_DYNAMIC.len())?; qtrace!("[{self}] decoder dynamic indexed {index}"); let entry = table.get_dynamic(index, self.base, false)?; - Ok(Header::new( - parse_utf8(entry.name())?, - parse_utf8(entry.value())?, - )) + Ok(Header::new(parse_utf8(entry.name())?, entry.value())) } fn read_indexed_dynamic_post(&mut self, table: &HeaderTable) -> Res<Header> { @@ -337,10 +331,7 @@ impl<'a> HeaderDecoder<'a> { .read_prefixed_int(HEADER_FIELD_INDEX_DYNAMIC_POST.len())?; qtrace!("[{self}] decode post-based {index}"); let entry = table.get_dynamic(index, self.base, true)?; - Ok(Header::new( - parse_utf8(entry.name())?, - parse_utf8(entry.value())?, - )) + Ok(Header::new(parse_utf8(entry.name())?, entry.value())) } fn read_literal_with_name_ref_static(&mut self) -> Res<Header> { @@ -385,10 +376,13 @@ impl<'a> HeaderDecoder<'a> { fn read_literal_with_name_literal(&mut self) -> Res<Header> { qtrace!("[{self}] decode literal with name literal"); - let name = self + let name_bytes = self .buf .read_literal_from_buffer(HEADER_FIELD_LITERAL_NAME_LITERAL.len())?; + // Header names must be valid UTF-8 + let name = parse_utf8(&name_bytes)?.to_string(); + Ok(Header::new(name, self.buf.read_literal_from_buffer(0)?)) } } @@ -674,7 +668,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -702,7 +696,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -720,7 +714,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -737,7 +731,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -755,7 +749,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -773,7 +767,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -791,7 +785,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } @@ -808,7 +802,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), LITERAL_VALUE); - assert_eq!(result[0].value(), LITERAL_VALUE); + assert_eq!(result[0].value(), LITERAL_VALUE.as_bytes()); } else { panic!("No headers"); } @@ -819,7 +813,30 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), LITERAL_VALUE); - assert_eq!(result[0].value(), LITERAL_VALUE); + assert_eq!(result[0].value(), LITERAL_VALUE.as_bytes()); + } else { + panic!("No headers"); + } + } + + #[test] + fn decode_literal_non_utf8_value() { + // Test decoding a header with UTF-8 name but non-UTF8 value + // Based on LITERAL_LITERAL but with non-UTF8 value (0xE4 instead of "custom-key") + const LITERAL_NON_UTF8_VALUE: &[u8] = &[ + 0x0, 0x42, 0x27, 0x03, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, + 0x01, // value length = 1 + 0xE4, // non-UTF8 byte + ]; + + let table = HeaderTable::new(false); + let mut decoder_h = HeaderDecoder::new(LITERAL_NON_UTF8_VALUE); + if let HeaderDecoderResult::Headers(result) = + decoder_h.decode_header_block(&table, 1000, 0).unwrap() + { + assert_eq!(result.len(), 1); + assert_eq!(result[0].name(), "custom-key"); + assert_eq!(result[0].value(), &[0xE4u8]); } else { panic!("No headers"); } @@ -872,7 +889,7 @@ mod tests { { assert_eq!(result.len(), 1); assert_eq!(result[0].name(), *decoded1); - assert_eq!(result[0].value(), *decoded2); + assert_eq!(result[0].value(), decoded2.as_bytes()); } else { panic!("No headers"); } diff --git a/third_party/rust/neqo-qpack/src/huffman_decode_helper.rs b/third_party/rust/neqo-qpack/src/huffman_decode_helper.rs @@ -12,7 +12,7 @@ use crate::huffman_table::HUFFMAN_TABLE; static_assertions::const_assert!(HUFFMAN_TABLE.len() <= u16::MAX as usize); pub struct HuffmanDecoderNode { - pub next: [Option<Box<HuffmanDecoderNode>>; 2], + pub next: [Option<Box<Self>>; 2], pub value: Option<u16>, } diff --git a/third_party/rust/neqo-qpack/src/reader.rs b/third_party/rust/neqo-qpack/src/reader.rs @@ -119,7 +119,7 @@ impl<'a> ReceiverBufferWrapper<'a> { /// `ReceiverBufferWrapper` is only used for decoding header blocks. The header blocks are read /// entirely before a decoding starts, therefore any incomplete varint or literal because of /// reaching the end of a buffer will be treated as the `Error::Decompression` error. - pub fn read_literal_from_buffer(&mut self, prefix_len: u8) -> Res<String> { + pub fn read_literal_from_buffer(&mut self, prefix_len: u8) -> Res<Vec<u8>> { debug_assert!(prefix_len < 7); let first_byte = self.read_byte()?; @@ -130,9 +130,9 @@ impl<'a> ReceiverBufferWrapper<'a> { .try_into() .or(Err(Error::Decompression))?; if use_huffman { - Ok(parse_utf8(&huffman::decode(self.slice(length)?)?)?.to_string()) + huffman::decode(self.slice(length)?) } else { - Ok(parse_utf8(self.slice(length)?)?.to_string()) + Ok(self.slice(length)?.to_vec()) } } @@ -383,7 +383,7 @@ mod tests { use test_receiver::TestReceiver; use super::{ - parse_utf8, str, test_receiver, Error, IntReader, LiteralReader, ReadByte as _, + huffman, test_receiver, Error, IntReader, LiteralReader, ReadByte as _, ReceiverBufferWrapper, Res, }; @@ -475,45 +475,45 @@ mod tests { } } - const TEST_CASES_LITERAL: [(&[u8], u8, &str); 9] = [ + const TEST_CASES_LITERAL: [(&[u8], u8, &[u8]); 9] = [ // No Huffman ( &[ 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, ], 1, - "custom-key", + b"custom-key", ), ( &[ 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, ], 3, - "custom-key", + b"custom-key", ), ( &[ 0xea, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, ], 3, - "custom-key", + b"custom-key", ), ( &[ 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, ], 1, - "custom-header", + b"custom-header", ), // With Huffman - (&[0x15, 0xae, 0xc3, 0x77, 0x1a, 0x4b], 3, "private"), + (&[0x15, 0xae, 0xc3, 0x77, 0x1a, 0x4b], 3, b"private"), ( &[ 0x56, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, ], 1, - "Mon, 21 Oct 2013 20:13:21 GMT", + b"Mon, 21 Oct 2013 20:13:21 GMT", ), ( &[ @@ -521,7 +521,7 @@ mod tests { 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, ], 4, - "Mon, 21 Oct 2013 20:13:21 GMT", + b"Mon, 21 Oct 2013 20:13:21 GMT", ), ( &[ @@ -529,7 +529,7 @@ mod tests { 0x82, 0xae, 0x43, 0xd3, ], 1, - "https://www.example.com", + b"https://www.example.com", ), ( &[ @@ -537,7 +537,7 @@ mod tests { 0x82, 0xae, 0x43, 0xd3, ], 0, - "https://www.example.com", + b"https://www.example.com", ), ]; @@ -547,10 +547,7 @@ mod tests { let mut reader = LiteralReader::new_with_first_byte(buf[0], *prefix_len); let mut test_receiver: TestReceiver = TestReceiver::default(); test_receiver.write(&buf[1..]); - assert_eq!( - parse_utf8(&reader.read(&mut test_receiver).unwrap()).unwrap(), - *value - ); + assert_eq!(reader.read(&mut test_receiver).unwrap().as_slice(), *value); } } @@ -600,4 +597,43 @@ mod tests { Err(Error::Decompression) ); } + + #[test] + fn read_non_utf8_huffman_literal() { + // Test non-UTF8 data with Huffman encoding + // 0xE4 is 'ä' in ISO-8859-1 (extended ASCII), which is invalid UTF-8 + let non_utf8_data = &[0xE4u8]; + let encoded = huffman::encode(non_utf8_data); + + // Build a QPACK literal: [huffman_bit | length][data] + // For prefix_len=3, the huffman bit is at position (0x80 >> 3) = 0x10 + let mut buf = Vec::new(); + #[expect(clippy::cast_possible_truncation, reason = "Test data is small")] + let len = encoded.len() as u8; + buf.push(0x10 | len); // Huffman bit set + length + buf.extend_from_slice(&encoded); + + let mut buffer = ReceiverBufferWrapper::new(&buf); + let result = buffer.read_literal_from_buffer(3).unwrap(); + assert_eq!(result, non_utf8_data); + } + + #[test] + fn read_non_utf8_plain_literal() { + // Test non-UTF8 data without Huffman encoding + // 0xFF, 0xFE are invalid UTF-8 sequences + let non_utf8_data = &[0xFFu8, 0xFEu8]; + + // Build a QPACK literal without Huffman: [length][data] + // For prefix_len=3, no huffman bit + let mut buf = Vec::new(); + #[expect(clippy::cast_possible_truncation, reason = "Test data is small")] + let len = non_utf8_data.len() as u8; + buf.push(len); // No Huffman bit, just length + buf.extend_from_slice(non_utf8_data); + + let mut buffer = ReceiverBufferWrapper::new(&buf); + let result = buffer.read_literal_from_buffer(3).unwrap(); + assert_eq!(result, non_utf8_data); + } } diff --git a/third_party/rust/neqo-transport/.cargo-checksum.json b/third_party/rust/neqo-transport/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"5c87c9bdbc4d2b5cd11644ba50eb0482ec34a76cbf2f544514b684855bc136d6","benches/min_bandwidth.rs":"73f9a222b9eb8edac291d1957df61a516bb9491fa6bd81a4cb5546184b76b94d","benches/range_tracker.rs":"37a921fa4aa6375ff9cb785adde24df537a18ab624afda6478bbf848c58b0e8d","benches/rx_stream_orderer.rs":"a40e50b0f4e28465b1b65ebcf95570a116c2635eea790d4196fb73f1b3977e63","benches/sent_packets.rs":"aa9f67da4cd54bca1f69030ed3359ee0c77b31e07a586d59ece933d11fcc88b3","benches/transfer.rs":"de2c47025256db1d9af3622b52a5232f7c36683605b273db16089d3bd9c28aac","build.rs":"78ec79c93bf13c3a40ceef8bba1ea2eada61c8f2dfc15ea7bf117958d367949c","src/ackrate.rs":"08f7f4777b7ac1ec85afb4c8cd1cab99a39bd2cb023628193e99325c8c0e34e2","src/addr_valid.rs":"1290915bae733962952f8f92300593587dc4372fce3520dedfe05e51e533275a","src/cc/classic_cc.rs":"f34b06504e030225b3c024d4d7c149cd643950326cd7b28ce8a44c53d4a9b961","src/cc/cubic.rs":"d33721578a236c3cad13410b1baac33439f1bab70e826211341ecb19e2976764","src/cc/mod.rs":"af7b073e05769c2dee7a7f1346c4287ed498c9fb276f03eed49a151b86aed20d","src/cc/new_reno.rs":"278e6caa6164e69a30e84f1eb32358d5f4fac05a05addfa129cc6006c1e856c9","src/cc/tests/cubic.rs":"e6121b610a9e18463ae7d38a933db2f1151ce873586fcf4d67ad985e0f2a41ff","src/cc/tests/mod.rs":"017bf402a9a8c71b5c43343677635644babb57a849d81d0affc328b4b4b9cebb","src/cc/tests/new_reno.rs":"34899c87b6175fe9bf8d0f3f9bb1164a0fbab1f9435840f07f56e21ca467762f","src/cid.rs":"caf814af49d25891a9b79e1f97c741fac41d4062c3e923e4b0e0d5ed0d471077","src/connection/idle.rs":"7fffb026530f17604ac41b28c71cf3b3554e6b1958db96436478d9332b65217c","src/connection/mod.rs":"23e9d68604da238427f0b19d0058a00021e6661253d089a17e0077765d565249","src/connection/params.rs":"c15fbdf4ad42487d6a93285529f6fb74d854d338df236ae75ca9b5e4a27f887c","src/connection/state.rs":"76d1bbe9b6230c88ff948b631feefd9b1e556d57fc5e3eee4f2e7e4151278c20","src/connection/test_internal.rs":"25814d143b745c50c67708cd82aeecbc1e6f123e067b69c5021b9ba653c0c6c2","src/connection/tests/ackrate.rs":"4b7325b5798817d971c14f76ca79c1d7f677aee8e579c97623f49eb87727bd6f","src/connection/tests/cc.rs":"7b113bb1713d440a03bf34bcd42b146dd201c2110140650ee1d98b136d7cec48","src/connection/tests/close.rs":"4c266cf15fe2ad2ae3a7ef77e44586ec5cc6c3c79239184fcf32dd67146ec810","src/connection/tests/datagram.rs":"38e76aaf790d9091e0c50fb875458e8702cae904619c37cfff8fda5e4fb04448","src/connection/tests/ecn.rs":"df46cc993abb99d8d7a2187bb80bc5528763929ee1a888db5a0f53069c9d60bd","src/connection/tests/handshake.rs":"aa55e7bfb03fad6b813709a2874d5c92b2d4e5797f25fb671d7d4e898837716e","src/connection/tests/idle.rs":"903c3b45b89df9bd7cefa216e58df09f24e9f6a5b42b6de2762a5b737f7c7632","src/connection/tests/keys.rs":"c3f93b5410d1b402901d8d18bfbf673fd4ca473136ac2777ede61bdd94e7e29c","src/connection/tests/migration.rs":"c8abfd6577662b7b549b004173fb3d2ab2499a1974f576e92c5e124c31ecf754","src/connection/tests/mod.rs":"5613c937845552291d70811120135ea3c57534fcac0d0ca4f3c0402fd0256c84","src/connection/tests/null.rs":"d39d34c895c40ea88bcc137cba43c34386ef9759c6f66f3487ffd41a5099feb8","src/connection/tests/pmtud.rs":"39e9aa38c4d5ecfe8d3040ce268ce33fa63ad87a774f7cad45f5dcc6e43aaf82","src/connection/tests/priority.rs":"00fb90881299fb93b9ad628840585852422676a6db0dbeee348a51ea3029456d","src/connection/tests/recovery.rs":"f3d148f48c7d67982fdf6a1f30428efbc39859d2d51c9d0698fbb0e4d350013c","src/connection/tests/resumption.rs":"93ec8439d8cc05df77e21566b06c2948ab7ea4e8c7518228de328bc19a88815b","src/connection/tests/stream.rs":"771296f838e06337bf7e7b29b14fe68c99e31b21dc028da75b2f337c576158d4","src/connection/tests/vn.rs":"a2e69ad687ebc433e849c6dbb125eec25e5ac2a4f3945af94d9ef04034d2b1fb","src/connection/tests/zerortt.rs":"94a5a705283c31f50f68a74c49d4bba4ba2a51d8122d9e50a14a831a902f8578","src/crypto.rs":"9cb60f094f3c8051e1b0048648e19cda3e642592bc813716a55ecfb8216a3d2f","src/ecn.rs":"66411888a79d3fbe7445418cafcc1ad7c1ca2a466341fb8de6240ff2cef25a77","src/events.rs":"17cff113f464cf44d49a6a1f958a6d709252657ac6d6c0bad73ef1fdfc2e061b","src/fc.rs":"fc37c8cb9cf35657810f104b7803b7951633e8d97bc5fbb88749229abac978fd","src/frame.rs":"caad40490f7f12e4b6eed911a7525468bdda74af504deb3c1f6a1414dae1c18b","src/lib.rs":"51b26e65e15e0f1e25db6e2761f8dfc7b0d0100d6b8b0fbf62ea610144abc9a5","src/pace.rs":"e6be7801d2e4ee0c4883716564a3717a3bf14e9782c9fc1470d78ed78c50ead7","src/packet/metadata.rs":"7a3f520af2932e1ba26197df37bcc706293600cf94c7b559a5192a6f05f6b728","src/packet/mod.rs":"9f023d6611caab33664bd02f580d839dcf4222e61c6d73ce8c4790a01345f6cc","src/packet/retry.rs":"df1c7874ff3d3c478ca95a6c7adcb9b1d988894bf6467cee9afe9607ef0a7a20","src/path.rs":"c9c0534dbf8898390a49d5068abbee8b2395fa95173023ebba1c9e98735ec3fe","src/pmtud.rs":"ecbcad8aa2262672271612fa984c183ebbd5a8b859a1fd23fc377656a4eb33b0","src/qlog.rs":"e5a0a93f19959b235dab7f741b088d4a37aabde62b18d54493eac1418bae6321","src/quic_datagrams.rs":"fd2a665f1febc211571cb4505729f1639eb4b76e8f1a0588749c16f2b646fd5e","src/recovery/mod.rs":"25fcbd567d3a8f77cfbcb8ecbcc26a84d2cf4b8ccf85321c8b17a692dad1d1c8","src/recovery/sent.rs":"c84643c5929c3a8db954500fc859054cfc19770c851354152ddfce292d0be28c","src/recovery/token.rs":"667c827df3d321ee61689e6cda78d9c27656525ab2978282754a78b2370a9a2f","src/recv_stream.rs":"019664ee7b77c679a8ce6fb3743b86fb0da66eb9ddd527baba6aaf967c423722","src/rtt.rs":"ab74c7618f1c200c095bb6f9feda5e8e164738de9196561673d0ea52e7117c90","src/saved.rs":"55c0d1c415079138674974a46d8c6ecbcda49ce4070bf0920286b1401c161078","src/send_stream.rs":"941616ce02608d9a9278f07eda8231a9dda3c9e8b2a2c9e6c6c7bcd1531e3f9a","src/sender.rs":"d49b4d014e27b7120e0c3edc3ce43edbc25acea8394cd7cf15e460cf25cde225","src/server.rs":"bda1165f157cfdc4c7b9a02f8e2c32775cdb6a8b2962ee2524ae242869e378e6","src/sni.rs":"34361fd7a19fa7c8b33519ba1bdefc32a2cadec0b9cbcf1073a2e75f0c1730ae","src/stats.rs":"1e7a212af18c86093e6e25a76c806da701a6bb5c42c61d67b0af8a3ddef426da","src/stream_id.rs":"a6a44d6f02967feeed3250dadc658861f20e00373a0f239685776b2fa72a59e4","src/streams.rs":"17bea7b2f899e096f7a72aeafb74349f27cebc655ff6d304226f1006e90fe024","src/tparams.rs":"0c401dc2d825150b804690d64b2b0e75de22026f876af140a6dd3abf5c88f896","src/tracking.rs":"f81786cf0cdbc84e5e4c196e0e575a1a465928160a0c9c0ecd4896213eefbe55","src/version.rs":"76b8d3ec0464a749849280756b5acd243543e8cb43185936802ba552a207aeca","tests/common/mod.rs":"d1f96c024543cab68d09e2847990ffdf83568835c6ac740bf9cb765e7a661e1d","tests/conn_vectors.rs":"0e4a1b92c02b527842c127b789e70f6c4372c2b61b1a59a8e695f744ce155e2a","tests/connection.rs":"93c1982a6f049f735634ba5cfee6677f919a5f59694b6375e118efb20a69c09f","tests/network.rs":"2173d8347543d457a236817d219d2865315dbd005e1d19c2277e3a34d5cca759","tests/retry.rs":"0343ac640ea785655678f8d66ef5ce7c95b8dc584065ae1d606293a0860cb92b","tests/server.rs":"2dc921b4973d6c7875cd52874857526873d4cbcf5ebc2b2a60debcde7791bf4c","tests/sni.rs":"5cf5c6ec0601712596fdfd589eb346af599e45c80849adf31879ba4ba3640f38","tests/stats.rs":"af8c1da46e984b55b172118aff4ad33be2375443f405e297d40981e65eb4d0cf"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"9628136184d4e3cba8b0f01201bb9528aed55813cb03ef3665ce7548ab189a08","benches/min_bandwidth.rs":"73f9a222b9eb8edac291d1957df61a516bb9491fa6bd81a4cb5546184b76b94d","benches/range_tracker.rs":"546c4701424a8f286a459af10d93bf591b3912f1273125272b32485a27e5631d","benches/rx_stream_orderer.rs":"23c4646e91c4e20d27a81544c73a83dc02b2362bab870136e39d52a9ddced01b","benches/sent_packets.rs":"1e6cde25a8047f5c63629ae2b80950ee8b08b19d8db216a871eee04a59049f5b","benches/transfer.rs":"c5b0a538a8f9ecaf623e85e0b78b3cc4d37ecdab46c142218f068a503e0c5360","build.rs":"78ec79c93bf13c3a40ceef8bba1ea2eada61c8f2dfc15ea7bf117958d367949c","src/ackrate.rs":"08f7f4777b7ac1ec85afb4c8cd1cab99a39bd2cb023628193e99325c8c0e34e2","src/addr_valid.rs":"0c39026a8e5a6b959442f0c904ed3933701ba76644dbcaab1c69eab6127e5f4b","src/cc/classic_cc.rs":"3bbdb65dd36e6c9a633a168489093ef315766f6b32e3b0a5cd61cdb1b0c9325b","src/cc/cubic.rs":"5969163642c2d7ac8593f8603fba93c80940e6eccfb2602f2912628e5c66d64c","src/cc/mod.rs":"af7b073e05769c2dee7a7f1346c4287ed498c9fb276f03eed49a151b86aed20d","src/cc/new_reno.rs":"278e6caa6164e69a30e84f1eb32358d5f4fac05a05addfa129cc6006c1e856c9","src/cc/tests/cubic.rs":"3ba0c6ae3d094596d30da01c506faa2348111371192a4539eeb3bc108bc9726d","src/cc/tests/mod.rs":"017bf402a9a8c71b5c43343677635644babb57a849d81d0affc328b4b4b9cebb","src/cc/tests/new_reno.rs":"34899c87b6175fe9bf8d0f3f9bb1164a0fbab1f9435840f07f56e21ca467762f","src/cid.rs":"bb749a96f3615bcdf5441993d553d64a593229b2b3dc1c16b77787ffe93e057a","src/connection/idle.rs":"7fffb026530f17604ac41b28c71cf3b3554e6b1958db96436478d9332b65217c","src/connection/mod.rs":"de3b20f7160a95648c434be59d623381bc7cc54c8d9c93264bfdd548d111b332","src/connection/params.rs":"378b8c59cc79aa0c0743e2d4d8d7f463590dafd0bcd7536833b7ecbf33843c2d","src/connection/state.rs":"76d1bbe9b6230c88ff948b631feefd9b1e556d57fc5e3eee4f2e7e4151278c20","src/connection/test_internal.rs":"25814d143b745c50c67708cd82aeecbc1e6f123e067b69c5021b9ba653c0c6c2","src/connection/tests/ackrate.rs":"4b7325b5798817d971c14f76ca79c1d7f677aee8e579c97623f49eb87727bd6f","src/connection/tests/cc.rs":"7b113bb1713d440a03bf34bcd42b146dd201c2110140650ee1d98b136d7cec48","src/connection/tests/close.rs":"3844fb0fa626db47a1f914189923bd71737c5331e87cd0e293d10f556405c204","src/connection/tests/datagram.rs":"38a59287974ae3c99d1c1c26fb0d14de9a31fd9db87c0c86b95842edafdc2ab5","src/connection/tests/ecn.rs":"df46cc993abb99d8d7a2187bb80bc5528763929ee1a888db5a0f53069c9d60bd","src/connection/tests/handshake.rs":"2776540e3caa9f61f7ec7a570e386f8bd085fee34e35ec217e9aeab5c9a90ea4","src/connection/tests/idle.rs":"f8607ee48bd28486c83e497ce9882239cd8dcf9bdf3a5b1e475d5f50836e6223","src/connection/tests/keys.rs":"3481eb3b24fd034b2b51bfaa02ce58d1890bc10213d3f9d0effdd4d7fceb677c","src/connection/tests/migration.rs":"357f207a4b972f2b4fc85273d84739046f4bc698fc6902bd538248655599cda7","src/connection/tests/mod.rs":"5613c937845552291d70811120135ea3c57534fcac0d0ca4f3c0402fd0256c84","src/connection/tests/null.rs":"d39d34c895c40ea88bcc137cba43c34386ef9759c6f66f3487ffd41a5099feb8","src/connection/tests/pmtud.rs":"39e9aa38c4d5ecfe8d3040ce268ce33fa63ad87a774f7cad45f5dcc6e43aaf82","src/connection/tests/priority.rs":"00fb90881299fb93b9ad628840585852422676a6db0dbeee348a51ea3029456d","src/connection/tests/recovery.rs":"8ad7f52ca28c341bda818fde3bc4cbad69b3a6c68ea9a03870deb9c2f12696c2","src/connection/tests/resumption.rs":"93ec8439d8cc05df77e21566b06c2948ab7ea4e8c7518228de328bc19a88815b","src/connection/tests/stream.rs":"772faea8f971d7b091345ccca3bc7070202f288c3acf6a8e98bfc8c0a5bce5e4","src/connection/tests/vn.rs":"94709b10922b31d518bb7afe8d10489ee5a6e52a51c2fe46b54dac2382997c59","src/connection/tests/zerortt.rs":"94a5a705283c31f50f68a74c49d4bba4ba2a51d8122d9e50a14a831a902f8578","src/crypto.rs":"9cb60f094f3c8051e1b0048648e19cda3e642592bc813716a55ecfb8216a3d2f","src/ecn.rs":"66411888a79d3fbe7445418cafcc1ad7c1ca2a466341fb8de6240ff2cef25a77","src/events.rs":"0548408fd72c1d6c2458c9a682b6d63e2ce6dc699bce6fd7b72818b04bb9d61c","src/fc.rs":"2c9dddc4dd50ce941a11d0a7d9ee528d087694bfbdd002e589bf17666c1d91a2","src/frame.rs":"1db679a2a10fdd13b2a884374505ddeafc8219b49c2e16fcdd5d2c41b6e25864","src/lib.rs":"0c3c3610343593f10d0c75e75a01fcf9844c0a082d5a6071150358594cb91743","src/pace.rs":"bcdae5ecb11e844caa5b84c910f1ca54ed6c161758972989e7b80f2ea5ac4b0f","src/packet/metadata.rs":"7a3f520af2932e1ba26197df37bcc706293600cf94c7b559a5192a6f05f6b728","src/packet/mod.rs":"c8191689d3672c5ea19d8e39df197ce6671b62030e0394e539da2e016ba477dc","src/packet/retry.rs":"df1c7874ff3d3c478ca95a6c7adcb9b1d988894bf6467cee9afe9607ef0a7a20","src/path.rs":"d8d83c4b7c8d6f8755c814044a1b6c4746b4db06782e5f8f9de6c012909c4fb8","src/pmtud.rs":"ecbcad8aa2262672271612fa984c183ebbd5a8b859a1fd23fc377656a4eb33b0","src/qlog.rs":"e5a0a93f19959b235dab7f741b088d4a37aabde62b18d54493eac1418bae6321","src/quic_datagrams.rs":"e57b0bbcf1e9ca39b961c78e59d461e007e8083e026ee20f4694f0a708c3e44d","src/recovery/mod.rs":"623f29a103c911ef78a6895b1d57b3c559b7c52f2919cb3a66211824a2bc459c","src/recovery/sent.rs":"c84643c5929c3a8db954500fc859054cfc19770c851354152ddfce292d0be28c","src/recovery/token.rs":"b8866bd2605e59d0c0eaa767275f972563c2fe58804e210b1bf78445c114f05b","src/recv_stream.rs":"40802b5ed4bcf301626a5ba447b05e61d0a222390d97448ed580c09a1a86b967","src/rtt.rs":"ab74c7618f1c200c095bb6f9feda5e8e164738de9196561673d0ea52e7117c90","src/saved.rs":"55c0d1c415079138674974a46d8c6ecbcda49ce4070bf0920286b1401c161078","src/send_stream.rs":"3a1323c7dec70cd11b0f6ca0466d7434ff97891ce891a6c4030d934b996dbc0a","src/sender.rs":"d49b4d014e27b7120e0c3edc3ce43edbc25acea8394cd7cf15e460cf25cde225","src/server.rs":"bda1165f157cfdc4c7b9a02f8e2c32775cdb6a8b2962ee2524ae242869e378e6","src/sni.rs":"34361fd7a19fa7c8b33519ba1bdefc32a2cadec0b9cbcf1073a2e75f0c1730ae","src/stateless_reset.rs":"626e3c90682418d6390b18b0d6823171aa117d994f9762b7350eefd81a9ab4d8","src/stats.rs":"1e7a212af18c86093e6e25a76c806da701a6bb5c42c61d67b0af8a3ddef426da","src/stream_id.rs":"a6a44d6f02967feeed3250dadc658861f20e00373a0f239685776b2fa72a59e4","src/streams.rs":"b0727b9ba66b1064a6cf1c13bdae49e0b153728216f56eafbcbb96dfe7b9d012","src/tparams.rs":"aea84772ecee409beb39782a46f872bad8b735aaa95eaa423de02b36b10f7a6e","src/tracking.rs":"a9a05d4e34619dc69f405fe1dd5e8155f09b3838a1cdd70a2821a3374ca194fa","src/version.rs":"780ae2ce70b8424d9a2a2cdd3cc7b8b040f4588ecf7f1aaf091ea80b8c28a54d","tests/common/mod.rs":"d1f96c024543cab68d09e2847990ffdf83568835c6ac740bf9cb765e7a661e1d","tests/conn_vectors.rs":"0e4a1b92c02b527842c127b789e70f6c4372c2b61b1a59a8e695f744ce155e2a","tests/connection.rs":"93c1982a6f049f735634ba5cfee6677f919a5f59694b6375e118efb20a69c09f","tests/network.rs":"2173d8347543d457a236817d219d2865315dbd005e1d19c2277e3a34d5cca759","tests/retry.rs":"81c9e6979cbd62c038bb6145e4c8b2e2cb7050d382c935f8a8a53277600f7cac","tests/server.rs":"4fd24a308ceb5b0469a3fb147a6d8a3db9dbdb1c8c8a16d0b8f880d7ecd92f4b","tests/sni.rs":"5cf5c6ec0601712596fdfd589eb346af599e45c80849adf31879ba4ba3640f38","tests/stats.rs":"af8c1da46e984b55b172118aff4ad33be2375443f405e297d40981e65eb4d0cf"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-transport/Cargo.toml b/third_party/rust/neqo-transport/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-transport" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -174,9 +174,9 @@ version = "2.0.12" default-features = false [dev-dependencies.criterion] -version = "0.6" -features = ["cargo_bench_support"] +version = "4" default-features = false +package = "codspeed-criterion-compat" [dev-dependencies.neqo-transport] path = "." diff --git a/third_party/rust/neqo-transport/benches/range_tracker.rs b/third_party/rust/neqo-transport/benches/range_tracker.rs @@ -4,6 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] + use std::hint::black_box; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs b/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs @@ -4,6 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] + use std::hint::black_box; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/third_party/rust/neqo-transport/benches/sent_packets.rs b/third_party/rust/neqo-transport/benches/sent_packets.rs @@ -4,6 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] + use std::{hint::black_box, time::Instant}; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/third_party/rust/neqo-transport/benches/transfer.rs b/third_party/rust/neqo-transport/benches/transfer.rs @@ -4,6 +4,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::significant_drop_tightening, + reason = "Inherent in codspeed criterion_group! macro." +)] + use std::{hint::black_box, time::Duration}; use criterion::{criterion_group, criterion_main, BatchSize::SmallInput, Criterion}; diff --git a/third_party/rust/neqo-transport/src/addr_valid.rs b/third_party/rust/neqo-transport/src/addr_valid.rs @@ -85,11 +85,11 @@ impl AddressValidation { match peer_address.ip() { IpAddr::V4(a) => { aad.encode_byte(4); - aad.encode(&a.octets()); + aad.encode(a.octets()); } IpAddr::V6(a) => { aad.encode_byte(6); - aad.encode(&a.octets()); + aad.encode(a.octets()); } } if retry { diff --git a/third_party/rust/neqo-transport/src/cc/classic_cc.rs b/third_party/rust/neqo-transport/src/cc/classic_cc.rs @@ -600,7 +600,7 @@ mod tests { use crate::{ cc::{ classic_cc::State, - cubic::{Cubic, CUBIC_BETA_USIZE_DIVIDEND, CUBIC_BETA_USIZE_DIVISOR}, + cubic::Cubic, new_reno::NewReno, tests::{IP_ADDR, MTU, RTT}, CongestionControl, CongestionControlAlgorithm, CWND_INITIAL_PKTS, @@ -689,7 +689,7 @@ mod tests { let cwnd_initial = cc.cwnd_initial(); persistent_congestion_by_algorithm( cc, - cwnd_initial * CUBIC_BETA_USIZE_DIVIDEND / CUBIC_BETA_USIZE_DIVISOR, + cwnd_initial * Cubic::BETA_USIZE_DIVIDEND / Cubic::BETA_USIZE_DIVISOR, lost_packets, persistent_expected, ); diff --git a/third_party/rust/neqo-transport/src/cc/cubic.rs b/third_party/rust/neqo-transport/src/cc/cubic.rs @@ -15,69 +15,6 @@ use neqo_common::qtrace; use crate::cc::classic_cc::WindowAdjustment; -/// > Constant that determines the aggressiveness of CUBIC in competing with other congestion -/// > control algorithms in high-BDP networks. -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> -/// -/// See section 5.1 of RFC9438 for discussion on how to set the concrete value: -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-fairness-to-reno> -pub const CUBIC_C: f64 = 0.4; -/// > CUBIC additive increase factor used in the Reno-friendly region \[to achieve approximately the -/// > same average congestion window size as Reno\]. -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> -/// -/// > The model used to calculate CUBIC_ALPHA is not absolutely precise, -/// > but analysis and simulation \[...\], as well as over a decade of experience with -/// > CUBIC in the public Internet, show that this approach produces acceptable -/// > levels of rate fairness between CUBIC and Reno flows. -/// -/// Formula: -/// -/// `CUBIC_ALPHA = 3.0 * (1.0 - CUBIC_BETA) / (1.0 + CUBIC_BETA)` -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-reno-friendly-region> -pub const CUBIC_ALPHA: f64 = 3.0 * (1.0 - 0.7) / (1.0 + 0.7); // with CUBIC_BETA = 0.7 -/// `CUBIC_BETA` = 0.7; -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> -/// -/// > To balance between the scalability and convergence speed, CUBIC sets the multiplicative window -/// > decrease factor to 0.7 while Standard TCP uses 0.5. While this improves the scalability of -/// > CUBIC, a side effect of this decision is slower convergence, especially under low statistical -/// > multiplexing environments. -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-principle-4-for-the-cubic-d> -/// -/// For implementation reasons neqo uses a dividend and divisor approach with `usize` typing to -/// construct `CUBIC_BETA = 0.7`. -pub const CUBIC_BETA_USIZE_DIVIDEND: usize = 7; -/// > CUBIC multiplicative decrease factor -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> -/// -/// > To balance between the scalability and convergence speed, CUBIC sets the multiplicative window -/// > decrease factor to 0.7 while Standard TCP uses 0.5. While this improves the scalability of -/// > CUBIC, a side effect of this decision is slower convergence, especially under low statistical -/// > multiplexing environments. -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-principle-4-for-the-cubic-d> -/// -/// For implementation reasons neqo uses a dividend and divisor approach with `usize` typing to -/// construct `CUBIC_BETA = 0.7` -pub const CUBIC_BETA_USIZE_DIVISOR: usize = 10; - -/// This is the factor that is used by fast convergence to further reduce the next `W_max` when a -/// congestion event occurs while `cwnd < W_max`. This speeds up the bandwidth release for when a -/// new flow joins the network. -/// -/// The calculation assumes `CUBIC_BETA = 0.7`. -/// -/// <https://datatracker.ietf.org/doc/html/rfc9438#name-fast-convergence> -pub const CUBIC_FAST_CONVERGENCE_FACTOR: f64 = (1.0 + 0.7) / 2.0; - /// Convert an integer congestion window value into a floating point value. /// This has the effect of reducing larger values to `1<<53`. /// If you have a congestion window that large, something is probably wrong. @@ -157,6 +94,75 @@ impl Display for Cubic { } impl Cubic { + /// > Constant that determines the aggressiveness of CUBIC in competing with other congestion + /// > control algorithms in high-BDP networks. + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> + /// + /// See section 5.1 of RFC9438 for discussion on how to set the concrete value: + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-fairness-to-reno> + pub const C: f64 = 0.4; + + /// > CUBIC additive increase factor used in the Reno-friendly region \[to achieve approximately + /// > the same average congestion window size as Reno\]. + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> + /// + /// > The model used to calculate CUBIC_ALPHA is not absolutely precise, + /// > but analysis and simulation \[...\], as well as over a decade of experience with + /// > CUBIC in the public Internet, show that this approach produces acceptable + /// > levels of rate fairness between CUBIC and Reno flows. + /// + /// Formula: + /// + /// `ALPHA = 3.0 * (1.0 - CUBIC_BETA) / (1.0 + CUBIC_BETA)` + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-reno-friendly-region> + pub const ALPHA: f64 = 3.0 * (1.0 - 0.7) / (1.0 + 0.7); // with CUBIC_BETA = 0.7 + + /// `CUBIC_BETA` = 0.7; + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> + /// + /// > To balance between the scalability and convergence speed, CUBIC sets the multiplicative + /// > window + /// > decrease factor to 0.7 while Standard TCP uses 0.5. While this improves the scalability of + /// > CUBIC, a side effect of this decision is slower convergence, especially under low + /// > statistical multiplexing environments. + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-principle-4-for-the-cubic-d> + /// + /// For implementation reasons neqo uses a dividend and divisor approach with `usize` typing to + /// construct `CUBIC_BETA = 0.7`. + pub const BETA_USIZE_DIVIDEND: usize = 7; + + /// > CUBIC multiplicative decrease factor + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-constants-of-interest> + /// + /// > To balance between the scalability and convergence speed, CUBIC sets the multiplicative + /// > window + /// > decrease factor to 0.7 while Standard TCP uses 0.5. While this improves the scalability of + /// > CUBIC, a side effect of this decision is slower convergence, especially under low + /// > statistical + /// > multiplexing environments. + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-principle-4-for-the-cubic-d> + /// + /// For implementation reasons neqo uses a dividend and divisor approach with `usize` typing to + /// construct `CUBIC_BETA = 0.7` + pub const BETA_USIZE_DIVISOR: usize = 10; + + /// This is the factor that is used by fast convergence to further reduce the next `W_max` when + /// a congestion event occurs while `cwnd < W_max`. This speeds up the bandwidth release for + /// when a new flow joins the network. + /// + /// The calculation assumes `CUBIC_BETA = 0.7`. + /// + /// <https://datatracker.ietf.org/doc/html/rfc9438#name-fast-convergence> + pub const FAST_CONVERGENCE_FACTOR: f64 = (1.0 + 0.7) / 2.0; + /// Original equation is: /// /// `k = cubic_root((w_max - cwnd_epoch)/C)` @@ -171,7 +177,7 @@ impl Cubic { /// /// `k = cubic_root((w_max - cwnd_epoch)/SMSS/C)` fn calc_k(&self, cwnd_epoch: f64, max_datagram_size: f64) -> f64 { - ((self.w_max - cwnd_epoch) / max_datagram_size / CUBIC_C).cbrt() + ((self.w_max - cwnd_epoch) / max_datagram_size / Self::C).cbrt() } /// `w_cubic(t) = C*(t-K)^3 + w_max` @@ -185,7 +191,7 @@ impl Cubic { /// /// `w_cubic(t) = (C*(t-K)^3) * SMSS + w_max` fn w_cubic(&self, t: f64, max_datagram_size: f64) -> f64 { - (CUBIC_C * (t - self.k).powi(3)).mul_add(max_datagram_size, self.w_max) + (Self::C * (t - self.k).powi(3)).mul_add(max_datagram_size, self.w_max) } /// Sets `w_est`, `k`, `t_epoch` and `reno_acked_bytes` at the start of a new epoch @@ -301,14 +307,14 @@ impl WindowAdjustment for Cubic { // <https://datatracker.ietf.org/doc/html/rfc9438#section-4.3-9> // We first calculate the increase in segments and floor it to only include whole segments. - let increase = (CUBIC_ALPHA * self.reno_acked_bytes / curr_cwnd).floor(); + let increase = (Self::ALPHA * self.reno_acked_bytes / curr_cwnd).floor(); // Only apply the increase if it is at least by one segment. if increase > 0.0 { self.w_est += increase * max_datagram_size; // Because we floored the increase to whole segments we cannot just zero // `reno_acked_bytes` but have to calculate the actual bytes used. - let acked_bytes_used = increase * curr_cwnd / CUBIC_ALPHA; + let acked_bytes_used = increase * curr_cwnd / Self::ALPHA; self.reno_acked_bytes -= acked_bytes_used; } @@ -398,7 +404,7 @@ impl WindowAdjustment for Cubic { // "Check cwnd + MAX_DATAGRAM_SIZE instead of cwnd because with cwnd in bytes, cwnd may be // slightly off." self.w_max = if curr_cwnd_f64 + convert_to_f64(max_datagram_size) < self.w_max { - curr_cwnd_f64 * CUBIC_FAST_CONVERGENCE_FACTOR + curr_cwnd_f64 * Self::FAST_CONVERGENCE_FACTOR } else { curr_cwnd_f64 }; @@ -406,8 +412,8 @@ impl WindowAdjustment for Cubic { // Reducing the congestion window and resetting time self.t_epoch = None; ( - curr_cwnd * CUBIC_BETA_USIZE_DIVIDEND / CUBIC_BETA_USIZE_DIVISOR, - acked_bytes * CUBIC_BETA_USIZE_DIVIDEND / CUBIC_BETA_USIZE_DIVISOR, + curr_cwnd * Self::BETA_USIZE_DIVIDEND / Self::BETA_USIZE_DIVISOR, + acked_bytes * Self::BETA_USIZE_DIVIDEND / Self::BETA_USIZE_DIVISOR, ) } diff --git a/third_party/rust/neqo-transport/src/cc/tests/cubic.rs b/third_party/rust/neqo-transport/src/cc/tests/cubic.rs @@ -21,10 +21,7 @@ use super::{IP_ADDR, MTU, RTT}; use crate::{ cc::{ classic_cc::ClassicCongestionControl, - cubic::{ - convert_to_f64, Cubic, CUBIC_ALPHA, CUBIC_BETA_USIZE_DIVIDEND, - CUBIC_BETA_USIZE_DIVISOR, CUBIC_C, CUBIC_FAST_CONVERGENCE_FACTOR, - }, + cubic::{convert_to_f64, Cubic}, CongestionControl as _, }, packet, @@ -34,11 +31,11 @@ use crate::{ }; const fn cwnd_after_loss(cwnd: usize) -> usize { - cwnd * CUBIC_BETA_USIZE_DIVIDEND / CUBIC_BETA_USIZE_DIVISOR + cwnd * Cubic::BETA_USIZE_DIVIDEND / Cubic::BETA_USIZE_DIVISOR } const fn cwnd_after_loss_slow_start(cwnd: usize, mtu: usize) -> usize { - (cwnd + mtu) * CUBIC_BETA_USIZE_DIVIDEND / CUBIC_BETA_USIZE_DIVISOR + (cwnd + mtu) * Cubic::BETA_USIZE_DIVIDEND / Cubic::BETA_USIZE_DIVISOR } fn fill_cwnd(cc: &mut ClassicCongestionControl<Cubic>, mut next_pn: u64, now: Instant) -> u64 { @@ -85,7 +82,7 @@ fn packet_lost(cc: &mut ClassicCongestionControl<Cubic>, pn: u64) { fn expected_tcp_acks(cwnd_rtt_start: usize, mtu: usize) -> u64 { (f64::from(i32::try_from(cwnd_rtt_start).unwrap()) / f64::from(i32::try_from(mtu).unwrap()) - / CUBIC_ALPHA) + / Cubic::ALPHA) .round() as u64 } @@ -108,15 +105,15 @@ fn tcp_phase() { // in this phase cwnd is increase by CUBIC_ALPHA every RTT. We can look at it as // increase of MAX_DATAGRAM_SIZE every 1 / CUBIC_ALPHA RTTs. // The phase will end when cwnd calculated with cubic equation is equal to TCP estimate: - // CUBIC_C * (n * RTT / CUBIC_ALPHA)^3 * MAX_DATAGRAM_SIZE = n * MAX_DATAGRAM_SIZE - // from this n = sqrt(CUBIC_ALPHA^3/ (CUBIC_C * RTT^3)). - let num_tcp_increases = (CUBIC_ALPHA.powi(3) / (CUBIC_C * RTT.as_secs_f64().powi(3))) + // Cubic::C * (n * RTT / Cubic::ALPHA)^3 * MAX_DATAGRAM_SIZE = n * MAX_DATAGRAM_SIZE + // from this n = sqrt(Cubic::ALPHA^3/ (Cubic::C * RTT^3)). + let num_tcp_increases = (Cubic::ALPHA.powi(3) / (Cubic::C * RTT.as_secs_f64().powi(3))) .sqrt() .floor() as u64; for _ in 0..num_tcp_increases { let cwnd_rtt_start = cubic.cwnd(); - // Expected acks during a period of RTT / CUBIC_ALPHA. + // Expected acks during a period of RTT / Cubic::ALPHA. let acks = expected_tcp_acks(cwnd_rtt_start, cubic.max_datagram_size()); // The time between acks if they are ideally paced over a RTT. let time_increase = @@ -149,7 +146,7 @@ fn tcp_phase() { } // Make sure that the increase is not according to TCP equation, i.e., that it took - // less than RTT / CUBIC_ALPHA. + // less than RTT / Cubic::ALPHA. let expected_ack_tcp_increase = expected_tcp_acks(cwnd_rtt_start, cubic.max_datagram_size()); assert!(num_acks < expected_ack_tcp_increase); @@ -180,13 +177,13 @@ fn tcp_phase() { // The time needed to increase cwnd by MAX_DATAGRAM_SIZE using the cubic equation will be // calculated from: W_cubic(elapsed_time + t_to_increase) - W_cubic(elapsed_time) = - // MAX_DATAGRAM_SIZE => CUBIC_C * (elapsed_time + t_to_increase)^3 * MAX_DATAGRAM_SIZE + - // CWND_INITIAL - CUBIC_C * elapsed_time^3 * MAX_DATAGRAM_SIZE + CWND_INITIAL = - // MAX_DATAGRAM_SIZE => t_to_increase = cbrt((1 + CUBIC_C * elapsed_time^3) / CUBIC_C) - + // MAX_DATAGRAM_SIZE => Cubic::C * (elapsed_time + t_to_increase)^3 * MAX_DATAGRAM_SIZE + + // CWND_INITIAL - Cubic::C * elapsed_time^3 * MAX_DATAGRAM_SIZE + CWND_INITIAL = + // MAX_DATAGRAM_SIZE => t_to_increase = cbrt((1 + Cubic::C * elapsed_time^3) / Cubic::C) - // elapsed_time (t_to_increase is in seconds) // number of ack needed is t_to_increase / time_increase. let expected_ack_cubic_increase = - (((CUBIC_C.mul_add((elapsed_time).as_secs_f64().powi(3), 1.0) / CUBIC_C).cbrt() + (((Cubic::C.mul_add((elapsed_time).as_secs_f64().powi(3), 1.0) / Cubic::C).cbrt() - elapsed_time.as_secs_f64()) / time_increase.as_secs_f64()) .ceil() as u64; @@ -212,7 +209,7 @@ fn cubic_phase() { next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); let k = (cwnd_initial_f64.mul_add(10.0, -cwnd_initial_f64) - / CUBIC_C + / Cubic::C / convert_to_f64(cubic.max_datagram_size())) .cbrt(); let epoch_start = now; @@ -231,7 +228,7 @@ fn cubic_phase() { next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); } - let expected = (CUBIC_C * ((now - epoch_start).as_secs_f64() - k).powi(3)) + let expected = (Cubic::C * ((now - epoch_start).as_secs_f64() - k).powi(3)) .mul_add( convert_to_f64(cubic.max_datagram_size()), cwnd_initial_f64 * 10.0, @@ -335,7 +332,7 @@ fn congestion_event_congestion_avoidance_fast_convergence() { assert_within( cubic.cc_algorithm().w_max(), - cwnd_initial_f64 * CUBIC_FAST_CONVERGENCE_FACTOR, + cwnd_initial_f64 * Cubic::FAST_CONVERGENCE_FACTOR, f64::EPSILON, ); assert_eq!(cubic.cwnd(), cwnd_after_loss(cubic.cwnd_initial())); diff --git a/third_party/rust/neqo-transport/src/cid.rs b/third_party/rust/neqo-transport/src/cid.rs @@ -19,28 +19,24 @@ use neqo_common::{hex, hex_with_len, qdebug, qinfo, Buffer, Decoder, Encoder}; use neqo_crypto::{random, randomize}; use smallvec::{smallvec, SmallVec}; -use crate::{frame::FrameType, packet, recovery, stats::FrameStats, Error, Res}; - -pub const MAX_CONNECTION_ID_LEN: usize = 20; -pub const LOCAL_ACTIVE_CID_LIMIT: usize = 8; -pub const CONNECTION_ID_SEQNO_INITIAL: u64 = 0; -pub const CONNECTION_ID_SEQNO_PREFERRED: u64 = 1; -/// A special value. See `ConnectionIdManager::add_odcid`. -const CONNECTION_ID_SEQNO_ODCID: u64 = u64::MAX; -/// A special value. See `ConnectionIdEntry::empty_remote`. -const CONNECTION_ID_SEQNO_EMPTY: u64 = u64::MAX - 1; +use crate::{ + frame::FrameType, packet, recovery, stateless_reset::Token as Srt, stats::FrameStats, Error, + Res, +}; #[derive(Clone, Default, Eq, Hash, PartialEq)] pub struct ConnectionId { - cid: SmallVec<[u8; MAX_CONNECTION_ID_LEN]>, + cid: SmallVec<[u8; Self::MAX_LEN]>, } impl ConnectionId { + pub const MAX_LEN: usize = 20; + /// # Panics - /// When `len` is larger than `MAX_CONNECTION_ID_LEN`. + /// When `len` is larger than `ConnectionId::MAX_LEN`. #[must_use] pub fn generate(len: usize) -> Self { - assert!(matches!(len, 0..=MAX_CONNECTION_ID_LEN)); + assert!(matches!(len, 0..=Self::MAX_LEN)); let mut cid = smallvec![0; len]; randomize(&mut cid); Self { cid } @@ -73,8 +69,8 @@ impl Borrow<[u8]> for ConnectionId { } } -impl From<SmallVec<[u8; MAX_CONNECTION_ID_LEN]>> for ConnectionId { - fn from(cid: SmallVec<[u8; MAX_CONNECTION_ID_LEN]>) -> Self { +impl From<SmallVec<[u8; Self::MAX_LEN]>> for ConnectionId { + fn from(cid: SmallVec<[u8; Self::MAX_LEN]>) -> Self { Self { cid } } } @@ -248,42 +244,26 @@ pub struct ConnectionIdEntry<SRT: Clone + PartialEq> { srt: SRT, } -impl ConnectionIdEntry<[u8; 16]> { - /// Create a random stateless reset token so that it is hard to guess the correct - /// value and reset the connection. - pub fn random_srt() -> [u8; 16] { - random::<16>() - } - +impl ConnectionIdEntry<Srt> { /// Create the first entry, which won't have a stateless reset token. pub fn initial_remote(cid: ConnectionId) -> Self { - Self::new(CONNECTION_ID_SEQNO_INITIAL, cid, Self::random_srt()) + Self::new(Self::SEQNO_INITIAL, cid, Srt::random()) } /// Create an empty for when the peer chooses empty connection IDs. /// This uses a special sequence number just because it can. pub fn empty_remote() -> Self { Self::new( - CONNECTION_ID_SEQNO_EMPTY, + ConnectionIdManager::SEQNO_EMPTY, ConnectionId::from(&[]), - Self::random_srt(), + Srt::random(), ) } - fn token_equal(a: &[u8; 16], b: &[u8; 16]) -> bool { - // rustc might decide to optimize this and make this non-constant-time - // with respect to `t`, but it doesn't appear to currently. - let mut c = 0; - for (&a, &b) in a.iter().zip(b) { - c |= a ^ b; - } - c == 0 - } - /// Determine whether this is a valid stateless reset. - pub fn is_stateless_reset(&self, token: &[u8; 16]) -> bool { + pub fn is_stateless_reset(&self, token: &Srt) -> bool { // A sequence number of 2^62 or more has no corresponding stateless reset token. - (self.seqno < (1 << 62)) && Self::token_equal(&self.srt, token) + (self.seqno < (1 << 62)) && self.srt.eq(token) } /// Return true if the two contain any equal parts. @@ -303,7 +283,7 @@ impl ConnectionIdEntry<[u8; 16]> { builder: &mut packet::Builder<B>, stats: &mut FrameStats, ) -> bool { - let len = 1 + Encoder::varint_len(self.seqno) + 1 + 1 + self.cid.len() + 16; + let len = 1 + Encoder::varint_len(self.seqno) + 1 + 1 + self.cid.len() + Srt::LEN; if builder.remaining() < len { return false; } @@ -318,10 +298,14 @@ impl ConnectionIdEntry<[u8; 16]> { } pub fn is_empty(&self) -> bool { - self.seqno == CONNECTION_ID_SEQNO_EMPTY || self.cid.is_empty() + self.seqno == ConnectionIdManager::SEQNO_EMPTY || self.cid.is_empty() } } +impl<T: Clone + PartialEq> ConnectionIdEntry<T> { + const SEQNO_INITIAL: u64 = 0; +} + impl ConnectionIdEntry<()> { /// Create an initial entry. pub const fn initial_local(cid: ConnectionId) -> Self { @@ -336,13 +320,13 @@ impl<SRT: Clone + PartialEq> ConnectionIdEntry<SRT> { /// Update the stateless reset token. This panics if the sequence number is non-zero. pub fn set_stateless_reset_token(&mut self, srt: SRT) { - assert_eq!(self.seqno, CONNECTION_ID_SEQNO_INITIAL); + assert_eq!(self.seqno, Self::SEQNO_INITIAL); self.srt = srt; } /// Replace the connection ID. This panics if the sequence number is non-zero. pub fn update_cid(&mut self, cid: ConnectionId) { - assert_eq!(self.seqno, CONNECTION_ID_SEQNO_INITIAL); + assert_eq!(self.seqno, Self::SEQNO_INITIAL); self.cid = cid; } @@ -355,7 +339,7 @@ impl<SRT: Clone + PartialEq> ConnectionIdEntry<SRT> { } } -pub type RemoteConnectionIdEntry = ConnectionIdEntry<[u8; 16]>; +pub type RemoteConnectionIdEntry = ConnectionIdEntry<Srt>; /// A collection of connection IDs that are indexed by a sequence number. /// Used to store connection IDs that are provided by a peer. @@ -386,8 +370,8 @@ impl<SRT: Clone + PartialEq> ConnectionIdStore<SRT> { } } -impl ConnectionIdStore<[u8; 16]> { - pub fn add_remote(&mut self, entry: ConnectionIdEntry<[u8; 16]>) -> Res<()> { +impl ConnectionIdStore<Srt> { + pub fn add_remote(&mut self, entry: ConnectionIdEntry<Srt>) -> Res<()> { // It's OK if this perfectly matches an existing entry. if self.cids.iter().any(|c| c == &entry) { return Ok(()); @@ -452,15 +436,25 @@ pub struct ConnectionIdManager { /// the client. connection_ids: ConnectionIdStore<()>, /// The maximum number of connection IDs this will accept. This is at least 2 and won't - /// be more than `LOCAL_ACTIVE_CID_LIMIT`. + /// be more than `Self::ACTIVE_LIMIT`. limit: usize, /// The next sequence number that will be used for sending `NEW_CONNECTION_ID` frames. next_seqno: u64, /// Outstanding, but lost `NEW_CONNECTION_ID` frames will be stored here. - lost_new_connection_id: Vec<ConnectionIdEntry<[u8; 16]>>, + lost_new_connection_id: Vec<ConnectionIdEntry<Srt>>, } impl ConnectionIdManager { + pub const ACTIVE_LIMIT: usize = 8; + + /// A special value. See `ConnectionIdManager::add_odcid`. + const SEQNO_ODCID: u64 = u64::MAX; + + /// A special value. See `ConnectionIdEntry::empty_remote`. + const SEQNO_EMPTY: u64 = u64::MAX - 1; + + pub const SEQNO_PREFERRED: u64 = 1; + pub fn new(generator: Rc<RefCell<dyn ConnectionIdGenerator>>, initial: ConnectionId) -> Self { let mut connection_ids = ConnectionIdStore::default(); connection_ids.add_local(ConnectionIdEntry::initial_local(initial)); @@ -491,19 +485,17 @@ impl ConnectionIdManager { } /// Generate a connection ID and stateless reset token for a preferred address. - pub fn preferred_address_cid(&mut self) -> Res<(ConnectionId, [u8; 16])> { + pub fn preferred_address_cid(&mut self) -> Res<(ConnectionId, Srt)> { if self.generator.deref().borrow().generates_empty_cids() { return Err(Error::ConnectionIdsExhausted); } if let Some(cid) = self.generator.borrow_mut().generate_cid() { assert_ne!(cid.len(), 0); - debug_assert_eq!(self.next_seqno, CONNECTION_ID_SEQNO_PREFERRED); + debug_assert_eq!(self.next_seqno, Self::SEQNO_PREFERRED); self.connection_ids .add_local(ConnectionIdEntry::new(self.next_seqno, cid.clone(), ())); self.next_seqno += 1; - - let srt = ConnectionIdEntry::random_srt(); - Ok((cid, srt)) + Ok((cid, Srt::random())) } else { Err(Error::ConnectionIdsExhausted) } @@ -516,7 +508,7 @@ impl ConnectionIdManager { pub fn retire(&mut self, seqno: u64) { // TODO(mt) - consider keeping connection IDs around for a short while. - let empty_cid = seqno == CONNECTION_ID_SEQNO_EMPTY + let empty_cid = seqno == Self::SEQNO_EMPTY || self .connection_ids .cids @@ -535,20 +527,20 @@ impl ConnectionIdManager { /// Note that this is only done *after* an Initial packet from the client is /// successfully processed. pub fn add_odcid(&mut self, cid: ConnectionId) { - let entry = ConnectionIdEntry::new(CONNECTION_ID_SEQNO_ODCID, cid, ()); + let entry = ConnectionIdEntry::new(Self::SEQNO_ODCID, cid, ()); self.connection_ids.add_local(entry); } /// Stop treating the original destination connection ID as valid. pub fn remove_odcid(&mut self) { - self.connection_ids.retire(CONNECTION_ID_SEQNO_ODCID); + self.connection_ids.retire(Self::SEQNO_ODCID); } pub fn set_limit(&mut self, limit: u64) { debug_assert!(limit >= 2); self.limit = min( - LOCAL_ACTIVE_CID_LIMIT, - usize::try_from(limit).unwrap_or(LOCAL_ACTIVE_CID_LIMIT), + Self::ACTIVE_LIMIT, + usize::try_from(limit).unwrap_or(Self::ACTIVE_LIMIT), ); } @@ -587,26 +579,24 @@ impl ConnectionIdManager { let maybe_cid = self.generator.borrow_mut().generate_cid(); if let Some(cid) = maybe_cid { assert_ne!(cid.len(), 0); - // TODO: generate the stateless reset tokens from the connection ID and a key. - let srt = ConnectionIdEntry::random_srt(); - let seqno = self.next_seqno; self.next_seqno += 1; self.connection_ids .add_local(ConnectionIdEntry::new(seqno, cid.clone(), ())); - let entry = ConnectionIdEntry::new(seqno, cid, srt); + // TODO: generate the stateless reset tokens from the connection ID and a key. + let entry = ConnectionIdEntry::new(seqno, cid, Srt::random()); entry.write(builder, stats); tokens.push(recovery::Token::NewConnectionId(entry)); } } } - pub fn lost(&mut self, entry: &ConnectionIdEntry<[u8; 16]>) { + pub fn lost(&mut self, entry: &ConnectionIdEntry<Srt>) { self.lost_new_connection_id.push(entry.clone()); } - pub fn acked(&mut self, entry: &ConnectionIdEntry<[u8; 16]>) { + pub fn acked(&mut self, entry: &ConnectionIdEntry<Srt>) { self.lost_new_connection_id .retain(|e| e.seqno != entry.seqno); } @@ -615,9 +605,15 @@ impl ConnectionIdManager { #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { + use neqo_common::Encoder; use test_fixture::fixture_init; - use crate::{cid::MAX_CONNECTION_ID_LEN, ConnectionId}; + use crate::{ + cid::{ConnectionId, ConnectionIdEntry}, + packet, + stats::FrameStats, + Token as Srt, + }; #[test] fn generate_initial_cid() { @@ -625,9 +621,32 @@ mod tests { for _ in 0..100 { let cid = ConnectionId::generate_initial(); assert!( - matches!(cid.len(), 8..=MAX_CONNECTION_ID_LEN), + matches!(cid.len(), 8..=ConnectionId::MAX_LEN), "connection ID length {cid:?}", ); } } + + #[test] + fn write_checks_length_correctly() { + fixture_init(); + let entry = ConnectionIdEntry::new(1, ConnectionId::from(&[]), Srt::random()); + let limit = 1 + + Encoder::varint_len(entry.sequence_number()) + + 1 + + 1 + + entry.connection_id().len() + + Srt::LEN; + let enc = Encoder::with_capacity(limit); + let mut builder = packet::Builder::short(enc, false, Some(&[]), limit); + assert_eq!( + builder.remaining(), + limit - 1, + "Builder::short consumed one byte" + ); + assert!( + !entry.write(&mut builder, &mut FrameStats::default()), + "couldn't write frame into too-short builder", + ); + } } diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs @@ -34,7 +34,7 @@ use crate::{ addr_valid::{AddressValidation, NewTokenState}, cid::{ ConnectionId, ConnectionIdEntry, ConnectionIdGenerator, ConnectionIdManager, - ConnectionIdRef, ConnectionIdStore, LOCAL_ACTIVE_CID_LIMIT, + ConnectionIdRef, ConnectionIdStore, }, crypto::{Crypto, CryptoDxState, Epoch}, ecn, @@ -49,6 +49,7 @@ use crate::{ rtt::{RttEstimate, GRANULARITY}, saved::SavedDatagrams, send_stream::{self, SendStream}, + stateless_reset::Token as Srt, stats::{Stats, StatsCell}, stream_id::StreamType, streams::{SendOrder, Streams}, @@ -282,7 +283,7 @@ pub struct Connection { cid_manager: ConnectionIdManager, address_validation: AddressValidationInfo, /// The connection IDs that were provided by the peer. - cids: ConnectionIdStore<[u8; 16]>, + cids: ConnectionIdStore<Srt>, /// The source connection ID that this endpoint uses for the handshake. /// Since we need to communicate this to our peer in tparams, setting this @@ -1340,11 +1341,11 @@ impl Connection { fn is_stateless_reset(&self, path: &PathRef, d: &[u8]) -> bool { // If the datagram is too small, don't try. // If the connection is connected, then the reset token will be invalid. - if d.len() < 16 || !self.state.connected() { + if d.len() < Srt::LEN || !self.state.connected() { return false; } - <&[u8; 16]>::try_from(&d[d.len() - 16..]) - .is_ok_and(|token| path.borrow().is_stateless_reset(token)) + Srt::try_from(&d[d.len() - Srt::LEN..]) + .is_ok_and(|token| path.borrow().is_stateless_reset(&token)) } fn check_stateless_reset( @@ -1357,7 +1358,10 @@ impl Connection { if first && self.is_stateless_reset(path, d) { // Failing to process a packet in a datagram might // indicate that there is a stateless reset present. - qdebug!("[{self}] Stateless reset: {}", hex(&d[d.len() - 16..])); + qdebug!( + "[{self}] Stateless reset: {}", + hex(&d[d.len() - Srt::LEN..]) + ); self.state_signaling.reset(); self.set_state( State::Draining { @@ -2073,7 +2077,7 @@ impl Connection { } fn migrate_to_preferred_address(&mut self, now: Instant) -> Res<()> { - let spa: Option<(tparams::PreferredAddress, ConnectionIdEntry<[u8; 16]>)> = if matches!( + let spa: Option<(tparams::PreferredAddress, ConnectionIdEntry<Srt>)> = if matches!( self.conn_params.get_preferred_address(), PreferredAddressConfig::Disabled ) { @@ -2199,7 +2203,12 @@ impl Connection { version: Version, grease_quic_bit: bool, limit: usize, - ) -> (packet::Type, packet::Builder<&'a mut Vec<u8>>) { + largest_acknowledged: Option<packet::Number>, + ) -> ( + packet::Type, + packet::Builder<&'a mut Vec<u8>>, + packet::Number, + ) { let pt = packet::Type::from(epoch); let mut builder = if pt == packet::Type::Short { qdebug!("Building Short dcid {:?}", path.remote_cid()); @@ -2226,16 +2235,6 @@ impl Connection { } } - (pt, builder) - } - - #[must_use] - fn add_packet_number( - builder: &mut packet::Builder<&mut Vec<u8>>, - tx: &CryptoDxState, - largest_acknowledged: Option<packet::Number>, - ) -> packet::Number { - // Get the packet number and work out how long it is. let pn = tx.next_pn(); let unacked_range = largest_acknowledged.map_or_else(|| pn + 1, |la| (pn - la) << 1); // Count how many bytes in this range are non-zero. @@ -2247,7 +2246,8 @@ impl Connection { ); // TODO(mt) also use `4*path CWND/path MTU` to set a minimum length. builder.pn(pn, pn_len); - pn + + (pt, builder, pn) } fn can_grease_quic_bit(&self) -> bool { @@ -2677,7 +2677,7 @@ impl Connection { let header_start = encoder.len(); - let (pt, mut builder) = Self::build_packet_header( + let (pt, mut builder, pn) = Self::build_packet_header( &path.borrow(), epoch, encoder, @@ -2688,10 +2688,6 @@ impl Connection { // Limit the packet builder further to leave space for AEAD // expansion added in `builder.build` below. limit - aead_expansion, - ); - let pn = Self::add_packet_number( - &mut builder, - tx, self.loss_recovery.largest_acknowledged_pn(space), ); // The builder will set the limit to 0 if there isn't enough space for the header. @@ -2773,7 +2769,9 @@ impl Connection { initial_sent = Some(sent); needs_padding = true; } else { - if pt == packet::Type::Handshake && self.role == Role::Client { + if pt.is_long() && self.role == Role::Client && initial_sent.is_none() { + // Disable padding for any long header packet if the UDP packet doesn't include + // an Initial packet. needs_padding = false; } self.loss_recovery.on_packet_sent(path, sent, now); @@ -2931,8 +2929,8 @@ impl Connection { } let reset_token = remote.get_bytes(StatelessResetToken).map_or_else( - || Ok(ConnectionIdEntry::random_srt()), - |token| <[u8; 16]>::try_from(token).map_err(|_| Error::TransportParameter), + || Ok(Srt::random()), + |token| Srt::try_from(token).map_err(|_| Error::TransportParameter), )?; let path = self.paths.primary().ok_or(Error::NoAvailablePath)?; path.borrow_mut().set_reset_token(reset_token); @@ -3279,10 +3277,10 @@ impl Connection { self.cids.add_remote(ConnectionIdEntry::new( sequence_number, ConnectionId::from(connection_id), - stateless_reset_token.to_owned(), + stateless_reset_token, ))?; self.paths.retire_cids(retire_prior, &mut self.cids); - if self.cids.len() >= LOCAL_ACTIVE_CID_LIMIT { + if self.cids.len() >= ConnectionIdManager::ACTIVE_LIMIT { qinfo!("[{self}] received too many connection IDs"); return Err(Error::ConnectionIdLimitExceeded); } @@ -3830,7 +3828,7 @@ impl Connection { let mut buffer = Vec::new(); let encoder = Encoder::new_borrowed_vec(&mut buffer); - let (_, mut builder) = Self::build_packet_header( + let (_, builder, _) = Self::build_packet_header( &path.borrow(), epoch, encoder, @@ -3839,10 +3837,6 @@ impl Connection { version, false, usize::MAX, - ); - _ = Self::add_packet_number( - &mut builder, - tx, self.loss_recovery .largest_acknowledged_pn(PacketNumberSpace::ApplicationData), ); diff --git a/third_party/rust/neqo-transport/src/connection/params.rs b/third_party/rust/neqo-transport/src/connection/params.rs @@ -6,12 +6,9 @@ use std::{cmp::max, time::Duration}; -use neqo_common::MAX_VARINT; - pub use crate::recovery::FAST_PTO_SCALE; use crate::{ - connection::{ConnectionIdManager, Role, LOCAL_ACTIVE_CID_LIMIT}, - recv_stream::INITIAL_RECV_WINDOW_SIZE, + connection::{ConnectionIdManager, Role}, rtt::GRANULARITY, stream_id::StreamType, tparams::{ @@ -29,9 +26,66 @@ use crate::{ CongestionControlAlgorithm, Res, DEFAULT_INITIAL_RTT, }; -const LOCAL_MAX_DATA: u64 = MAX_VARINT; -const LOCAL_STREAM_LIMIT_BIDI: u64 = 16; -const LOCAL_STREAM_LIMIT_UNI: u64 = 16; +/// Maximum number of bidirectional streams that the remote can open. +/// +/// Constant throughout the lifetime of the connection. +/// +/// See also <https://github.com/google/quiche/blob/4f1f0fcea045cd71410c2c318773fc24c3523ed7/quiche/quic/core/quic_constants.h#L113-L114>. +const LOCAL_STREAM_LIMIT_BIDI: u64 = 100; +/// Maximum number of unidirectional streams that the remote can open. +/// +/// Constant throughout the lifetime of the connection. +/// +/// See also <https://github.com/google/quiche/blob/4f1f0fcea045cd71410c2c318773fc24c3523ed7/quiche/quic/core/quic_constants.h#L113-L114>. +const LOCAL_STREAM_LIMIT_UNI: u64 = 100; + +/// Factor to multiply stream-level data flow control limits to get +/// connection-level data flow control limits. +/// +/// Prevents a single stream from taking up the entire connection-level +/// capacity. +/// +/// TODO: Consider further tuning. +const CONNECTION_FACTOR: u64 = 2; + +/// Initial stream-level receive window size. +/// +/// Auto-tuned throughout the lifetime of the connection. See flow control +/// implementation for details. +/// +/// See also <https://datatracker.ietf.org/doc/html/rfc9000#name-max_stream_data-frames>. +pub const INITIAL_LOCAL_MAX_STREAM_DATA: usize = 1024 * 1024; +/// Initial connection-level receive window size. +/// +/// Set to 16 times the initial stream-level receive window to enable some +/// connection-level parallelism. +/// +/// Auto-tuned throughout the lifetime of the connection. See flow control +/// implementation for details. +/// +/// See also <https://datatracker.ietf.org/doc/html/rfc9000#frame-max-data>. +pub const INITIAL_LOCAL_MAX_DATA: u64 = INITIAL_LOCAL_MAX_STREAM_DATA as u64 * CONNECTION_FACTOR; + +/// Limit for the maximum amount of bytes active on a single stream, i.e. limit +/// for the size of the stream receive window. +/// +/// A value of 10 MiB allows for: +/// +/// - 10ms rtt and 8.3 GBit/s +/// - 20ms rtt and 4.2 GBit/s +/// - 40ms rtt and 2.1 GBit/s +/// - 100ms rtt and 0.8 GBit/s +/// +/// Keep in sync with [`crate::send_stream::MAX_SEND_BUFFER_SIZE`]. +/// +/// See also <https://datatracker.ietf.org/doc/html/rfc9000#name-max_stream_data-frames>. +pub const MAX_LOCAL_MAX_STREAM_DATA: u64 = 10 * 1024 * 1024; +/// Limit for the maximum amount of bytes active on the connection, i.e. limit +/// for the size of the connection-level receive window. +/// +/// See also <https://datatracker.ietf.org/doc/html/rfc9000#frame-max-data>. +pub const MAX_LOCAL_MAX_DATA: u64 = MAX_LOCAL_MAX_STREAM_DATA * CONNECTION_FACTOR; + // Maximum size of a QUIC DATAGRAM frame, as specified in https://datatracker.ietf.org/doc/html/rfc9221#section-3-4. const MAX_DATAGRAM_FRAME_SIZE: u64 = 65535; const MAX_QUEUED_DATAGRAMS_DEFAULT: usize = 10; @@ -105,12 +159,12 @@ impl Default for ConnectionParameters { Self { versions: version::Config::default(), cc_algorithm: CongestionControlAlgorithm::Cubic, - max_data: LOCAL_MAX_DATA, - max_stream_data_bidi_remote: u64::try_from(INITIAL_RECV_WINDOW_SIZE) + max_data: INITIAL_LOCAL_MAX_DATA, + max_stream_data_bidi_remote: u64::try_from(INITIAL_LOCAL_MAX_STREAM_DATA) .expect("usize fits in u64"), - max_stream_data_bidi_local: u64::try_from(INITIAL_RECV_WINDOW_SIZE) + max_stream_data_bidi_local: u64::try_from(INITIAL_LOCAL_MAX_STREAM_DATA) .expect("usize fits in u64"), - max_stream_data_uni: u64::try_from(INITIAL_RECV_WINDOW_SIZE) + max_stream_data_uni: u64::try_from(INITIAL_LOCAL_MAX_STREAM_DATA) .expect("usize fits in u64"), max_streams_bidi: LOCAL_STREAM_LIMIT_BIDI, max_streams_uni: LOCAL_STREAM_LIMIT_UNI, @@ -450,7 +504,7 @@ impl ConnectionParameters { // default parameters tps.local_mut().set_integer( ActiveConnectionIdLimit, - u64::try_from(LOCAL_ACTIVE_CID_LIMIT)?, + u64::try_from(ConnectionIdManager::ACTIVE_LIMIT)?, ); if self.disable_migration { tps.local_mut().set_empty(DisableMigration); diff --git a/third_party/rust/neqo-transport/src/connection/tests/close.rs b/third_party/rust/neqo-transport/src/connection/tests/close.rs @@ -13,6 +13,7 @@ use super::{ connect, connect_force_idle, default_client, default_server, send_something, }; use crate::{ + stateless_reset::Token as Srt, tparams::{TransportParameter, TransportParameterId::StatelessResetToken}, AppError, CloseReason, Error, ERROR_APPLICATION_CLOSE, }; @@ -211,7 +212,10 @@ fn stateless_reset_client() { let mut client = default_client(); let mut server = default_server(); server - .set_local_tparam(StatelessResetToken, TransportParameter::Bytes(vec![77; 16])) + .set_local_tparam( + StatelessResetToken, + TransportParameter::Bytes(vec![77; Srt::LEN]), + ) .unwrap(); connect_force_idle(&mut client, &mut server); diff --git a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs @@ -536,11 +536,11 @@ fn too_many_datagram_events() { // Datagram with FIRST_DATAGRAM data will be dropped. assert!(matches!( client.next_event().unwrap(), - ConnectionEvent::Datagram(data) if data == SECOND_DATAGRAM + ConnectionEvent::IncomingDatagramDropped )); assert!(matches!( client.next_event().unwrap(), - ConnectionEvent::IncomingDatagramDropped + ConnectionEvent::Datagram(data) if data == SECOND_DATAGRAM )); assert!(matches!( client.next_event().unwrap(), diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs @@ -948,9 +948,11 @@ fn anti_amplification() { assert!(!maybe_authenticate(&mut client)); // No need yet. // The client sends a padded datagram, with just ACKs for Initial and Handshake. + // Per RFC 9000 Section 14.1, datagrams containing Initial packets must be + // at least 1200 bytes, even when coalesced with Handshake packets. assert_eq!(client.stats().frame_tx.ack, ack_count + 2); assert_eq!(client.stats().frame_tx.all(), frame_count + 2); - assert_ne!(ack.len(), client.plpmtu()); // Not padded (it includes Handshake). + assert_eq!(ack.len(), client.plpmtu()); // Must be padded (contains Initial). now += DEFAULT_RTT / 2; let remainder = server.process(Some(ack), now).dgram(); diff --git a/third_party/rust/neqo-transport/src/connection/tests/idle.rs b/third_party/rust/neqo-transport/src/connection/tests/idle.rs @@ -16,8 +16,7 @@ use super::{ AT_LEAST_PTO, DEFAULT_STREAM_DATA, }; use crate::{ - packet::{self, PACKET_LIMIT}, - recovery, + packet, recovery, stats::FrameStats, stream_id::{StreamId, StreamType}, tparams::{TransportParameter, TransportParameterId}, @@ -286,7 +285,7 @@ fn idle_caching() { let mut client = default_client(); let mut server = default_server(); let start = now(); - let mut builder = packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + let mut builder = packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); // Perform the first round trip, but drop the Initial from the server. // The client then caches the Handshake packet. diff --git a/third_party/rust/neqo-transport/src/connection/tests/keys.rs b/third_party/rust/neqo-transport/src/connection/tests/keys.rs @@ -5,7 +5,10 @@ // except according to those terms. use neqo_common::{qdebug, Datagram}; -use test_fixture::{now, split_datagram}; +use test_fixture::{ + assertions::{is_handshake, is_initial}, + now, split_datagram, +}; use super::{ super::{ @@ -17,7 +20,7 @@ use super::{ }; use crate::{ crypto::{OVERWRITE_INVOCATIONS, UPDATE_WRITE_KEYS_AT}, - packet, + packet, MIN_INITIAL_PACKET_SIZE, }; fn check_discarded( @@ -356,3 +359,52 @@ fn automatic_update_write_keys_blocked() { State::Closed(CloseReason::Transport(Error::KeysExhausted)) )); } + +/// Test that when both Initial and Handshake packets are sent together due to PTO, +/// the resulting datagram is properly padded to `MIN_INITIAL_PACKET_SIZE` (1200 bytes). +/// +/// See RFC 9000 14.1 <https://www.rfc-editor.org/rfc/rfc9000.html#name-initial-datagram-size>. +#[test] +fn initial_handshake_pto_padding() { + let mut client = default_client(); + let mut now = now(); + + let c_init1 = client.process_output(now).dgram(); + let c_init2 = client.process_output(now).dgram(); + assert!(c_init1.is_some() && c_init2.is_some()); + + let mut server = default_server(); + server.process_input(c_init1.unwrap(), now); + let s_hs1 = server.process(c_init2, now).dgram(); + assert!(s_hs1.is_some()); + let s_hs2 = server.process_output(now).dgram(); + assert!(s_hs2.is_some()); + + // Client receives server handshake messages but we immediately advance time + // to trigger PTO before allowing any client output. This simulates the + // scenario where all client packets are lost. + client.process_input(s_hs1.unwrap(), now); + client.process_input(s_hs2.unwrap(), now); + now += AT_LEAST_PTO; + + // Collect all PTO datagrams - there may be multiple. + let mut pto_dgrams = Vec::new(); + while let Some(dgram) = client.process_output(now).dgram() { + pto_dgrams.push(dgram); + } + assert!(!pto_dgrams.is_empty()); + + // Iterate over all datagrams to find one with coalesced Initial+Handshake. + // Any datagram containing an Initial packet must be properly padded. + let mut found_coalesced = false; + for dgram in &pto_dgrams { + let (first, second) = split_datagram(dgram); + if is_initial(&first, false) { + assert!(dgram.len() >= MIN_INITIAL_PACKET_SIZE); + if let Some(hs) = &second { + found_coalesced |= is_handshake(hs); + } + } + } + assert!(found_coalesced); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/migration.rs b/third_party/rust/neqo-transport/src/connection/tests/migration.rs @@ -30,7 +30,7 @@ use super::{ CountingConnectionIdGenerator, }; use crate::{ - cid::LOCAL_ACTIVE_CID_LIMIT, + cid::ConnectionIdManager, connection::tests::{ assert_path_challenge_min_len, connect, send_something_paced, send_with_extra, }, @@ -1059,7 +1059,7 @@ impl crate::connection::test_internal::FrameWriter for RetireAll { .encode_varint(SEQNO) .encode_varint(SEQNO) // Retire Prior To .encode_vec(1, &cid) - .encode(&[0x7f; 16]); + .encode([0x7f; 16]); } } @@ -1093,7 +1093,7 @@ fn retire_all() { ); assert_eq!( client.stats().frame_tx.retire_connection_id, - retire_cid_before + LOCAL_ACTIVE_CID_LIMIT + retire_cid_before + ConnectionIdManager::ACTIVE_LIMIT ); assert_ne!(get_cid(&retire), original_cid); diff --git a/third_party/rust/neqo-transport/src/connection/tests/recovery.rs b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant}; use neqo_common::qdebug; use neqo_crypto::AuthenticationStatus; use test_fixture::{ - assertions::{assert_handshake, assert_initial}, + assertions::{assert_handshake, assert_initial, is_handshake, is_initial}, now, split_datagram, }; @@ -944,3 +944,91 @@ fn ack_for_unsent() { } )); } + +/// Test that PTO fires for Handshake space even when no Handshake packets have been sent. +/// +/// This reproduces a handshake loss scenario where: +/// 1. Client receives Server Hello → Handshake keys installed +/// 2. Server's Handshake flight (Certificate, etc.) is lost/corrupted +/// 3. Client never sends any Handshake packets (has nothing to send yet) +/// 4. Client's Handshake space has `last_ack_eliciting` = None +/// 5. PTO timer never arms for Handshake space +/// 6. Connection times out +/// +/// The client should probe Handshake space to elicit server retransmission. +#[test] +fn pto_handshake_space_when_server_flight_lost() { + const RTT: Duration = Duration::from_millis(10); + let mut now = now(); + // This test assumes PTOs only involve single-packet flights, which is incompatible with + // multi-packet flights as used by MLKEM. + let mut client = new_client(ConnectionParameters::default().mlkem(false)); + let mut server = default_server(); + // This is a greasing transport parameter, and large enough that the + // server needs to send two Handshake packets. + let big = TransportParameter::Bytes(vec![0; Pmtud::default_plpmtu(DEFAULT_ADDR.ip())]); + server + .set_local_tparam(TestTransportParameter, big) + .unwrap(); + + let c1 = client.process_output(now).dgram(); + now += RTT / 2; + + // Collect all server output. + let mut server_dgrams = Vec::new(); + server_dgrams.push(server.process(c1, now).dgram().unwrap()); + while let Some(dgram) = server.process_output(now).dgram() { + server_dgrams.push(dgram); + } + assert!(!server_dgrams.is_empty()); + + // Send all Initial packets to the client, but drop all Handshake packets. + now += RTT / 2; + let mut found_hs = false; + for dgram in server_dgrams { + let (first, second) = split_datagram(&dgram); + if is_initial(&first, false) { + assert!(!found_hs, "got Initial after Handshake"); + if let Some(hs) = second { + found_hs |= is_handshake(&hs); + } + client.process_input(first, now); + } else { + found_hs |= is_handshake(&first); + } + } + assert!(found_hs); + + // Client processes the Initial packets (which install Handshake keys). + let c2 = client.process_output(now).dgram(); + assert!(c2.is_some()); // This is an ACK. Drop it. + assert_eq!(*client.state(), State::Handshaking); + + let pto = client.process_output(now).callback(); + assert_ne!(pto, Duration::ZERO); + now += pto; + + // Drain all packets and get the next timeout. + let next_timeout = loop { + if let Output::Callback(callback) = client.process_output(now) { + break callback; + } + }; + // Fire PTO - this should prime Handshake space. + now += next_timeout; + + // Collect all packets from this timeout and verify PING was sent. + let stats_before = client.stats().frame_tx; + let mut pto_packets = Vec::new(); + while let Some(dgram) = client.process_output(now).dgram() { + pto_packets.push(dgram); + } + + // Check if any Handshake PING probes packets were sent. + let mut has_handshake = false; + for dgram in &pto_packets { + let (first, second) = split_datagram(dgram); + has_handshake |= is_handshake(&first) || second.as_ref().is_some_and(|s| is_handshake(s)); + } + assert!(has_handshake && client.stats().frame_tx.ping > stats_before.ping); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/stream.rs b/third_party/rust/neqo-transport/src/connection/tests/stream.rs @@ -15,10 +15,10 @@ use super::{ DEFAULT_STREAM_DATA, }; use crate::{ + connection::params::INITIAL_LOCAL_MAX_STREAM_DATA, events::ConnectionEvent, frame::FrameType, packet, - recv_stream::INITIAL_RECV_WINDOW_SIZE, send_stream::{self, OrderGroup}, streams::{SendOrder, StreamOrder}, tparams::{TransportParameter, TransportParameterId::*}, @@ -438,7 +438,7 @@ fn max_data() { client.streams.handle_max_data(100_000_000); assert_eq!( client.stream_avail_send_space(stream_id).unwrap(), - INITIAL_RECV_WINDOW_SIZE - SMALL_MAX_DATA + INITIAL_LOCAL_MAX_STREAM_DATA - SMALL_MAX_DATA ); let evts = client.events().collect::<Vec<_>>(); @@ -885,7 +885,7 @@ fn stream_data_blocked_generates_max_stream_data() { } written += amount; } - assert_eq!(written, INITIAL_RECV_WINDOW_SIZE); + assert_eq!(written, INITIAL_LOCAL_MAX_STREAM_DATA); } /// See <https://github.com/mozilla/neqo/issues/871> diff --git a/third_party/rust/neqo-transport/src/connection/tests/vn.rs b/third_party/rust/neqo-transport/src/connection/tests/vn.rs @@ -23,7 +23,7 @@ use crate::{ tests::{connect_rtt_idle_with_modifier, AT_LEAST_PTO}, }, frame::FrameType, - packet::{self, PACKET_BIT_LONG}, + packet::{self}, tparams::{TransportParameter, TransportParameterId::*}, ConnectionParameters, Error, Stats, Version, MIN_INITIAL_PACKET_SIZE, }; @@ -74,8 +74,8 @@ fn create_vn(initial_pkt: &[u8], versions: &[u32]) -> Vec<u8> { let src_cid = dec.decode_vec(1).expect("client SCID"); let mut encoder = Encoder::default(); - encoder.encode_byte(PACKET_BIT_LONG); - encoder.encode(&[0; 4]); // Zero version == VN. + encoder.encode_byte(packet::BIT_LONG); + encoder.encode([0; 4]); // Zero version == VN. encoder.encode_vec(1, src_cid); encoder.encode_vec(1, dst_cid); diff --git a/third_party/rust/neqo-transport/src/events.rs b/third_party/rust/neqo-transport/src/events.rs @@ -164,31 +164,25 @@ impl ConnectionEvents { } // The number of datagrams in the events queue is limited to max_queued_datagrams. - // This function ensure this and deletes the oldest datagrams if needed. + // This function ensure this and deletes the oldest datagrams (head-drop) if needed. fn check_datagram_queued(&self, max_queued_datagrams: usize, stats: &mut Stats) { - let mut q = self.events.borrow_mut(); - let mut remove = None; - if q.iter() + let mut queue = self.events.borrow_mut(); + let count = queue + .iter() .filter(|evt| matches!(evt, ConnectionEvent::Datagram(_))) - .count() - == max_queued_datagrams - { - if let Some(d) = q - .iter() - .rev() - .enumerate() - .filter(|(_, evt)| matches!(evt, ConnectionEvent::Datagram(_))) - .take(1) - .next() - { - remove = Some(d.0); - } - } - if let Some(r) = remove { - q.remove(r); - q.push_back(ConnectionEvent::IncomingDatagramDropped); - stats.incoming_datagram_dropped += 1; + .count(); + if count < max_queued_datagrams { + // Below the limit. No action needed. + return; } + let first = queue + .iter_mut() + .find(|evt| matches!(evt, ConnectionEvent::Datagram(_))) + .expect("Checked above"); + // Remove the oldest (head-drop), replacing it with an + // IncomingDatagramDropped placeholder. + *first = ConnectionEvent::IncomingDatagramDropped; + stats.incoming_datagram_dropped += 1; } pub fn add_datagram(&self, max_queued_datagrams: usize, data: &[u8], stats: &mut Stats) { @@ -256,7 +250,7 @@ impl EventProvider for ConnectionEvents { mod tests { use neqo_common::event::Provider as _; - use crate::{CloseReason, ConnectionEvent, ConnectionEvents, Error, State, StreamId}; + use crate::{CloseReason, ConnectionEvent, ConnectionEvents, Error, State, Stats, StreamId}; #[test] fn event_culling() { @@ -317,4 +311,70 @@ mod tests { evts.connection_state_change(State::Closed(CloseReason::Transport(Error::StreamState))); assert_eq!(evts.events().count(), 1); } + + #[test] + fn datagram_queue_drops_oldest() { + const MAX_QUEUED: usize = 2; + + // Fill the queue to capacity, verify that and that there are no drops yet. + let e = ConnectionEvents::default(); + let mut stats = Stats::default(); + e.add_datagram(MAX_QUEUED, &[1], &mut stats); + e.add_datagram(MAX_QUEUED, &[2], &mut stats); + assert_eq!(stats.incoming_datagram_dropped, 0); + assert_eq!(e.events.borrow().len(), MAX_QUEUED); + + // Add one more datagram - this should drop the oldest ("1"). + e.add_datagram(MAX_QUEUED, &[3], &mut stats); + assert_eq!(stats.incoming_datagram_dropped, 1); + + // Should have one `IncomingDatagramDropped` event + `MAX_QUEUED` datagrams. + assert_eq!( + e.events.borrow().iter().collect::<Vec<_>>(), + [ + &ConnectionEvent::IncomingDatagramDropped, + &ConnectionEvent::Datagram(vec![2]), + &ConnectionEvent::Datagram(vec![3]), + ] + ); + } + + /// Previously `check_datagram_queued` had a bug that caused it to + /// potentially drop an unrelated event. + /// + /// See <https://github.com/mozilla/neqo/pull/3105> for details. + #[test] + fn datagram_queue_drops_datagram_not_unrelated_event() { + const MAX_QUEUED: usize = 2; + + let e = ConnectionEvents::default(); + let mut stats = Stats::default(); + + // Add unrelated event. + e.new_stream(4.into()); + + // Fill the queue with datagrams to capacity. + e.add_datagram(MAX_QUEUED, &[1], &mut stats); + e.add_datagram(MAX_QUEUED, &[2], &mut stats); + assert_eq!(stats.incoming_datagram_dropped, 0); + assert_eq!(e.events.borrow().len(), 1 + MAX_QUEUED); + + // Add one more datagram - this should drop the oldest ("1"), not the + // unrelated event. + e.add_datagram(MAX_QUEUED, &[3], &mut stats); + assert_eq!(stats.incoming_datagram_dropped, 1); + + // Should have one `IncomingDatagramDropped` event + `MAX_QUEUED` datagrams. + assert_eq!( + e.events.borrow().iter().collect::<Vec<_>>(), + [ + &ConnectionEvent::NewStream { + stream_id: StreamId::new(4) + }, + &ConnectionEvent::IncomingDatagramDropped, + &ConnectionEvent::Datagram(vec![2]), + &ConnectionEvent::Datagram(vec![3]), + ] + ); + } } diff --git a/third_party/rust/neqo-transport/src/fc.rs b/third_party/rust/neqo-transport/src/fc.rs @@ -9,7 +9,7 @@ use std::{ cmp::min, - fmt::Debug, + fmt::{Debug, Display}, num::NonZeroU64, ops::{Deref, DerefMut, Index, IndexMut}, time::{Duration, Instant}, @@ -18,10 +18,10 @@ use std::{ use neqo_common::{qdebug, qtrace, Buffer, Role, MAX_VARINT}; use crate::{ + connection::params::{MAX_LOCAL_MAX_DATA, MAX_LOCAL_MAX_STREAM_DATA}, frame::FrameType, packet, recovery::{self, StreamRecoveryToken}, - recv_stream::MAX_RECV_WINDOW_SIZE, stats::FrameStats, stream_id::{StreamId, StreamType}, Error, Res, @@ -44,6 +44,23 @@ pub const WINDOW_UPDATE_FRACTION: u64 = 4; /// congestion control window, in order to not unnecessarily limit throughput. const WINDOW_INCREASE_MULTIPLIER: u64 = 4; +/// Subject for flow control auto-tuning, used to avoid heap allocations +/// when logging. +#[derive(Debug, Clone, Copy)] +enum AutoTuneSubject { + Connection, + Stream(StreamId), +} + +impl Display for AutoTuneSubject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Connection => write!(f, "connection"), + Self::Stream(id) => write!(f, "stream {id}"), + } + } +} + #[derive(Debug)] pub struct SenderFlowControl<T> where @@ -225,8 +242,10 @@ where max_allowed: u64, /// Last time a flow control update was sent. /// - /// Only used in [`ReceiverFlowControl<StreamId>`] implementation for - /// receive window auto-tuning. + /// Used by auto-tuning logic to estimate sending rate between updates. + /// This is active for both stream-level + /// ([`ReceiverFlowControl<StreamId>`]) and connection-level + /// ([`ReceiverFlowControl<()>`]) flow control. last_update: Option<Instant>, /// Item received, but not retired yet. /// This will be used for byte flow control: each stream will remember its largest byte @@ -322,6 +341,70 @@ where pub const fn consumed(&self) -> u64 { self.consumed } + + /// Core auto-tuning logic for adjusting the maximum flow control window. + /// + /// This method is called by both connection-level and stream-level + /// implementations. It increases `max_active` when the sending rate exceeds + /// what the current window and RTT would allow, capping at `max_window`. + fn auto_tune_inner( + &mut self, + now: Instant, + rtt: Duration, + max_window: u64, + subject: AutoTuneSubject, + ) { + let Some(max_allowed_sent_at) = self.last_update else { + return; + }; + + let Ok(elapsed): Result<u64, _> = now + .duration_since(max_allowed_sent_at) + .as_micros() + .try_into() + else { + return; + }; + + let Ok(rtt): Result<NonZeroU64, _> = rtt + .as_micros() + .try_into() + .and_then(|rtt: u64| NonZeroU64::try_from(rtt)) + else { + // RTT is zero, no need for tuning. + return; + }; + + // Compute the amount of bytes we have received in excess + // of what `max_active` might allow. + let window_bytes_expected = self.max_active * elapsed / rtt; + let window_bytes_used = self.max_active - (self.max_allowed - self.retired); + let Some(excess) = window_bytes_used.checked_sub(window_bytes_expected) else { + // Used below expected. No auto-tuning needed. + return; + }; + + let prev_max_active = self.max_active; + self.max_active = min( + self.max_active + excess * WINDOW_INCREASE_MULTIPLIER, + max_window, + ); + + let increase = self.max_active - prev_max_active; + if increase > 0 { + qdebug!( + "Increasing max {subject} receive window by {} B, \ + previous max_active: {} MiB, \ + new max_active: {} MiB, \ + last update: {:?}, \ + rtt: {rtt:?}", + increase, + prev_max_active / 1024 / 1024, + self.max_active / 1024 / 1024, + now - max_allowed_sent_at, + ); + } + } } impl ReceiverFlowControl<()> { @@ -330,10 +413,15 @@ impl ReceiverFlowControl<()> { builder: &mut packet::Builder<B>, tokens: &mut recovery::Tokens, stats: &mut FrameStats, + now: Instant, + rtt: Duration, ) { if !self.frame_needed() { return; } + + self.auto_tune(now, rtt); + let max_allowed = self.next_limit(); if builder.write_varint_frame(&[FrameType::MaxData.into(), max_allowed]) { stats.max_data += 1; @@ -341,9 +429,21 @@ impl ReceiverFlowControl<()> { max_allowed, ))); self.frame_sent(max_allowed); + self.last_update = Some(now); } } + /// Auto-tune [`ReceiverFlowControl::max_active`], i.e. the connection flow + /// control window. + /// + /// If the sending rate (`window_bytes_used`) exceeds the rate allowed by + /// the maximum flow control window and the current rtt + /// (`window_bytes_expected`), try to increase the maximum flow control + /// window ([`ReceiverFlowControl::max_active`]). + fn auto_tune(&mut self, now: Instant, rtt: Duration) { + self.auto_tune_inner(now, rtt, MAX_LOCAL_MAX_DATA, AutoTuneSubject::Connection); + } + pub fn add_retired(&mut self, count: u64) { debug_assert!(self.retired + count <= self.consumed); self.retired += count; @@ -407,53 +507,11 @@ impl ReceiverFlowControl<StreamId> { /// (`window_bytes_expected`), try to increase the maximum flow control /// window ([`ReceiverFlowControl::max_active`]). fn auto_tune(&mut self, now: Instant, rtt: Duration) { - let Some(max_allowed_sent_at) = self.last_update else { - return; - }; - - let Ok(elapsed): Result<u64, _> = now - .duration_since(max_allowed_sent_at) - .as_micros() - .try_into() - else { - return; - }; - - let Ok(rtt): Result<NonZeroU64, _> = rtt - .as_micros() - .try_into() - .and_then(|rtt: u64| NonZeroU64::try_from(rtt)) - else { - // RTT is zero, no need for tuning. - return; - }; - - // Compute the amount of bytes we have received in excess - // of what `max_active` might allow. - let window_bytes_expected = self.max_active * elapsed / rtt; - let window_bytes_used = self.max_active - (self.max_allowed - self.retired); - let Some(excess) = window_bytes_used.checked_sub(window_bytes_expected) else { - // Used below expected. No auto-tuning needed. - return; - }; - - let prev_max_active = self.max_active; - self.max_active = min( - self.max_active + excess * WINDOW_INCREASE_MULTIPLIER, - MAX_RECV_WINDOW_SIZE, - ); - qdebug!( - "Increasing max stream receive window by {} B, \ - previous max_active: {} MiB, \ - new max_active: {} MiB, \ - last update: {:?}, \ - rtt: {rtt:?}, \ - stream_id: {}", - self.max_active - prev_max_active, - prev_max_active / 1024 / 1024, - self.max_active / 1024 / 1024, - now - max_allowed_sent_at, - self.subject, + self.auto_tune_inner( + now, + rtt, + MAX_LOCAL_MAX_STREAM_DATA, + AutoTuneSubject::Stream(self.subject), ); } @@ -671,18 +729,17 @@ mod test { time::{Duration, Instant}, }; - use neqo_common::{qdebug, Encoder, Role, MAX_VARINT}; + use neqo_common::{qdebug, Encoder, Role}; use neqo_crypto::random; use super::{LocalStreamLimits, ReceiverFlowControl, RemoteStreamLimits, SenderFlowControl}; use crate::{ + connection::params::{MAX_LOCAL_MAX_DATA, MAX_LOCAL_MAX_STREAM_DATA}, fc::WINDOW_UPDATE_FRACTION, - packet::{self, PACKET_LIMIT}, - recovery, - recv_stream::MAX_RECV_WINDOW_SIZE, + packet, recovery, stats::FrameStats, stream_id::{StreamId, StreamType}, - ConnectionParameters, Error, Res, INITIAL_RECV_WINDOW_SIZE, + ConnectionParameters, Error, Res, INITIAL_LOCAL_MAX_DATA, INITIAL_LOCAL_MAX_STREAM_DATA, }; #[test] @@ -922,7 +979,7 @@ mod test { fc[StreamType::BiDi].send_flowc_update(); // consume the frame let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut tokens = recovery::Tokens::new(); fc[StreamType::BiDi].write_frames(&mut builder, &mut tokens, &mut FrameStats::default()); assert_eq!(tokens.len(), 1); @@ -1030,7 +1087,7 @@ mod test { fn write_frames(fc: &mut ReceiverFlowControl<StreamId>, rtt: Duration, now: Instant) -> usize { let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut tokens = recovery::Tokens::new(); fc.write_frames( &mut builder, @@ -1046,9 +1103,10 @@ mod test { fn trigger_factor() -> Res<()> { let rtt = Duration::from_millis(40); let now = test_fixture::now(); - let mut fc = ReceiverFlowControl::new(StreamId::new(0), INITIAL_RECV_WINDOW_SIZE as u64); + let mut fc = + ReceiverFlowControl::new(StreamId::new(0), INITIAL_LOCAL_MAX_STREAM_DATA as u64); - let fraction = INITIAL_RECV_WINDOW_SIZE as u64 / WINDOW_UPDATE_FRACTION; + let fraction = INITIAL_LOCAL_MAX_STREAM_DATA as u64 / WINDOW_UPDATE_FRACTION; let consumed = fc.set_consumed(fraction)?; fc.add_retired(consumed); @@ -1067,7 +1125,8 @@ mod test { fn auto_tuning_increase_no_decrease() -> Res<()> { let rtt = Duration::from_millis(40); let mut now = test_fixture::now(); - let mut fc = ReceiverFlowControl::new(StreamId::new(0), INITIAL_RECV_WINDOW_SIZE as u64); + let mut fc = + ReceiverFlowControl::new(StreamId::new(0), INITIAL_LOCAL_MAX_STREAM_DATA as u64); let initial_max_active = fc.max_active(); // Consume and retire multiple receive windows without increasing time. @@ -1102,7 +1161,8 @@ mod test { fn stream_data_blocked_triggers_auto_tuning() -> Res<()> { let rtt = Duration::from_millis(40); let now = test_fixture::now(); - let mut fc = ReceiverFlowControl::new(StreamId::new(0), INITIAL_RECV_WINDOW_SIZE as u64); + let mut fc = + ReceiverFlowControl::new(StreamId::new(0), INITIAL_LOCAL_MAX_STREAM_DATA as u64); // Send first window update to give auto-tuning algorithm a baseline. let consumed = fc.set_consumed(fc.next_limit())?; @@ -1158,12 +1218,12 @@ mod test { let mut send_to_recv = VecDeque::new(); let mut recv_to_send = VecDeque::new(); - let mut last_max_active = INITIAL_RECV_WINDOW_SIZE as u64; + let mut last_max_active = INITIAL_LOCAL_MAX_STREAM_DATA as u64; let mut last_max_active_changed = now; - let mut sender_window = INITIAL_RECV_WINDOW_SIZE as u64; + let mut sender_window = INITIAL_LOCAL_MAX_STREAM_DATA as u64; let mut fc = - ReceiverFlowControl::new(StreamId::new(0), INITIAL_RECV_WINDOW_SIZE as u64); + ReceiverFlowControl::new(StreamId::new(0), INITIAL_LOCAL_MAX_STREAM_DATA as u64); loop { // Sender receives window updates. @@ -1226,12 +1286,12 @@ mod test { ); assert!( - fc.max_active() + TOLERANCE >= bdp || fc.max_active() == MAX_RECV_WINDOW_SIZE, + fc.max_active() + TOLERANCE >= bdp || fc.max_active() == MAX_LOCAL_MAX_STREAM_DATA, "{summary} Receive window is smaller than the bdp." ); assert!( fc.max_active - TOLERANCE <= bdp - || fc.max_active == INITIAL_RECV_WINDOW_SIZE as u64, + || fc.max_active == INITIAL_LOCAL_MAX_STREAM_DATA as u64, "{summary} Receive window is larger than the bdp." ); @@ -1242,24 +1302,105 @@ mod test { } #[test] - fn max_active_larger_max_varint() { - // Instead of doing proper connection flow control, Neqo simply sets - // the largest connection flow control limit possible. + fn connection_flow_control_initial_window() { let max_data = ConnectionParameters::default().get_max_data(); - assert_eq!(max_data, MAX_VARINT); - let mut fc = ReceiverFlowControl::new((), max_data); + assert_eq!(max_data, INITIAL_LOCAL_MAX_DATA); + } - // Say that the remote consumes 1 byte of that connection flow control - // limit and then requests a connection flow control update. - fc.consume(1).unwrap(); - fc.add_retired(1); - fc.send_flowc_update(); + #[test] + fn connection_flow_control_auto_tune() -> Res<()> { + let rtt = Duration::from_millis(40); + let now = test_fixture::now(); + let initial_window = (INITIAL_LOCAL_MAX_STREAM_DATA * 16) as u64; + let mut fc = ReceiverFlowControl::new((), initial_window); + let initial_max_active = fc.max_active(); - // Neqo should never attempt writing a connection flow control update - // larger than the largest possible QUIC varint value. - let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); - let mut tokens = recovery::Tokens::new(); - fc.write_frames(&mut builder, &mut tokens, &mut FrameStats::default()); + // Helper to write frames + let write_conn_frames = |fc: &mut ReceiverFlowControl<()>, now: Instant| { + let mut builder = + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); + let mut tokens = recovery::Tokens::new(); + fc.write_frames( + &mut builder, + &mut tokens, + &mut FrameStats::default(), + now, + rtt, + ); + tokens.len() + }; + + // Consume and retire multiple windows to trigger auto-tuning. + // Each iteration: consume a full window, retire it, send update. + for _ in 1..11 { + let to_consume = fc.max_active(); + fc.consume(to_consume)?; + fc.add_retired(to_consume); + write_conn_frames(&mut fc, now); + } + let increased_max_active = fc.max_active(); + + assert!( + initial_max_active < increased_max_active, + "expect connection-level receive window auto-tuning to increase max_active on full utilization" + ); + + Ok(()) + } + + #[test] + fn connection_flow_control_respects_max_window() -> Res<()> { + let rtt = Duration::from_millis(40); + let now = test_fixture::now(); + let initial_window = (INITIAL_LOCAL_MAX_STREAM_DATA * 16) as u64; + let mut fc = ReceiverFlowControl::new((), initial_window); + + // Helper to write frames + let write_conn_frames = |fc: &mut ReceiverFlowControl<()>| { + let mut builder = + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); + let mut tokens = recovery::Tokens::new(); + fc.write_frames( + &mut builder, + &mut tokens, + &mut FrameStats::default(), + now, + rtt, + ); + tokens.len() + }; + + // Consume and retire many full windows to push window to the limit. + // Keep consuming without advancing time to create maximum pressure. + for _ in 0..1000 { + let prev_max = fc.max_active(); + let to_consume = fc.max_active(); + fc.consume(to_consume)?; + fc.add_retired(to_consume); + write_conn_frames(&mut fc); + + // Stop if we've reached the maximum and it's not growing anymore + if fc.max_active() == MAX_LOCAL_MAX_DATA && fc.max_active() == prev_max { + qdebug!( + "Reached and stabilized at max window: {} MiB", + fc.max_active() / 1024 / 1024 + ); + break; + } + } + + assert_eq!( + fc.max_active(), + MAX_LOCAL_MAX_DATA, + "expect connection-level receive window to cap at MAX_LOCAL_MAX_DATA (100 MiB), got {} MiB", + fc.max_active() / 1024 / 1024 + ); + + qdebug!( + "Connection flow control window reached max: {} MiB", + fc.max_active() / 1024 / 1024 + ); + + Ok(()) } } diff --git a/third_party/rust/neqo-transport/src/frame.rs b/third_party/rust/neqo-transport/src/frame.rs @@ -12,10 +12,10 @@ use neqo_common::{qtrace, Decoder, Encoder, MAX_VARINT}; use strum::FromRepr; use crate::{ - cid::MAX_CONNECTION_ID_LEN, ecn, packet, + stateless_reset::Token as Srt, stream_id::{StreamId, StreamType}, - AppError, Error, Res, TransportError, + AppError, ConnectionId, Error, Res, TransportError, }; #[repr(u64)] @@ -209,7 +209,7 @@ pub enum Frame<'a> { sequence_number: u64, retire_prior: u64, connection_id: &'a [u8], - stateless_reset_token: &'a [u8; 16], + stateless_reset_token: Srt, }, RetireConnectionId { sequence_number: u64, @@ -608,11 +608,10 @@ impl<'a> Frame<'a> { let sequence_number = dv(dec)?; let retire_prior = dv(dec)?; let connection_id = d(dec.decode_vec(1))?; - if connection_id.len() > MAX_CONNECTION_ID_LEN { + if connection_id.len() > ConnectionId::MAX_LEN { return Err(Error::FrameEncoding); } - let srt = d(dec.decode(16))?; - let stateless_reset_token = <&[_; 16]>::try_from(srt)?; + let stateless_reset_token = Srt::try_from(dec)?; Ok(Self::NewConnectionId { sequence_number, @@ -691,10 +690,9 @@ mod tests { use neqo_common::{Decoder, Encoder}; use crate::{ - cid::MAX_CONNECTION_ID_LEN, ecn::Count, frame::{AckRange, Frame, FrameType}, - CloseError, Error, StreamId, StreamType, + CloseError, ConnectionId, Error, StreamId, StreamType, Token as Srt, }; fn just_dec(f: &Frame, s: &str) { @@ -906,7 +904,7 @@ mod tests { sequence_number: 0x1234, retire_prior: 0, connection_id: &[0x01, 0x02], - stateless_reset_token: &[9; 16], + stateless_reset_token: Srt::new([9; Srt::LEN]), }; just_dec(&f, "1852340002010209090909090909090909090909090909"); @@ -915,7 +913,7 @@ mod tests { #[test] fn too_large_new_connection_id() { let mut enc = Encoder::from_hex("18523400"); // up to the CID - enc.encode_vvec(&[0x0c; MAX_CONNECTION_ID_LEN + 10]); + enc.encode_vvec(&[0x0c; ConnectionId::MAX_LEN + 10]); enc.encode(&[0x11; 16][..]); assert_eq!( Frame::decode(&mut enc.as_decoder()).unwrap_err(), diff --git a/third_party/rust/neqo-transport/src/lib.rs b/third_party/rust/neqo-transport/src/lib.rs @@ -49,6 +49,7 @@ pub mod send_stream; mod sender; pub mod server; mod sni; +mod stateless_reset; mod stats; pub mod stream_id; pub mod streams; @@ -63,16 +64,20 @@ pub use self::{ EmptyConnectionIdGenerator, RandomConnectionIdGenerator, }, connection::{ - params::ConnectionParameters, Connection, Output, OutputBatch, State, ZeroRttState, + params::{ + ConnectionParameters, INITIAL_LOCAL_MAX_DATA, INITIAL_LOCAL_MAX_STREAM_DATA, + MAX_LOCAL_MAX_STREAM_DATA, + }, + Connection, Output, OutputBatch, State, ZeroRttState, }, events::{ConnectionEvent, ConnectionEvents}, frame::CloseError, packet::MIN_INITIAL_PACKET_SIZE, pmtud::Pmtud, quic_datagrams::DatagramTracking, - recv_stream::INITIAL_RECV_WINDOW_SIZE, rtt::DEFAULT_INITIAL_RTT, sni::find_sni, + stateless_reset::Token, stats::Stats, stream_id::{StreamId, StreamType}, version::Version, @@ -253,7 +258,7 @@ pub enum CloseReason { impl CloseReason { /// Checks enclosed error for [`Error::None`] and - /// [`CloseReason::Application(0)`]. + /// [`CloseReason::Application`] with code `0`. #[must_use] pub const fn is_error(&self) -> bool { !matches!(self, Self::Transport(Error::None) | Self::Application(0),) diff --git a/third_party/rust/neqo-transport/src/pace.rs b/third_party/rust/neqo-transport/src/pace.rs @@ -34,8 +34,9 @@ pub struct Pacer { t: Instant, /// The maximum capacity, or burst size, in bytes. m: usize, - /// The current capacity, in bytes. - c: usize, + /// The current capacity, in bytes. When negative, represents accumulated debt + /// from sub-granularity sends that will be paid off in future pacing calculations. + c: isize, /// The packet size or minimum capacity for sending, in bytes. p: usize, } @@ -53,11 +54,12 @@ impl Pacer { /// fraction of the maximum packet size, if not the packet size. pub fn new(enabled: bool, now: Instant, m: usize, p: usize) -> Self { assert!(m >= p, "maximum capacity has to be at least one packet"); + assert!(isize::try_from(p).is_ok(), "p ({p}) exceeds isize::MAX"); Self { enabled, t: now, m, - c: m, + c: isize::try_from(m).expect("maximum capacity fits into isize"), p, } } @@ -70,12 +72,14 @@ impl Pacer { self.p = mtu; } - /// Determine when the next packet will be available based on the provided RTT - /// and congestion window. This doesn't update state. - /// This returns a time, which could be in the past (this object doesn't know what - /// the current time is). + /// Determine when the next packet will be available based on the provided + /// RTT, provided congestion window and accumulated credit or debt. This + /// doesn't update state. This returns a time, which could be in the past + /// (this object doesn't know what the current time is). pub fn next(&self, rtt: Duration, cwnd: usize) -> Instant { - if self.c >= self.p { + let packet = isize::try_from(self.p).expect("packet size fits into isize"); + + if self.c >= packet { qtrace!("[{self}] next {cwnd}/{rtt:?} no wait = {:?}", self.t); return self.t; } @@ -83,7 +87,9 @@ impl Pacer { // This is the inverse of the function in `spend`: // self.t + rtt * (self.p - self.c) / (PACER_SPEEDUP * cwnd) let r = rtt.as_nanos(); - let d = r.saturating_mul(u128::try_from(self.p - self.c).expect("usize fits into u128")); + let deficit = + u128::try_from(packet - self.c).expect("packet is larger than current credit"); + let d = r.saturating_mul(deficit); let add = d / u128::try_from(cwnd * PACER_SPEEDUP).expect("usize fits into u128"); let w = u64::try_from(add).map(Duration::from_nanos).unwrap_or(rtt); @@ -98,10 +104,13 @@ impl Pacer { nxt } - /// Spend credit. This cannot fail; users of this API are expected to call - /// `next()` to determine when to spend. This takes the current time (`now`), - /// an estimate of the round trip time (`rtt`), the estimated congestion - /// window (`cwnd`), and the number of bytes that were sent (`count`). + /// Spend credit. This cannot fail, but instead may carry debt into the + /// future (see [`Pacer::c`]). Users of this API are expected to call + /// [`Pacer::next`] to determine when to spend. + /// + /// This function takes the current time (`now`), an estimate of the round + /// trip time (`rtt`), the estimated congestion window (`cwnd`), and the + /// number of bytes that were sent (`count`). pub fn spend(&mut self, now: Instant, rtt: Duration, cwnd: usize, count: usize) { if !self.enabled { self.t = now; @@ -121,7 +130,12 @@ impl Pacer { .unwrap_or(self.m); // Add the capacity up to a limit of `self.m`, then subtract `count`. - self.c = min(self.m, (self.c + incr).saturating_sub(count)); + self.c = min( + isize::try_from(self.m).unwrap_or(isize::MAX), + self.c + .saturating_add(isize::try_from(incr).unwrap_or(isize::MAX)) + .saturating_sub(isize::try_from(count).unwrap_or(isize::MAX)), + ); self.t = now; } } @@ -192,4 +206,25 @@ mod tests { "Expect packet to be sent immediately, instead of being paced below timer granularity" ); } + + #[test] + fn sends_below_granularity_accumulate_eventually() { + const RTT: Duration = Duration::from_millis(100); + const BW: usize = 50 * 1_000_000; + let bdp = usize::try_from( + u128::try_from(BW / 8).expect("usize fits in u128") * RTT.as_nanos() + / Duration::from_secs(1).as_nanos(), + ) + .expect("cwnd fits in usize"); + let mut n = now(); + let mut p = Pacer::new(true, n, 2 * PACKET, PACKET); + let start = n; + let packet_count = 10_000; + for _ in 0..packet_count { + n = p.next(RTT, bdp); + p.spend(n, RTT, bdp, PACKET); + } + // We expect _some_ time to have progressed after sending all the packets. + assert!(n - start > Duration::ZERO); + } } diff --git a/third_party/rust/neqo-transport/src/packet/mod.rs b/third_party/rust/neqo-transport/src/packet/mod.rs @@ -19,7 +19,7 @@ use neqo_crypto::{random, AeadTrait as _}; use strum::{EnumIter, FromRepr}; use crate::{ - cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef, MAX_CONNECTION_ID_LEN}, + cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef}, crypto::{CryptoDxState, CryptoStates, Epoch}, frame::FrameType, version::{self, Version}, @@ -30,14 +30,14 @@ use crate::{ /// a new connection across all QUIC versions this server supports. pub const MIN_INITIAL_PACKET_SIZE: usize = 1200; -pub const PACKET_BIT_LONG: u8 = 0x80; -const PACKET_BIT_SHORT: u8 = 0x00; -const PACKET_BIT_FIXED_QUIC: u8 = 0x40; -const PACKET_BIT_SPIN: u8 = 0x20; -const PACKET_BIT_KEY_PHASE: u8 = 0x04; +pub const BIT_LONG: u8 = 0x80; +const BIT_SHORT: u8 = 0x00; +const BIT_FIXED_QUIC: u8 = 0x40; +const BIT_SPIN: u8 = 0x20; +const BIT_KEY_PHASE: u8 = 0x04; -const PACKET_HP_MASK_LONG: u8 = 0x0f; -const PACKET_HP_MASK_SHORT: u8 = 0x1f; +const HP_MASK_LONG: u8 = 0x0f; +const HP_MASK_SHORT: u8 = 0x1f; const SAMPLE_SIZE: usize = 16; const SAMPLE_OFFSET: usize = 4; @@ -74,16 +74,18 @@ impl Type { #[must_use] fn to_byte(self, v: Version) -> u8 { - assert!( - matches!( - self, - Self::Initial | Self::ZeroRtt | Self::Handshake | Self::Retry - ), - "is a long header packet type" - ); + assert!(self.is_long(), "is a long header packet type"); // Version2 adds one to the type, modulo 4 (self as u8 + u8::from(v == Version::Version2)) & 3 } + + #[must_use] + pub const fn is_long(self) -> bool { + matches!( + self, + Self::Initial | Self::ZeroRtt | Self::Handshake | Self::Retry + ) + } } impl TryFrom<Type> for Epoch { @@ -155,8 +157,8 @@ impl Builder<Vec<u8>> { encoder.encode_vec(1, odcid); let start = encoder.len(); encoder.encode_byte( - PACKET_BIT_LONG - | PACKET_BIT_FIXED_QUIC + BIT_LONG + | BIT_FIXED_QUIC | (Type::Retry.to_byte(version) << 4) | (random::<1>()[0] & 0xf), ); @@ -185,8 +187,8 @@ impl Builder<Vec<u8>> { let mut encoder = Encoder::default(); let mut grease = random::<4>(); // This will not include the "QUIC bit" sometimes. Intentionally. - encoder.encode_byte(PACKET_BIT_LONG | (grease[3] & 0x7f)); - encoder.encode(&[0; 4]); // Zero version == VN. + encoder.encode_byte(BIT_LONG | (grease[3] & 0x7f)); + encoder.encode([0; 4]); // Zero version == VN. encoder.encode_vec(1, dcid); encoder.encode_vec(1, scid); @@ -231,8 +233,7 @@ impl<B: Buffer> Builder<B> { if limit > encoder.len() && 5 + dcid.as_ref().map_or(0, |d| d.as_ref().len()) < limit - encoder.len() { - encoder - .encode_byte(PACKET_BIT_SHORT | PACKET_BIT_FIXED_QUIC | (u8::from(key_phase) << 2)); + encoder.encode_byte(BIT_SHORT | BIT_FIXED_QUIC | (u8::from(key_phase) << 2)); if let Some(dcid) = dcid { encoder.encode(dcid.as_ref()); } @@ -244,7 +245,7 @@ impl<B: Buffer> Builder<B> { pn: u64::MAX, header: header_start..header_start, offsets: BuilderOffsets { - first_byte_mask: PACKET_HP_MASK_SHORT, + first_byte_mask: HP_MASK_SHORT, pn: 0..0, len: 0, }, @@ -277,8 +278,7 @@ impl<B: Buffer> Builder<B> { + scid.as_ref().map_or(0, |d| d.as_ref().len()) < limit - encoder.len() { - encoder - .encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC | (pt.to_byte(version) << 4)); + encoder.encode_byte(BIT_LONG | BIT_FIXED_QUIC | (pt.to_byte(version) << 4)); encoder.encode_uint(4, version.wire_version()); encoder.encode_vec(1, dcid.take().as_ref().map_or(&[], AsRef::as_ref)); encoder.encode_vec(1, scid.take().as_ref().map_or(&[], AsRef::as_ref)); @@ -291,7 +291,7 @@ impl<B: Buffer> Builder<B> { pn: u64::MAX, header: header_start..header_start, offsets: BuilderOffsets { - first_byte_mask: PACKET_HP_MASK_LONG, + first_byte_mask: HP_MASK_LONG, pn: 0..0, len: 0, }, @@ -301,7 +301,7 @@ impl<B: Buffer> Builder<B> { } fn is_long(&self) -> bool { - self.as_ref()[self.header.start] & 0x80 == PACKET_BIT_LONG + self.as_ref()[self.header.start] & 0x80 == BIT_LONG } /// This stores a value that can be used as a limit. This does not cause @@ -359,8 +359,8 @@ impl<B: Buffer> Builder<B> { /// Add unpredictable values for unprotected parts of the packet. pub fn scramble(&mut self, quic_bit: bool) { debug_assert!(self.len() > self.header.start); - let mask = if quic_bit { PACKET_BIT_FIXED_QUIC } else { 0 } - | if self.is_long() { 0 } else { PACKET_BIT_SPIN }; + let mask = + if quic_bit { BIT_FIXED_QUIC } else { 0 } | if self.is_long() { 0 } else { BIT_SPIN }; let first = self.header.start; self.encoder.as_mut()[first] ^= random::<1>()[0] & mask; } @@ -398,7 +398,7 @@ impl<B: Buffer> Builder<B> { } self.offsets.len = self.encoder.len(); - self.encoder.encode(&[0; LONG_PACKET_LENGTH_LEN]); + self.encoder.encode([0; LONG_PACKET_LENGTH_LEN]); } // This allows the input to be >4, which is absurd, but we can eat that. @@ -625,7 +625,7 @@ impl<'a> Public<'a> { let mut decoder = Decoder::new(data); let first = Self::opt(decoder.decode_uint::<u8>())?; - if first & 0x80 == PACKET_BIT_SHORT { + if first & 0x80 == BIT_SHORT { // Conveniently, this also guarantees that there is enough space // for a connection ID of any size. if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE { @@ -688,7 +688,7 @@ impl<'a> Public<'a> { )); }; - if dcid.len() > MAX_CONNECTION_ID_LEN || scid.len() > MAX_CONNECTION_ID_LEN { + if dcid.len() > ConnectionId::MAX_LEN || scid.len() > ConnectionId::MAX_LEN { return Err(Error::InvalidPacket); } let packet_type = Type::from_byte((first >> 4) & 3, version); @@ -830,9 +830,9 @@ impl<'a> Public<'a> { // Un-mask the leading byte. let bits = if self.packet_type == Type::Short { - PACKET_HP_MASK_SHORT + HP_MASK_SHORT } else { - PACKET_HP_MASK_LONG + HP_MASK_LONG }; let first_byte = self.data[0] ^ (mask[0] & bits); @@ -856,8 +856,8 @@ impl<'a> Public<'a> { qtrace!("unmasked hdr={}", hex(&self.data[hdrbytes.clone()])); - let key_phase = self.packet_type == Type::Short - && (first_byte & PACKET_BIT_KEY_PHASE) == PACKET_BIT_KEY_PHASE; + let key_phase = + self.packet_type == Type::Short && (first_byte & BIT_KEY_PHASE) == BIT_KEY_PHASE; let pn = Self::decode_pn(crypto.next_pn(), pn_encoded, pn_len); Ok((key_phase, pn, hdrbytes)) } @@ -970,7 +970,7 @@ impl Deref for Decrypted<'_> { } #[cfg(test)] -pub const PACKET_LIMIT: usize = 2048; +pub const LIMIT: usize = 2048; #[cfg(all(test, not(feature = "disable-encryption")))] #[cfg(test)] @@ -980,12 +980,8 @@ mod tests { use test_fixture::{fixture_init, now}; use crate::{ - cid::MAX_CONNECTION_ID_LEN, crypto::{CryptoDxState, CryptoStates}, - packet::{ - Builder, Public, Type, PACKET_BIT_FIXED_QUIC, PACKET_BIT_LONG, PACKET_BIT_SPIN, - PACKET_LIMIT, - }, + packet::{self, Builder, Public, Type}, ConnectionId, EmptyConnectionIdGenerator, Error, RandomConnectionIdGenerator, Version, }; @@ -1035,7 +1031,7 @@ mod tests { Version::default(), None::<&[u8]>, Some(ConnectionId::from(SERVER_CID)), - PACKET_LIMIT, + packet::LIMIT, ); builder.initial_token(&[]); builder.pn(1, 2); @@ -1067,11 +1063,11 @@ mod tests { #[test] fn disallow_long_dcid() { let mut enc = Encoder::new(); - enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC); + enc.encode_byte(packet::BIT_LONG | packet::BIT_FIXED_QUIC); enc.encode_uint(4, Version::default().wire_version()); - enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 1]); + enc.encode_vec(1, &[0x00; ConnectionId::MAX_LEN + 1]); enc.encode_vec(1, &[]); - enc.encode(&[0xff; 40]); // junk + enc.encode([0xff; 40]); // junk assert!(Public::decode(enc.as_mut(), &cid_mgr()).is_err()); } @@ -1079,11 +1075,11 @@ mod tests { #[test] fn disallow_long_scid() { let mut enc = Encoder::new(); - enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC); + enc.encode_byte(packet::BIT_LONG | packet::BIT_FIXED_QUIC); enc.encode_uint(4, Version::default().wire_version()); enc.encode_vec(1, &[]); - enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 2]); - enc.encode(&[0xff; 40]); // junk + enc.encode_vec(1, &[0x00; ConnectionId::MAX_LEN + 2]); + enc.encode([0xff; 40]); // junk assert!(Public::decode(enc.as_mut(), &cid_mgr()).is_err()); } @@ -1101,7 +1097,7 @@ mod tests { Encoder::new(), true, Some(ConnectionId::from(SERVER_CID)), - PACKET_LIMIT, + packet::LIMIT, ); builder.pn(0, 1); builder.encode(SAMPLE_SHORT_PAYLOAD); // Enough payload for sampling. @@ -1120,7 +1116,7 @@ mod tests { Encoder::new(), true, Some(ConnectionId::from(SERVER_CID)), - PACKET_LIMIT, + packet::LIMIT, ); builder.scramble(true); builder.pn(0, 1); @@ -1128,13 +1124,13 @@ mod tests { } let is_set = |bit| move |v| v & bit == bit; // There should be at least one value with the QUIC bit set: - assert!(firsts.iter().any(is_set(PACKET_BIT_FIXED_QUIC))); + assert!(firsts.iter().any(is_set(packet::BIT_FIXED_QUIC))); // ... but not all: - assert!(!firsts.iter().all(is_set(PACKET_BIT_FIXED_QUIC))); + assert!(!firsts.iter().all(is_set(packet::BIT_FIXED_QUIC))); // There should be at least one value with the spin bit set: - assert!(firsts.iter().any(is_set(PACKET_BIT_SPIN))); + assert!(firsts.iter().any(is_set(packet::BIT_SPIN))); // ... but not all: - assert!(!firsts.iter().all(is_set(PACKET_BIT_SPIN))); + assert!(!firsts.iter().all(is_set(packet::BIT_SPIN))); } #[test] @@ -1189,10 +1185,10 @@ mod tests { Version::default(), Some(ConnectionId::from(SERVER_CID)), Some(ConnectionId::from(CLIENT_CID)), - PACKET_LIMIT, + packet::LIMIT, ); builder.pn(0, 1); - builder.encode(&[0; 3]); + builder.encode([0; 3]); let encoder = builder.build(&mut prot).expect("build"); assert_eq!(encoder.len(), 45); let first = encoder.clone(); @@ -1201,10 +1197,10 @@ mod tests { encoder, false, Some(ConnectionId::from(SERVER_CID)), - PACKET_LIMIT, + packet::LIMIT, ); builder.pn(1, 3); - builder.encode(&[0]); // Minimal size (packet number is big enough). + builder.encode([0]); // Minimal size (packet number is big enough). let encoder = builder.build(&mut prot).expect("build"); assert_eq!( first.as_ref(), @@ -1229,10 +1225,10 @@ mod tests { Version::default(), None::<&[u8]>, None::<&[u8]>, - PACKET_LIMIT, + packet::LIMIT, ); builder.pn(0, 1); - builder.encode(&[1, 2, 3]); + builder.encode([1, 2, 3]); let packet = builder.build(&mut CryptoDxState::test_default()).unwrap(); assert_eq!(packet.as_ref(), EXPECTED); } @@ -1249,11 +1245,11 @@ mod tests { Version::default(), None::<&[u8]>, None::<&[u8]>, - PACKET_LIMIT, + packet::LIMIT, ); builder.pn(0, 1); builder.scramble(true); - if (builder.as_ref()[0] & PACKET_BIT_FIXED_QUIC) == 0 { + if (builder.as_ref()[0] & packet::BIT_FIXED_QUIC) == 0 { found_unset = true; } else { found_set = true; @@ -1271,7 +1267,7 @@ mod tests { Version::default(), None::<&[u8]>, Some(ConnectionId::from(SERVER_CID)), - PACKET_LIMIT, + packet::LIMIT, ); assert_ne!(builder.remaining(), 0); builder.initial_token(&[]); @@ -1562,7 +1558,7 @@ mod tests { /// A Version Negotiation packet can have a long connection ID. #[test] fn parse_vn_big_cid() { - const BIG_DCID: &[u8] = &[0x44; MAX_CONNECTION_ID_LEN + 1]; + const BIG_DCID: &[u8] = &[0x44; ConnectionId::MAX_LEN + 1]; const BIG_SCID: &[u8] = &[0xee; 255]; let mut enc = Encoder::from(&[0xff, 0x00, 0x00, 0x00, 0x00][..]); diff --git a/third_party/rust/neqo-transport/src/path.rs b/third_party/rust/neqo-transport/src/path.rs @@ -28,6 +28,7 @@ use crate::{ recovery::{self, sent}, rtt::{RttEstimate, RttSource}, sender::PacketSender, + stateless_reset::Token as Srt, stats::FrameStats, ConnectionParameters, Stats, }; @@ -330,7 +331,7 @@ impl Paths { /// Keep active paths if possible by pulling new connection IDs from the provided store. /// One slightly non-obvious consequence of this is that if migration is being attempted /// and the new path cannot obtain a new connection ID, the migration attempt will fail. - pub fn retire_cids(&mut self, retire_prior: u64, store: &mut ConnectionIdStore<[u8; 16]>) { + pub fn retire_cids(&mut self, retire_prior: u64, store: &mut ConnectionIdStore<Srt>) { let to_retire = &mut self.to_retire; let migration_target = &mut self.migration_target; @@ -682,14 +683,14 @@ impl Path { } /// Set the stateless reset token for the connection ID that is currently in use. - pub fn set_reset_token(&mut self, token: [u8; 16]) { + pub fn set_reset_token(&mut self, token: Srt) { if let Some(remote_cid) = self.remote_cid.as_mut() { remote_cid.set_stateless_reset_token(token); } } /// Determine if the provided token is a stateless reset token. - pub fn is_stateless_reset(&self, token: &[u8; 16]) -> bool { + pub fn is_stateless_reset(&self, token: &Srt) -> bool { self.remote_cid .as_ref() .is_some_and(|rcid| rcid.is_stateless_reset(token)) @@ -819,7 +820,7 @@ impl Path { qtrace!("[{self}] Initiating path challenge {probe_count}"); let data = random::<8>(); builder.encode_varint(FrameType::PathChallenge); - builder.encode(&data); + builder.encode(data); // As above, no recovery token. stats.path_challenge += 1; diff --git a/third_party/rust/neqo-transport/src/quic_datagrams.rs b/third_party/rust/neqo-transport/src/quic_datagrams.rs @@ -169,7 +169,7 @@ impl QuicDatagrams { return Err(Error::TooMuchData); } if self.datagrams.len() == self.max_queued_outgoing_datagrams { - qdebug!("QUIC datagram queue full, dropping first datagram in queue."); + qdebug!("QUIC datagram queue full, dropping first datagram in queue (head-drop)."); self.conn_events.datagram_outcome( self.datagrams .pop_front() diff --git a/third_party/rust/neqo-transport/src/recovery/mod.rs b/third_party/rust/neqo-transport/src/recovery/mod.rs @@ -134,6 +134,9 @@ pub struct LossRecoverySpace { /// The time used to calculate the PTO timer for this space. /// This is the time that the last ACK-eliciting packet in this space /// was sent. This might be the time that a probe was sent. + /// For Initial and Handshake spaces, this may also be set when we haven't + /// sent any packets yet but need a PTO baseline (see `on_packet_sent` and + /// `on_packets_acked` for how this is established). last_ack_eliciting: Option<Instant>, /// The number of outstanding packets in this space that are in flight. /// This might be less than the number of ACK-eliciting packets, @@ -196,12 +199,20 @@ impl LossRecoverySpace { // of the handshake. Technically, this has to stop once we receive // an ACK of Handshake or 1-RTT, or when we receive HANDSHAKE_DONE, // but a few extra probes won't hurt. - // It only means that we fail anti-amplification tests. - // A server shouldn't arm its PTO timer this way. The server sends - // ack-eliciting, in-flight packets immediately so this only - // happens when the server has nothing outstanding. If we had - // client authentication, this might cause some extra probes, - // but they would be harmless anyway. + // + // RFC 9002 Section 6.2.4 requires sending probes in packet number spaces + // with in-flight data. When we have keys for a space but haven't sent + // anything ack-eliciting yet (e.g., waiting for peer's Handshake flight), + // we still need to arm the PTO timer to probe and elicit retransmission. + // + // If no ack-eliciting packets have been sent in this space yet, + // last_ack_eliciting may be set as a PTO baseline in two ways: + // 1. When we send ANY packet in Initial/Handshake (see on_packet_sent) + // 2. When we receive ACKs in Initial and prime Handshake (see on_packets_acked) + // + // This ensures the PTO timer arms when we have keys for a space but + // nothing to send yet, allowing us to probe and elicit peer retransmission. + // RFC 9002 Section 6.2.4 requires probing packet number spaces. self.last_ack_eliciting } } @@ -570,6 +581,40 @@ impl Loss { self.confirmed_time.is_some() } + /// Prime the Handshake space PTO timer when stuck in Initial space. + fn maybe_prime_handshake_pto(&mut self, now: Instant) { + // Only prime if we're in Initial space. + let Some(pto) = self + .pto_state + .as_ref() + .filter(|pto| pto.space == PacketNumberSpace::Initial) + else { + return; + }; + + // Only prime if we've received Initial ACKs (proving the peer is alive). + if !self + .spaces + .get(PacketNumberSpace::Initial) + .is_some_and(|space| space.largest_acked.is_some()) + { + return; + } + + let Some(hs_space) = self.spaces.get_mut(PacketNumberSpace::Handshake) else { + return; + }; + + // Only prime if we haven't sent or received anything in Handshake space yet. + if hs_space.last_ack_eliciting.is_none() && hs_space.largest_acked.is_none() { + qtrace!( + "Priming Handshake PTO baseline (no HS packets after {} Initial PTOs)", + pto.count() + ); + hs_space.last_ack_eliciting = Some(now); + } + } + /// Returns (acked packets, lost packets) pub fn on_ack_received<R>( &mut self, @@ -863,6 +908,11 @@ impl Loss { if let Some(pn_space) = pto_space { qtrace!("[{self}] PTO {pn_space}, probing {allow_probes:?}"); self.fire_pto(pn_space, allow_probes, now); + + // Maybe prime the Handshake PTO when PTO fires in Initial space. + if pn_space == PacketNumberSpace::Initial { + self.maybe_prime_handshake_pto(now); + } } } @@ -958,14 +1008,15 @@ mod tests { use neqo_common::qlog::Qlog; use test_fixture::{now, DEFAULT_ADDR}; - use super::{LossRecoverySpace, PacketNumberSpace, SendProfile, FAST_PTO_SCALE}; + use super::{LossRecoverySpace, PacketNumberSpace, PtoState, SendProfile, FAST_PTO_SCALE}; use crate::{ cid::{ConnectionId, ConnectionIdEntry}, ecn, packet, path::{Path, PathRef}, recovery::{self, sent, MAX_PTO_PACKET_COUNT}, stats::{Stats, StatsCell}, - ConnectionParameters, + tracking::PacketNumberSpaceSet, + ConnectionParameters, Token as Srt, }; // Shorthand for a time in milliseconds. @@ -1036,7 +1087,7 @@ mod tests { ); path.make_permanent( None, - ConnectionIdEntry::new(0, ConnectionId::from(&[1, 2, 3]), [0; 16]), + ConnectionIdEntry::new(0, ConnectionId::from(&[1, 2, 3]), Srt::default()), ); path.set_primary(true, now()); path.rtt_mut().set_initial(TEST_RTT); @@ -1731,4 +1782,53 @@ mod tests { let profile = lr.send_profile(now); assert!(profile.pto.is_none()); } + + fn assert_no_handshake_last_ack_eliciting(lr: &Fixture) { + assert!(lr + .spaces + .get(PacketNumberSpace::Handshake) + .and_then(|s| s.last_ack_eliciting) + .is_none()); + } + + #[test] + fn maybe_prime_handshake_pto_no_pto_state() { + let mut lr = Fixture::default(); + assert!(lr.pto_state.is_none()); + + // Verify nothing changes - the Handshake space should not be primed afterwards. + lr.maybe_prime_handshake_pto(now()); + assert_no_handshake_last_ack_eliciting(&lr); + } + + #[test] + fn maybe_prime_handshake_pto_wrong_space() { + // Create a PTO state in Handshake space. + let mut lr = Fixture::default(); + let probe_set = PacketNumberSpaceSet::only(PacketNumberSpace::Handshake); + lr.pto_state = Some(PtoState::new(PacketNumberSpace::Handshake, probe_set)); + + // Verify nothing changes - the Handshake space should not be primed afterwards. + lr.maybe_prime_handshake_pto(now()); + assert_no_handshake_last_ack_eliciting(&lr); + } + + #[test] + fn maybe_prime_handshake_pto_no_handshake_space() { + // Create a PTO state in Initial space. + let mut lr = Fixture::default(); + let probe_set = PacketNumberSpaceSet::only(PacketNumberSpace::Initial); + lr.pto_state = Some(PtoState::new(PacketNumberSpace::Initial, probe_set)); + + // Set up Initial space with an ACK and drop Handshake space. + lr.spaces + .get_mut(PacketNumberSpace::Initial) + .unwrap() + .largest_acked = Some(0); + lr.spaces.drop_space(PacketNumberSpace::Handshake); + + // Verify Handshake space still doesn't exist afterwards. + lr.maybe_prime_handshake_pto(now()); + assert!(lr.spaces.get(PacketNumberSpace::Handshake).is_none()); + } } diff --git a/third_party/rust/neqo-transport/src/recovery/token.rs b/third_party/rust/neqo-transport/src/recovery/token.rs @@ -10,6 +10,7 @@ use crate::{ crypto::CryptoRecoveryToken, quic_datagrams::DatagramTracking, send_stream, + stateless_reset::Token as Srt, stream_id::{StreamId, StreamType}, tracking::AckToken, }; @@ -60,7 +61,7 @@ pub enum Token { reason = "This is how it is called in the spec." )] NewToken(usize), - NewConnectionId(ConnectionIdEntry<[u8; 16]>), + NewConnectionId(ConnectionIdEntry<Srt>), RetireConnectionId(u64), AckFrequency(AckRate), Datagram(DatagramTracking), diff --git a/third_party/rust/neqo-transport/src/recv_stream.rs b/third_party/rust/neqo-transport/src/recv_stream.rs @@ -33,21 +33,6 @@ use crate::{ AppError, Error, Res, }; -pub const INITIAL_RECV_WINDOW_SIZE: usize = 1024 * 1024; - -/// Limit for the maximum amount of bytes active on a single stream, i.e. limit -/// for the size of the stream receive window. -/// -/// A value of 10 MiB allows for: -/// -/// - 10ms rtt and 8.3 GBit/s -/// - 20ms rtt and 4.2 GBit/s -/// - 40ms rtt and 2.1 GBit/s -/// - 100ms rtt and 0.8 GBit/s -/// -/// Keep in sync with [`crate::send_stream::MAX_SEND_BUFFER_SIZE`]. -pub const MAX_RECV_WINDOW_SIZE: u64 = 10 * 1024 * 1024; - #[derive(Debug, Default)] pub struct RecvStreams { streams: BTreeMap<StreamId, RecvStream>, @@ -1005,11 +990,10 @@ mod tests { use super::RecvStream; use crate::{ fc::{ReceiverFlowControl, WINDOW_UPDATE_FRACTION}, - packet::{self, PACKET_LIMIT}, - recovery, + packet, recovery, recv_stream::RxStreamOrderer, stats::FrameStats, - ConnectionEvents, Error, StreamId, INITIAL_RECV_WINDOW_SIZE, + ConnectionEvents, Error, StreamId, INITIAL_LOCAL_MAX_STREAM_DATA, }; const SESSION_WINDOW: usize = 1024; @@ -1459,14 +1443,17 @@ mod tests { #[test] fn stream_flowc_update() { - let mut s = create_stream(1024 * INITIAL_RECV_WINDOW_SIZE as u64); - let mut buf = vec![0u8; INITIAL_RECV_WINDOW_SIZE + 100]; // Make it overlarge + let mut s = create_stream(1024 * INITIAL_LOCAL_MAX_STREAM_DATA as u64); + let mut buf = vec![0u8; INITIAL_LOCAL_MAX_STREAM_DATA + 100]; // Make it overlarge assert!(!s.has_frames_to_write()); - let big_buf = vec![0; INITIAL_RECV_WINDOW_SIZE]; + let big_buf = vec![0; INITIAL_LOCAL_MAX_STREAM_DATA]; s.inbound_stream_frame(false, 0, &big_buf).unwrap(); assert!(!s.has_frames_to_write()); - assert_eq!(s.read(&mut buf).unwrap(), (INITIAL_RECV_WINDOW_SIZE, false)); + assert_eq!( + s.read(&mut buf).unwrap(), + (INITIAL_LOCAL_MAX_STREAM_DATA, false) + ); assert!(!s.data_ready()); // flow msg generated! @@ -1474,7 +1461,7 @@ mod tests { // consume it let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut token = recovery::Tokens::new(); s.write_frame( &mut builder, @@ -1492,7 +1479,7 @@ mod tests { let conn_events = ConnectionEvents::default(); RecvStream::new( StreamId::from(67), - INITIAL_RECV_WINDOW_SIZE as u64, + INITIAL_LOCAL_MAX_STREAM_DATA as u64, Rc::new(RefCell::new(ReceiverFlowControl::new((), session_fc))), conn_events, ) @@ -1500,11 +1487,11 @@ mod tests { #[test] fn stream_max_stream_data() { - let mut s = create_stream(1024 * INITIAL_RECV_WINDOW_SIZE as u64); + let mut s = create_stream(1024 * INITIAL_LOCAL_MAX_STREAM_DATA as u64); assert!(!s.has_frames_to_write()); - let big_buf = vec![0; INITIAL_RECV_WINDOW_SIZE]; + let big_buf = vec![0; INITIAL_LOCAL_MAX_STREAM_DATA]; s.inbound_stream_frame(false, 0, &big_buf).unwrap(); - s.inbound_stream_frame(false, INITIAL_RECV_WINDOW_SIZE as u64, &[1; 1]) + s.inbound_stream_frame(false, INITIAL_LOCAL_MAX_STREAM_DATA as u64, &[1; 1]) .unwrap_err(); } @@ -1545,14 +1532,14 @@ mod tests { #[test] fn no_stream_flowc_event_after_exiting_recv() { - let mut s = create_stream(1024 * INITIAL_RECV_WINDOW_SIZE as u64); - let mut buf = vec![0; INITIAL_RECV_WINDOW_SIZE]; + let mut s = create_stream(1024 * INITIAL_LOCAL_MAX_STREAM_DATA as u64); + let mut buf = vec![0; INITIAL_LOCAL_MAX_STREAM_DATA]; // Write from buf at first. s.inbound_stream_frame(false, 0, &buf).unwrap(); // Then read into it. s.read(&mut buf).unwrap(); assert!(s.has_frames_to_write()); - s.inbound_stream_frame(true, INITIAL_RECV_WINDOW_SIZE as u64, &[]) + s.inbound_stream_frame(true, INITIAL_LOCAL_MAX_STREAM_DATA as u64, &[]) .unwrap(); assert!(!s.has_frames_to_write()); } @@ -1570,13 +1557,13 @@ mod tests { } fn create_stream_session_flow_control() -> (RecvStream, Rc<RefCell<ReceiverFlowControl<()>>>) { - static_assertions::const_assert!(INITIAL_RECV_WINDOW_SIZE > SESSION_WINDOW); + static_assertions::const_assert!(INITIAL_LOCAL_MAX_STREAM_DATA > SESSION_WINDOW); let session_fc = Rc::new(RefCell::new(ReceiverFlowControl::new( (), u64::try_from(SESSION_WINDOW).unwrap(), ))); ( - create_stream_with_fc(Rc::clone(&session_fc), INITIAL_RECV_WINDOW_SIZE as u64), + create_stream_with_fc(Rc::clone(&session_fc), INITIAL_LOCAL_MAX_STREAM_DATA as u64), session_fc, ) } @@ -1595,11 +1582,15 @@ mod tests { assert!(session_fc.borrow().frame_needed()); // consume it let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut token = recovery::Tokens::new(); - session_fc - .borrow_mut() - .write_frames(&mut builder, &mut token, &mut FrameStats::default()); + session_fc.borrow_mut().write_frames( + &mut builder, + &mut token, + &mut FrameStats::default(), + Instant::now(), + Duration::from_millis(100), + ); // Switch to SizeKnown state s.inbound_stream_frame(true, 2 * u64::try_from(SESSION_WINDOW).unwrap() - 1, &[0]) @@ -1617,11 +1608,15 @@ mod tests { assert!(session_fc.borrow().frame_needed()); // consume it let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut token = recovery::Tokens::new(); - session_fc - .borrow_mut() - .write_frames(&mut builder, &mut token, &mut FrameStats::default()); + session_fc.borrow_mut().write_frames( + &mut builder, + &mut token, + &mut FrameStats::default(), + Instant::now(), + Duration::from_millis(100), + ); // Test DataRecvd state let session_fc = Rc::new(RefCell::new(ReceiverFlowControl::new( @@ -1630,7 +1625,7 @@ mod tests { ))); let mut s = RecvStream::new( StreamId::from(567), - INITIAL_RECV_WINDOW_SIZE as u64, + INITIAL_LOCAL_MAX_STREAM_DATA as u64, Rc::clone(&session_fc), ConnectionEvents::default(), ); @@ -1922,11 +1917,16 @@ mod tests { // Write the fc update frame let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut token = recovery::Tokens::new(); let mut stats = FrameStats::default(); - fc.borrow_mut() - .write_frames(&mut builder, &mut token, &mut stats); + fc.borrow_mut().write_frames( + &mut builder, + &mut token, + &mut stats, + Instant::now(), + Duration::from_millis(100), + ); assert_eq!(stats.max_data, 0); s.write_frame( &mut builder, @@ -1953,8 +1953,13 @@ mod tests { ); assert!(fc.borrow().frame_needed()); assert!(!s.fc().unwrap().frame_needed()); - fc.borrow_mut() - .write_frames(&mut builder, &mut token, &mut stats); + fc.borrow_mut().write_frames( + &mut builder, + &mut token, + &mut stats, + Instant::now(), + Duration::from_millis(100), + ); assert_eq!(stats.max_data, 1); s.write_frame( &mut builder, diff --git a/third_party/rust/neqo-transport/src/send_stream.rs b/third_party/rust/neqo-transport/src/send_stream.rs @@ -39,10 +39,10 @@ use crate::{ /// The maximum stream send buffer size. /// -/// See [`crate::recv_stream::MAX_RECV_WINDOW_SIZE`] for an explanation of this +/// See [`crate::MAX_LOCAL_MAX_STREAM_DATA`] for an explanation of this /// concrete value. /// -/// Keep in sync with [`crate::recv_stream::MAX_RECV_WINDOW_SIZE`]. +/// Keep in sync with [`crate::MAX_LOCAL_MAX_STREAM_DATA`]. pub const MAX_SEND_BUFFER_SIZE: usize = 10 * 1024 * 1024; /// The priority that is assigned to sending data for the stream. @@ -1785,14 +1785,14 @@ mod tests { connection::{RetransmissionPriority, TransmissionPriority}, events::ConnectionEvent, fc::SenderFlowControl, - packet::{self, PACKET_LIMIT}, + packet, recovery::{self, StreamRecoveryToken}, send_stream::{ RangeState, RangeTracker, SendStream, SendStreams, State, TxBuffer, MAX_SEND_BUFFER_SIZE, }, stats::FrameStats, - ConnectionEvents, StreamId, INITIAL_RECV_WINDOW_SIZE, + ConnectionEvents, StreamId, INITIAL_LOCAL_MAX_STREAM_DATA, }; fn connection_fc(limit: u64) -> Rc<RefCell<SenderFlowControl<()>>> { @@ -2260,14 +2260,14 @@ mod tests { let mut txb = TxBuffer::new(); // Fill the buffer - let big_buf = vec![1; INITIAL_RECV_WINDOW_SIZE]; - assert_eq!(txb.send(&big_buf), INITIAL_RECV_WINDOW_SIZE); + let big_buf = vec![1; INITIAL_LOCAL_MAX_STREAM_DATA]; + assert_eq!(txb.send(&big_buf), INITIAL_LOCAL_MAX_STREAM_DATA); assert!(matches!(txb.next_bytes(), - Some((0, x)) if x.len() == INITIAL_RECV_WINDOW_SIZE + Some((0, x)) if x.len() == INITIAL_LOCAL_MAX_STREAM_DATA && x.iter().all(|ch| *ch == 1))); // Mark almost all as sent. Get what's left - let one_byte_from_end = INITIAL_RECV_WINDOW_SIZE as u64 - 1; + let one_byte_from_end = INITIAL_LOCAL_MAX_STREAM_DATA as u64 - 1; txb.mark_as_sent(0, usize::try_from(one_byte_from_end).unwrap()); assert!(matches!(txb.next_bytes(), Some((start, x)) if x.len() == 1 @@ -2275,7 +2275,7 @@ mod tests { && x.iter().all(|ch| *ch == 1))); // Mark all as sent. Get nothing - txb.mark_as_sent(0, INITIAL_RECV_WINDOW_SIZE); + txb.mark_as_sent(0, INITIAL_LOCAL_MAX_STREAM_DATA); assert!(txb.next_bytes().is_none()); // Mark as lost. Get it again @@ -2287,7 +2287,7 @@ mod tests { // Mark a larger range lost, including beyond what's in the buffer even. // Get a little more - let five_bytes_from_end = INITIAL_RECV_WINDOW_SIZE as u64 - 5; + let five_bytes_from_end = INITIAL_LOCAL_MAX_STREAM_DATA as u64 - 5; txb.mark_as_lost(five_bytes_from_end, 100); assert!(matches!(txb.next_bytes(), Some((start, x)) if x.len() == 5 @@ -2312,7 +2312,7 @@ mod tests { txb.mark_as_sent(five_bytes_from_end, 5); assert!(matches!(txb.next_bytes(), Some((start, x)) if x.len() == 30 - && start == INITIAL_RECV_WINDOW_SIZE as u64 + && start == INITIAL_LOCAL_MAX_STREAM_DATA as u64 && x.iter().all(|ch| *ch == 2))); } @@ -2321,14 +2321,14 @@ mod tests { let mut txb = TxBuffer::new(); // Fill the buffer - let big_buf = vec![1; INITIAL_RECV_WINDOW_SIZE]; - assert_eq!(txb.send(&big_buf), INITIAL_RECV_WINDOW_SIZE); + let big_buf = vec![1; INITIAL_LOCAL_MAX_STREAM_DATA]; + assert_eq!(txb.send(&big_buf), INITIAL_LOCAL_MAX_STREAM_DATA); assert!(matches!(txb.next_bytes(), - Some((0, x)) if x.len()==INITIAL_RECV_WINDOW_SIZE + Some((0, x)) if x.len()==INITIAL_LOCAL_MAX_STREAM_DATA && x.iter().all(|ch| *ch == 1))); // As above - let forty_bytes_from_end = INITIAL_RECV_WINDOW_SIZE as u64 - 40; + let forty_bytes_from_end = INITIAL_LOCAL_MAX_STREAM_DATA as u64 - 40; txb.mark_as_acked(0, usize::try_from(forty_bytes_from_end).unwrap()); assert!(matches!(txb.next_bytes(), @@ -2348,7 +2348,7 @@ mod tests { && x.iter().all(|ch| *ch == 1))); // Mark a range 'A' in second slice as sent. Should still return the same - let range_a_start = INITIAL_RECV_WINDOW_SIZE as u64 + 30; + let range_a_start = INITIAL_LOCAL_MAX_STREAM_DATA as u64 + 30; let range_a_end = range_a_start + 10; txb.mark_as_sent(range_a_start, 10); assert!(matches!(txb.next_bytes(), @@ -2357,7 +2357,7 @@ mod tests { && x.iter().all(|ch| *ch == 1))); // Ack entire first slice and into second slice - let ten_bytes_past_end = INITIAL_RECV_WINDOW_SIZE as u64 + 10; + let ten_bytes_past_end = INITIAL_LOCAL_MAX_STREAM_DATA as u64 + 10; txb.mark_as_acked(0, usize::try_from(ten_bytes_past_end).unwrap()); // Get up to marked range A @@ -2396,24 +2396,26 @@ mod tests { } // Should hit stream flow control limit before filling up send buffer - let big_buf = vec![4; INITIAL_RECV_WINDOW_SIZE + 100]; - let res = s.send(&big_buf[..INITIAL_RECV_WINDOW_SIZE]).unwrap(); + let big_buf = vec![4; INITIAL_LOCAL_MAX_STREAM_DATA + 100]; + let res = s.send(&big_buf[..INITIAL_LOCAL_MAX_STREAM_DATA]).unwrap(); assert_eq!(res, 1024 - 100); // should do nothing, max stream data already 1024 s.set_max_stream_data(1024); - let res = s.send(&big_buf[..INITIAL_RECV_WINDOW_SIZE]).unwrap(); + let res = s.send(&big_buf[..INITIAL_LOCAL_MAX_STREAM_DATA]).unwrap(); assert_eq!(res, 0); // should now hit the conn flow control (4096) s.set_max_stream_data(1_048_576); - let res = s.send(&big_buf[..INITIAL_RECV_WINDOW_SIZE]).unwrap(); + let res = s.send(&big_buf[..INITIAL_LOCAL_MAX_STREAM_DATA]).unwrap(); assert_eq!(res, 3072); // should now hit the tx buffer size - conn_fc.borrow_mut().update(INITIAL_RECV_WINDOW_SIZE as u64); + conn_fc + .borrow_mut() + .update(INITIAL_LOCAL_MAX_STREAM_DATA as u64); let res = s.send(&big_buf).unwrap(); - assert_eq!(res, INITIAL_RECV_WINDOW_SIZE - 4096); + assert_eq!(res, INITIAL_LOCAL_MAX_STREAM_DATA - 4096); // TODO(agrover@mozilla.com): test ooo acks somehow s.mark_as_acked(0, 40, false); @@ -2479,8 +2481,8 @@ mod tests { conn_fc.borrow_mut().update(1_000_000_000); assert_eq!(conn_events.events().count(), 0); - let big_buf = vec![b'a'; INITIAL_RECV_WINDOW_SIZE]; - assert_eq!(s.send(&big_buf).unwrap(), INITIAL_RECV_WINDOW_SIZE); + let big_buf = vec![b'a'; INITIAL_LOCAL_MAX_STREAM_DATA]; + assert_eq!(s.send(&big_buf).unwrap(), INITIAL_LOCAL_MAX_STREAM_DATA); } #[test] @@ -2570,7 +2572,7 @@ mod tests { let mut tokens = recovery::Tokens::new(); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); // Write a small frame: no fin. let written = builder.len(); @@ -2659,7 +2661,7 @@ mod tests { let mut tokens = recovery::Tokens::new(); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); ss.write_frames( TransmissionPriority::default(), &mut builder, @@ -2738,7 +2740,7 @@ mod tests { // This doesn't report blocking yet. let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut tokens = recovery::Tokens::new(); let mut stats = FrameStats::default(); s.write_blocked_frame( @@ -2805,7 +2807,7 @@ mod tests { // Assert that STREAM_DATA_BLOCKED is sent. let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut tokens = recovery::Tokens::new(); let mut stats = FrameStats::default(); s.write_blocked_frame( @@ -2893,7 +2895,7 @@ mod tests { // No frame should be sent here. let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut tokens = recovery::Tokens::new(); let mut stats = FrameStats::default(); s.write_stream_frame( @@ -2945,7 +2947,7 @@ mod tests { } let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let header_len = builder.len(); builder.set_limit(header_len + space); @@ -3047,7 +3049,7 @@ mod tests { s.close(); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let header_len = builder.len(); // Add 2 for the frame type and stream ID, then add the extra. builder.set_limit(header_len + data.len() + 2 + extra); diff --git a/third_party/rust/neqo-transport/src/stateless_reset.rs b/third_party/rust/neqo-transport/src/stateless_reset.rs @@ -0,0 +1,126 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Stateless Reset Token implementation. + +use neqo_common::Decoder; +use neqo_crypto::random; + +use crate::Error; + +/// A stateless reset token is a 16-byte value that is used to identify +/// a stateless reset packet. +#[derive(Clone, Debug, Default, Eq)] +pub struct Token([u8; Self::LEN]); + +impl Token { + pub const LEN: usize = 16; + + /// Create a new stateless reset token from a byte array. + #[must_use] + pub const fn new(token: [u8; Self::LEN]) -> Self { + Self(token) + } + + /// Generate a random stateless reset token. + #[must_use] + pub fn random() -> Self { + Self(random::<{ Self::LEN }>()) + } + + /// Get the token as a byte array. + #[must_use] + pub const fn as_bytes(&self) -> &[u8; Self::LEN] { + &self.0 + } +} + +/// Compare two tokens in constant time to prevent timing attacks. +impl PartialEq for Token { + fn eq(&self, other: &Self) -> bool { + // rustc might decide to optimize this and make this non-constant-time. + // It doesn't appear to currently. + let mut c = 0; + for (&a, &b) in self.0.iter().zip(&other.0) { + c |= a ^ b; + } + c == 0 + } +} + +impl TryFrom<&[u8]> for Token { + type Error = Error; + + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + Ok(Self(value.try_into()?)) + } +} + +impl TryFrom<&mut Decoder<'_>> for Token { + type Error = Error; + + fn try_from(d: &mut Decoder<'_>) -> Result<Self, Self::Error> { + Ok(Self( + d.decode(Self::LEN).ok_or(Error::NoMoreData)?.try_into()?, + )) + } +} + +impl AsRef<[u8]> for Token { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_token() { + let bytes = [1u8; Token::LEN]; + let token = Token::new(bytes); + assert_eq!(token.as_bytes(), &bytes); + } + + #[test] + fn random_token() { + neqo_crypto::init().unwrap(); + let token1 = Token::random(); + let token2 = Token::random(); + // With very high probability, two random tokens should be different + assert_ne!(token1, token2); + } + + #[test] + fn eq_same() { + let bytes = [42u8; Token::LEN]; + let token1 = Token::new(bytes); + let token2 = Token::new(bytes); + assert_eq!(token1, token2); + } + + #[test] + fn eq_different() { + let token1 = Token::new([1u8; Token::LEN]); + let token2 = Token::new([2u8; Token::LEN]); + assert_ne!(token1, token2); + } + + #[test] + fn from_slice_valid() { + let bytes = [3u8; Token::LEN]; + let token = Token::try_from(&bytes[..]).unwrap(); + assert_eq!(token.as_bytes(), &bytes); + } + + #[test] + fn from_slice_invalid_length() { + let bytes = [3u8; 15]; + let result = Token::try_from(&bytes[..]); + assert!(result.is_err()); + } +} diff --git a/third_party/rust/neqo-transport/src/streams.rs b/third_party/rust/neqo-transport/src/streams.rs @@ -220,7 +220,7 @@ impl Streams { // Send `MAX_DATA` as necessary. self.receiver_fc .borrow_mut() - .write_frames(builder, tokens, stats); + .write_frames(builder, tokens, stats, now, rtt); if builder.is_full() { return; } diff --git a/third_party/rust/neqo-transport/src/tparams.rs b/third_party/rust/neqo-transport/src/tparams.rs @@ -23,8 +23,9 @@ use neqo_crypto::{ use strum::FromRepr; use crate::{ - cid::{ConnectionId, ConnectionIdEntry, CONNECTION_ID_SEQNO_PREFERRED, MAX_CONNECTION_ID_LEN}, + cid::{ConnectionId, ConnectionIdEntry, ConnectionIdManager}, packet::MIN_INITIAL_PACKET_SIZE, + stateless_reset::Token as Srt, tracking::DEFAULT_REMOTE_ACK_DELAY, version::{self, Version}, Error, Res, @@ -146,7 +147,7 @@ pub enum TransportParameter { v4: Option<SocketAddrV4>, v6: Option<SocketAddrV6>, cid: ConnectionId, - srt: [u8; 16], + srt: Srt, }, Versions { current: version::Wire, @@ -176,16 +177,16 @@ impl TransportParameter { enc_inner.encode(&v4.ip().octets()[..]); enc_inner.encode_uint(2, v4.port()); } else { - enc_inner.encode(&[0; 6]); + enc_inner.encode([0; 6]); } if let Some(v6) = v6 { enc_inner.encode(&v6.ip().octets()[..]); enc_inner.encode_uint(2, v6.port()); } else { - enc_inner.encode(&[0; 18]); + enc_inner.encode([0; 18]); } enc_inner.encode_vec(1, &cid[..]); - enc_inner.encode(&srt[..]); + enc_inner.encode(srt); }); } Self::Versions { current, other } => { @@ -233,13 +234,12 @@ impl TransportParameter { // Connection ID (non-zero length) let cid = ConnectionId::from(d.decode_vec(1).ok_or(Error::NoMoreData)?); - if cid.is_empty() || cid.len() > MAX_CONNECTION_ID_LEN { + if cid.is_empty() || cid.len() > ConnectionId::MAX_LEN { return Err(Error::TransportParameter); } // Stateless reset token - let srtbuf = d.decode(16).ok_or(Error::NoMoreData)?; - let srt = <[u8; 16]>::try_from(srtbuf)?; + let srt = Srt::try_from(d).map_err(|_| Error::TransportParameter)?; Ok(Self::PreferredAddress { v4, v6, cid, srt }) } @@ -586,13 +586,17 @@ impl TransportParameters { /// Get the preferred address in a usable form. #[must_use] - pub fn get_preferred_address(&self) -> Option<(PreferredAddress, ConnectionIdEntry<[u8; 16]>)> { + pub fn get_preferred_address(&self) -> Option<(PreferredAddress, ConnectionIdEntry<Srt>)> { if let Some(TransportParameter::PreferredAddress { v4, v6, cid, srt }) = &self.params[TransportParameterId::PreferredAddress] { Some(( PreferredAddress::new(*v4, *v6), - ConnectionIdEntry::new(CONNECTION_ID_SEQNO_PREFERRED, cid.clone(), *srt), + ConnectionIdEntry::new( + ConnectionIdManager::SEQNO_PREFERRED, + cid.clone(), + srt.clone(), + ), )) } else { None @@ -891,6 +895,7 @@ mod tests { use super::PreferredAddress; use crate::{ + stateless_reset::Token as Srt, tparams::{TransportParameter, TransportParameterId, TransportParameters}, ConnectionId, Error, Version, }; @@ -941,7 +946,7 @@ mod tests { 0, )), cid: ConnectionId::from(&[1, 2, 3, 4, 5]), - srt: [3; 16], + srt: Srt::new([3; Srt::LEN]), } } diff --git a/third_party/rust/neqo-transport/src/tracking.rs b/third_party/rust/neqo-transport/src/tracking.rs @@ -631,7 +631,7 @@ mod tests { }; use crate::{ frame::Frame, - packet::{self, PACKET_LIMIT}, + packet, recovery::{self}, stats::FrameStats, Stats, @@ -770,7 +770,7 @@ mod tests { fn write_frame_at(rp: &mut RecvdPackets, now: Instant) { let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); let mut stats = FrameStats::default(); let mut tokens = recovery::Tokens::new(); rp.write_frame(now, RTT, &mut builder, &mut tokens, &mut stats); @@ -930,7 +930,7 @@ mod tests { let mut stats = Stats::default(); let mut tracker = AckTracker::default(); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); tracker .get_mut(PacketNumberSpace::Initial) .unwrap() @@ -999,7 +999,7 @@ mod tests { .is_some()); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); builder.set_limit(10); let mut stats = FrameStats::default(); @@ -1034,7 +1034,7 @@ mod tests { .is_some()); let mut builder = - packet::Builder::short(Encoder::new(), false, None::<&[u8]>, PACKET_LIMIT); + packet::Builder::short(Encoder::new(), false, None::<&[u8]>, packet::LIMIT); // The code pessimistically assumes that each range needs 16 bytes to express. // So this won't be enough for a second range. builder.set_limit(RecvdPackets::USEFUL_ACK_LEN + 8); diff --git a/third_party/rust/neqo-transport/src/version.rs b/third_party/rust/neqo-transport/src/version.rs @@ -83,6 +83,10 @@ impl Version { } } + #[cfg_attr( + not(feature = "draft-29"), + expect(clippy::unused_self, reason = "Only used with draft-29 feature.") + )] pub(crate) const fn is_draft(self) -> bool { #[cfg(feature = "draft-29")] return matches!(self, Self::Draft29); diff --git a/third_party/rust/neqo-transport/tests/retry.rs b/third_party/rust/neqo-transport/tests/retry.rs @@ -413,7 +413,7 @@ fn vn_after_retry() { let mut encoder = Encoder::default(); encoder.encode_byte(0x80); - encoder.encode(&[0; 4]); // Zero version == VN. + encoder.encode([0; 4]); // Zero version == VN. encoder.encode_vec(1, &client.odcid().unwrap()[..]); encoder.encode_vec(1, &[]); encoder.encode_uint(4, 0x5a5a_6a6a_u64); diff --git a/third_party/rust/neqo-transport/tests/server.rs b/third_party/rust/neqo-transport/tests/server.rs @@ -449,7 +449,7 @@ fn bad_client_initial() { .unwrap(); let mut payload_enc = Encoder::from(plaintext); - payload_enc.encode(&[0x08, 0x02, 0x00, 0x00]); // Add a stream frame. + payload_enc.encode([0x08, 0x02, 0x00, 0x00]); // Add a stream frame. // Make a new header with a 1 byte packet number length. let mut header_enc = Encoder::new(); @@ -541,7 +541,7 @@ fn bad_client_initial_connection_close() { let (_, pn) = header_protection::remove(&hp, header, payload); let mut payload_enc = Encoder::with_capacity(MIN_INITIAL_PACKET_SIZE); - payload_enc.encode(&[0x1c, 0x01, 0x00, 0x00]); // Add a CONNECTION_CLOSE frame. + payload_enc.encode([0x1c, 0x01, 0x00, 0x00]); // Add a CONNECTION_CLOSE frame. // Make a new header with a 1 byte packet number length. let mut header_enc = Encoder::new(); diff --git a/third_party/rust/neqo-udp/.cargo-checksum.json b/third_party/rust/neqo-udp/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"61de4d7309d0bd40ca4793cd8b4a93a120dee8c697f9ed36591c476dbd08e929","build.rs":"bf57cd35a78f636c14c442c1926abc2deca3d137e9d207e4f2f960f5b8363b07","src/lib.rs":"bb87c16ab8587eb2e3a68b948de6cc0d36a469194f243bfd6b33fcf31c9e6a2d"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"f6338dbe90fdc85795d1faa5e955f2cc1a3d00936bb4d5a206f7aa1ba8fbebe7","build.rs":"bf57cd35a78f636c14c442c1926abc2deca3d137e9d207e4f2f960f5b8363b07","src/lib.rs":"bb87c16ab8587eb2e3a68b948de6cc0d36a469194f243bfd6b33fcf31c9e6a2d"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/neqo-udp/Cargo.toml b/third_party/rust/neqo-udp/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.81.0" name = "neqo-udp" -version = "0.18.0" +version = "0.19.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false