tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d07761dfe92c7d6e1a549538489d3f926799d380
parent 563ae3de09941abe8701eafdfd4652069d2878e4
Author: Oskar Mansfeld <git@omansfeld.net>
Date:   Fri, 28 Nov 2025 12:20:16 +0000

Bug 2002613 - Update neqo to v0.20.0 r=necko-reviewers,supply-chain-reviewers,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D274320

Diffstat:
M.cargo/config.toml.in | 4++--
MCargo.lock | 31++++++++++++++++---------------
Mnetwerk/socket/neqo_glue/Cargo.toml | 12++++++------
Mnetwerk/test/http3server/Cargo.toml | 12++++++------
Msupply-chain/audits.toml | 4++--
Mthird_party/rust/mtu/.cargo-checksum.json | 4++--
Mthird_party/rust/mtu/Cargo.toml | 2+-
Mthird_party/rust/mtu/src/lib.rs | 29+++++++++++++++++++++--------
Mthird_party/rust/mtu/src/windows.rs | 10++++++----
Mthird_party/rust/neqo-bin/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-bin/Cargo.toml | 8++++++--
Mthird_party/rust/neqo-bin/src/client/mod.rs | 7+++++++
Mthird_party/rust/neqo-bin/src/server/mod.rs | 8++++++++
Mthird_party/rust/neqo-common/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-common/Cargo.toml | 14+++++++++++---
Mthird_party/rust/neqo-common/src/fuzz.rs | 25+++++++++++++------------
Mthird_party/rust/neqo-crypto/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-crypto/Cargo.toml | 4++--
Mthird_party/rust/neqo-http3/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-http3/Cargo.toml | 4++--
Mthird_party/rust/neqo-http3/tests/non_ascii_headers.rs | 7+++----
Mthird_party/rust/neqo-qpack/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-qpack/Cargo.toml | 9+++++++--
Mthird_party/rust/neqo-qpack/src/decoder.rs | 11+++++++++--
Mthird_party/rust/neqo-qpack/src/encoder.rs | 2++
Athird_party/rust/neqo-qpack/src/fuzz.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mthird_party/rust/neqo-qpack/src/lib.rs | 1+
Mthird_party/rust/neqo-qpack/src/table.rs | 2+-
Mthird_party/rust/neqo-transport/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-transport/Cargo.toml | 4++--
Mthird_party/rust/neqo-transport/benches/min_bandwidth.rs | 2+-
Mthird_party/rust/neqo-transport/src/addr_valid.rs | 13++++++++++---
Mthird_party/rust/neqo-transport/src/cc/classic_cc.rs | 333+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mthird_party/rust/neqo-transport/src/cc/mod.rs | 11+++++++++--
Mthird_party/rust/neqo-transport/src/cc/tests/cubic.rs | 58+++++++++++++++++++++++++++++++++++++++++-----------------
Mthird_party/rust/neqo-transport/src/cc/tests/new_reno.rs | 49+++++++++++++++++++++++++++++++++++++++++--------
Mthird_party/rust/neqo-transport/src/cid.rs | 18+++++++++++-------
Mthird_party/rust/neqo-transport/src/connection/mod.rs | 20++++++++++++--------
Mthird_party/rust/neqo-transport/src/connection/params.rs | 2--
Mthird_party/rust/neqo-transport/src/connection/state.rs | 36++++++++++++++++++++++--------------
Mthird_party/rust/neqo-transport/src/connection/tests/datagram.rs | 7++++---
Mthird_party/rust/neqo-transport/src/connection/tests/ecn.rs | 12+++++++-----
Mthird_party/rust/neqo-transport/src/connection/tests/handshake.rs | 2+-
Mthird_party/rust/neqo-transport/src/connection/tests/migration.rs | 6+++---
Mthird_party/rust/neqo-transport/src/connection/tests/mod.rs | 4++--
Mthird_party/rust/neqo-transport/src/connection/tests/recovery.rs | 7+++----
Mthird_party/rust/neqo-transport/src/crypto.rs | 15++++++++++-----
Mthird_party/rust/neqo-transport/src/fc.rs | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mthird_party/rust/neqo-transport/src/frame.rs | 39++++++++++++++++++++++++++++++++++++++-
Mthird_party/rust/neqo-transport/src/pace.rs | 28++++++++++++++--------------
Mthird_party/rust/neqo-transport/src/packet/mod.rs | 12++++++++----
Mthird_party/rust/neqo-transport/src/path.rs | 42++++++++++++++++++++++++------------------
Mthird_party/rust/neqo-transport/src/pmtud.rs | 64+++++++++++++++++++++++++++++-----------------------------------
Mthird_party/rust/neqo-transport/src/quic_datagrams.rs | 23+++++++++++++----------
Mthird_party/rust/neqo-transport/src/recovery/sent.rs | 14++++++++++++++
Mthird_party/rust/neqo-transport/src/saved.rs | 13++++++-------
Mthird_party/rust/neqo-transport/src/send_stream.rs | 57++++++++++++++++++++++++++++++---------------------------
Mthird_party/rust/neqo-transport/src/sender.rs | 14+++++++++++---
Mthird_party/rust/neqo-transport/src/stats.rs | 32++++++++++++++++++++++++++++----
Mthird_party/rust/neqo-transport/src/tparams.rs | 12+++++++++++-
Mthird_party/rust/neqo-transport/src/tracking.rs | 53+++++++++++++++++++++++++++++------------------------
Mthird_party/rust/neqo-udp/.cargo-checksum.json | 4++--
Mthird_party/rust/neqo-udp/Cargo.toml | 4++--
63 files changed, 1056 insertions(+), 362 deletions(-)

diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in @@ -110,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.19.0"] +[source."git+https://github.com/mozilla/neqo?tag=v0.20.0"] git = "https://github.com/mozilla/neqo" -tag = "v0.19.0" +tag = "v0.20.0" replace-with = "vendored-sources" [source."git+https://github.com/rust-lang/rust-bindgen?rev=9366e0af8da529c958b4cd4fcbe492d951c86f5c"] diff --git a/Cargo.lock b/Cargo.lock @@ -4764,7 +4764,7 @@ dependencies = [ [[package]] name = "mtu" version = "0.2.9" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "bindgen 0.72.0", "cfg_aliases", @@ -4808,13 +4808,14 @@ dependencies = [ [[package]] name = "neqo-bin" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "clap", "clap-verbosity-flag", "futures", "hex", + "libc", "log", "neqo-common", "neqo-crypto", @@ -4832,8 +4833,8 @@ dependencies = [ [[package]] name = "neqo-common" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "enum-map", "env_logger", @@ -4846,8 +4847,8 @@ dependencies = [ [[package]] name = "neqo-crypto" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "bindgen 0.72.0", "enum-map", @@ -4865,8 +4866,8 @@ dependencies = [ [[package]] name = "neqo-http3" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "enumset", "log", @@ -4884,8 +4885,8 @@ dependencies = [ [[package]] name = "neqo-qpack" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "log", "neqo-common", @@ -4898,8 +4899,8 @@ dependencies = [ [[package]] name = "neqo-transport" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "enum-map", "enumset", @@ -4918,8 +4919,8 @@ dependencies = [ [[package]] name = "neqo-udp" -version = "0.19.0" -source = "git+https://github.com/mozilla/neqo?tag=v0.19.0#3dbba8ffc2b3c78713161d1925b2858bd2098548" +version = "0.20.0" +source = "git+https://github.com/mozilla/neqo?tag=v0.20.0#126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" dependencies = [ "cfg_aliases", "libc", 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.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" } +neqo-udp = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-http3 = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-transport = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } +neqo-common = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-qpack = { tag = "v0.20.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.19.0" +tag = "v0.20.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.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" } +neqo-bin = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-transport = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo", features = ["gecko"] } +neqo-common = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-http3 = { tag = "v0.20.0", git = "https://github.com/mozilla/neqo" } +neqo-qpack = { tag = "v0.20.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.19.0" +tag = "v0.20.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,9 +4265,9 @@ criteria = "safe-to-deploy" delta = "0.2.6 -> 0.2.9" [[audits.mtu]] -who = "Valentin Gosu <valentin.gosu@gmail.com>" +who = "Oskar Mansfeld <git@omansfeld.net>" criteria = "safe-to-deploy" -delta = "0.2.9 -> 0.2.9@git:3dbba8ffc2b3c78713161d1925b2858bd2098548" +delta = "0.2.9 -> 0.2.9@git:126b1df97c7f88e4b66ef16dbfe4708dc6f104d9" importable = false notes = "mtu crate is now part of neqo and maintained by Mozilla employees" 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":"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 +{"files":{".clippy.toml":"6ab1a673bd5c7ba29bd77e62f42183db3ace327c23d446d5b4b0618f6c39d639","Cargo.toml":"44d90acbefc8884ffac0e3df85e3e63150d19f5ae991f9cd612a0074de6d016e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"4ad721b5b6a3d39ca3e2202f403d897c4a1d42896486dd58963a81f8e64ef61d","README.md":"24df0e24154b7f0096570ad221aea02bd53a0f1124a2adafff5730af5443a65c","SECURITY.md":"75455814b6cf997e22a927eb979b4356d788583aa1eb96e90853aaab0f82ad1b","build.rs":"a4bcd0562c80914a8e909e8b10507605bfd6f0f268fad9ef4d79f4c48bdaed6c","src/bsd.rs":"f6d472effbdd95f6fd4285dfb39d37a99da66ed7283906862ad29a3c2233fb19","src/lib.rs":"69aa3d9508a8c23979a94a6ebebd13767acf4c4e2345640a9faacaedb7eecb14","src/linux.rs":"d4d2e42d8e0835d64ac154b4bddb5fe9e9228e5d8c9ccd25d6afa89cfb6b6523","src/routesocket.rs":"be837947e2c3f9301a174499217fe8920ff492918bf85ca5eb281eb7ad2240e1","src/windows.rs":"b3383619f53463608c10bdc30dcb3b9c05a5fd8328a64cbd14fabefc5b5d0a8a"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/mtu/Cargo.toml b/third_party/rust/mtu/Cargo.toml @@ -176,4 +176,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/mtu/src/lib.rs b/third_party/rust/mtu/src/lib.rs @@ -135,10 +135,7 @@ pub fn interface_and_mtu(remote: IpAddr) -> Result<(String, usize)> { mod test { #![expect(clippy::unwrap_used, reason = "OK in tests.")] - use std::{ - env, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - }; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::interface_and_mtu; @@ -204,12 +201,28 @@ mod test { #[test] fn inet_v6() { - match interface_and_mtu(IpAddr::V6(Ipv6Addr::new( + let res = interface_and_mtu(IpAddr::V6(Ipv6Addr::new( 0x2606, 0x4700, 0, 0, 0, 0, 0x6810, 0x84e5, // cloudflare.com - ))) { + ))); + match res { Ok(res) => assert_eq!(res, INET), - // The GitHub CI environment does not have IPv6 connectivity. - Err(_) => assert!(env::var("GITHUB_ACTIONS").is_ok()), + Err(e) => { + #[cfg(not(target_os = "windows"))] + let no_ipv6 = matches!(e.raw_os_error(), Some(libc::ENETUNREACH | libc::ESRCH)); + #[cfg(target_os = "windows")] + let no_ipv6 = e.raw_os_error() + == Some( + windows::Win32::Foundation::ERROR_NETWORK_UNREACHABLE + .0 + .try_into() + .unwrap(), + ); + if no_ipv6 { + eprintln!("skipping IPv6 test due to lack of IPv6 connectivity: {e}"); + } else { + panic!("unexpected error on IPv6 interface_and_mtu lookup: {e}"); + } + } } } } diff --git a/third_party/rust/mtu/src/windows.rs b/third_party/rust/mtu/src/windows.rs @@ -12,7 +12,6 @@ use std::{ }; use windows::Win32::{ - Foundation::NO_ERROR, NetworkManagement::{ IpHelper::{ if_indextoname, FreeMibTable, GetBestInterfaceEx, GetIpInterfaceTable, @@ -100,15 +99,18 @@ pub fn interface_and_mtu_impl(remote: IpAddr) -> Result<(String, usize)> { ) }; if res != 0 { - return Err(Error::last_os_error()); + return Err(Error::from_raw_os_error(res.try_into().unwrap_or(i32::MAX))); } // Get a list of all interfaces with associated metadata. let mut if_table = MibTablePtr::default(); // GetIpInterfaceTable allocates memory, which MibTablePtr::drop will free. let family = if remote.is_ipv4() { AF_INET } else { AF_INET6 }; - if unsafe { GetIpInterfaceTable(family, if_table.mut_ptr_ptr()) } != NO_ERROR { - return Err(Error::last_os_error()); + let res = unsafe { GetIpInterfaceTable(family, if_table.mut_ptr_ptr()) }; + if res.is_err() { + return Err(Error::from_raw_os_error( + res.0.try_into().unwrap_or(i32::MAX), + )); } // Make a slice let ifaces = unsafe { 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":"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 +{"files":{"Cargo.toml":"f96203aee024db3626553b7df4f8e14c1a0d07fc75fc1364781791606b142d8d","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":"6cb39cdcbcceb4e6bfb311ab3aa80787306b48ab310d8fdba6ad47c8a101a0aa","src/lib.rs":"ef20c29297d978a192011371e6af12be26d57063b616d3e21fb3d2750987ce88","src/send_data.rs":"ef8ad949e8b787f77f091a4705672b9801dc79c863d9d54a5296e0839789802e","src/server/http09.rs":"6f8f9bec9c2b8d524f2c331fc0db81c17f71c8c8ac00d50e4b6670c3e226b2b2","src/server/http3.rs":"e38b375132b2455ff1aad816871fd2f279ca79550e821846c2952d9e1c3a8ec5","src/server/mod.rs":"21de7f6126d160dd33d14769be0f673a1a781c42a7ac0f7caf84cf164ee686ab","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false @@ -106,6 +106,10 @@ version = "0.4" features = ["std"] default-features = false +[dependencies.libc] +version = "0.2" +default-features = false + [dependencies.log] version = "0.4" default-features = false @@ -268,4 +272,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/neqo-bin/src/client/mod.rs b/third_party/rust/neqo-bin/src/client/mod.rs @@ -497,6 +497,13 @@ impl<'a, H: Handler> Runner<'a, H> { self.socket.writable().await?; // Now try again. } + Err(e) + if e.raw_os_error() == Some(libc::EIO) && dgram.num_datagrams() > 1 => + { + qinfo!("`libc::sendmsg` failed with {e}; quinn-udp will halt segmentation offload"); + // Drop the packets and let QUIC handle retransmission. + break; + } e @ Err(_) => return e, } }, diff --git a/third_party/rust/neqo-bin/src/server/mod.rs b/third_party/rust/neqo-bin/src/server/mod.rs @@ -357,6 +357,14 @@ impl<S: HttpServer + Unpin> Runner<S> { socket.writable().await?; // Now try again. } + Err(e) + if e.raw_os_error() == Some(libc::EIO) + && dgram.num_datagrams() > 1 => + { + qinfo!("`libc::sendmsg` failed with {e}; quinn-udp will halt segmentation offload"); + // Drop the packets and let QUIC handle retransmission. + break; + } e @ Err(_) => return e, } } 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":"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 +{"files":{"Cargo.toml":"11507d35393dba233e10ec7f56c5f76df462288b4df2dc72230dc3f62cdc9a32","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":"b50a43089c959c759bae21da5daadbaefc81cf10a6b8dca787c85619be31854f","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -45,7 +45,10 @@ bench = [ "test-fixture/bench", "log/release_max_level_info", ] -build-fuzzing-corpus = ["hex/alloc"] +build-fuzzing-corpus = [ + "hex/alloc", + "sha1", +] ci = [] test-fixture = [] @@ -85,6 +88,11 @@ default-features = false version = "0.15.1" default-features = false +[dependencies.sha1] +version = "0.10" +optional = true +default-features = false + [dependencies.strum] version = "0.27" features = ["derive"] @@ -204,4 +212,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/neqo-common/src/fuzz.rs b/third_party/rust/neqo-common/src/fuzz.rs @@ -4,17 +4,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::{ - collections::hash_map::DefaultHasher, - fs::File, - hash::{Hash as _, Hasher as _}, - io::Write, - path::Path, -}; +use std::{env, fs::File, io::Write, path::Path}; + +use sha1::{Digest as _, Sha1}; /// Write a data item `data` for the fuzzing target `target` to the fuzzing corpus. The caller needs /// to make sure that `target` is the correct fuzzing target name for the data written. /// +/// The corpus directory can be specified via the `NEQO_CORPUS` environment variable. +/// If not set, defaults to `../fuzz/corpus`. +/// /// # Panics /// /// Panics if the corpus directory does not exist or if the corpus item cannot be written. @@ -22,15 +21,17 @@ pub fn write_item_to_fuzzing_corpus(target: &str, data: &[u8]) { // This bakes in the assumption that we're executing in the root of the neqo workspace. // Unfortunately, `cargo fuzz` doesn't provide a way to learn the location of the corpus // directory. - let corpus = Path::new("../fuzz/corpus").join(target); + let corpus = + Path::new(&env::var("NEQO_CORPUS").unwrap_or_else(|_| "../fuzz/corpus".to_string())) + .join(target); if !corpus.exists() { std::fs::create_dir_all(&corpus).expect("failed to create corpus directory"); } - // Hash the data to get a unique name for the corpus item. - let mut hasher = DefaultHasher::new(); - data.hash(&mut hasher); - let item_name = hex::encode(hasher.finish().to_be_bytes()); + // Hash the data using SHA1 (like LLVM) to get a unique name for the corpus item. + let mut hasher = Sha1::new(); + hasher.update(data); + let item_name = hex::encode(hasher.finalize()); let item_path = corpus.join(item_name); if item_path.exists() { // Don't overwrite existing corpus items. 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":"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 +{"files":{"Cargo.toml":"ca9b945ff25ac1f4a539fc94ae2854c01c5350c2bdc84071e648873f06956931","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -236,4 +236,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] 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":"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 +{"files":{"Cargo.toml":"16e2fa39beb601ec03c20f87d931243030ec9354d9be0c6a3964e2cb5a99bc7c","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":"f86162d95da6f68871a2163ebeb7704c63ddcdf7eb1b8912ebe4dd4b672a73e3","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false @@ -251,4 +251,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/neqo-http3/tests/non_ascii_headers.rs b/third_party/rust/neqo-http3/tests/non_ascii_headers.rs @@ -4,6 +4,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect(clippy::unwrap_used, reason = "OK in test code.")] + 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}; @@ -17,7 +19,6 @@ fn echo_header(request_header_name: &str, response_header_name: &str, test_data: // 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); @@ -40,9 +41,7 @@ fn echo_header(request_header_name: &str, response_header_name: &str, test_data: let mut received_headers = None; while let Some(event) = server.next_event() { if let Http3ServerEvent::Headers { - stream, - headers, - fin: _, + stream, headers, .. } = event { received_stream = Some(stream); 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":"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 +{"files":{"Cargo.toml":"86f4b7907e79a72f73653ea509f3c49b6c286e3a3dd1f3c9926be33d783abf7c","src/decoder.rs":"74fe07840aec0f1aaa42311bb5168d16806674aebc77df41ace08bf4b7d88eb7","src/decoder_instructions.rs":"6b36eea01fdf92088ddac6b6988a239c28ddeb3cc7ecb16abf302f5d1ca8191a","src/encoder.rs":"10295dd855d18a18cbd7fe7d160337ce984d923be0976e456244b48a4379e14e","src/encoder_instructions.rs":"1cf1ba5ab2bbfc8f77ecfbc2bc59e40f77e12f85af5c10d0db2652000a8ff102","src/fuzz.rs":"c9ee149dab0b30a2850f530096274e05475920b43fbff1104def7e789f3e5c6c","src/header_block.rs":"6ed65adefdd251178bdfac0601db4ee8bbd00221de89947e52dc9335c0ac149b","src/huffman.rs":"c3740084c71580a5270c73cae4b7c5035fae913f533474f4a9cbc39b1f29adb7","src/huffman_decode_helper.rs":"3b983bafc69f58810ae93890f101f82148d5d6dbbce8bc232f58bcb50f3946a1","src/huffman_table.rs":"aaa9ee17b8bceb47877d41fdf12fd29d49662a12db183acdb6b06c6e2ad182d9","src/lib.rs":"2177e51a81c85746dd7e3a626c8fc912bd554b5d7fac3a3086e3b480fe6c3a8b","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":"7f4c59650f262cbae6e776beb3be2f2c3a52aa22f7a912e6e1df2af3188e3f13"},"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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = false autolib = false @@ -48,6 +48,11 @@ bench = [ "neqo-transport/bench", "log/release_max_level_info", ] +build-fuzzing-corpus = [ + "neqo-common/build-fuzzing-corpus", + "neqo-transport/build-fuzzing-corpus", + "test-fixture/disable-random", +] [lib] name = "neqo_qpack" @@ -174,4 +179,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/neqo-qpack/src/decoder.rs b/third_party/rust/neqo-qpack/src/decoder.rs @@ -14,7 +14,7 @@ use crate::{ encoder_instructions::{DecodedEncoderInstruction, EncoderInstructionReader}, header_block::{HeaderDecoder, HeaderDecoderResult}, qpack_send_buf::Data, - reader::ReceiverConnWrapper, + reader::{ReadByte, Reader, ReceiverConnWrapper}, stats::Stats, table::HeaderTable, Error, Res, Settings, @@ -91,8 +91,12 @@ impl Decoder { fn read_instructions(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> { let mut recv = ReceiverConnWrapper::new(conn, stream_id); + self.process_instructions(&mut recv) + } + + pub(crate) fn process_instructions<T: ReadByte + Reader>(&mut self, recv: &mut T) -> Res<()> { loop { - match self.instruction_reader.read_instructions(&mut recv) { + match self.instruction_reader.read_instructions(recv) { Ok(instruction) => self.execute_instruction(instruction)?, Err(Error::NeedMoreData) => break Ok(()), Err(e) => break Err(e), @@ -206,6 +210,9 @@ impl Decoder { buf: &[u8], stream_id: StreamId, ) -> Res<Option<Vec<Header>>> { + #[cfg(feature = "build-fuzzing-corpus")] + crate::fuzz::write_item_to_fuzzing_corpus(stream_id, buf); + qdebug!("[{self}] decode header block"); let mut decoder = HeaderDecoder::new(buf); diff --git a/third_party/rust/neqo-qpack/src/encoder.rs b/third_party/rust/neqo-qpack/src/encoder.rs @@ -492,6 +492,8 @@ impl Encoder { .push_front(ref_entries); self.stats.dynamic_table_references += 1; } + #[cfg(feature = "build-fuzzing-corpus")] + crate::fuzz::write_item_to_fuzzing_corpus(stream_id, &encoded_h); encoded_h } diff --git a/third_party/rust/neqo-qpack/src/fuzz.rs b/third_party/rust/neqo-qpack/src/fuzz.rs @@ -0,0 +1,89 @@ +// 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. + +//! Fuzzing support for QPACK. + +#[cfg(feature = "build-fuzzing-corpus")] +pub use write_corpus::write_item_to_fuzzing_corpus; + +#[cfg(feature = "build-fuzzing-corpus")] +/// Helpers to write data to the fuzzing corpus. +mod write_corpus { + use neqo_transport::StreamId; + + /// Write QPACK data to the fuzzing corpus. + pub fn write_item_to_fuzzing_corpus(stream_id: StreamId, buf: &[u8]) { + let mut data = Vec::with_capacity(size_of::<u64>() + buf.len()); + data.extend_from_slice(&stream_id.as_u64().to_le_bytes()); + data.extend_from_slice(buf); + neqo_common::write_item_to_fuzzing_corpus("qpack", &data); + } +} + +#[cfg(fuzzing)] +/// Helpers to support fuzzing. +mod fuzzing { + use crate::{ + reader::{ReadByte, Reader}, + Decoder, Error, Res, + }; + + /// Buffer wrapper that implements `ReadByte` and `Reader` for a byte slice. + /// Returns `NeedMoreData` when the buffer is exhausted. + struct BufferReader<'a> { + buf: &'a [u8], + offset: usize, + } + + impl ReadByte for BufferReader<'_> { + fn read_byte(&mut self) -> Res<u8> { + if self.offset < self.buf.len() { + let b = self.buf[self.offset]; + self.offset += 1; + Ok(b) + } else { + Err(Error::NeedMoreData) + } + } + } + + impl Reader for BufferReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> Res<usize> { + let available = self.buf.len() - self.offset; + let to_read = buf.len().min(available); + buf[..to_read].copy_from_slice(&self.buf[self.offset..self.offset + to_read]); + self.offset += to_read; + Ok(to_read) + } + } + + impl<'a> BufferReader<'a> { + const fn new(buf: &'a [u8]) -> Self { + Self { buf, offset: 0 } + } + } + + fn map_error(err: Error) -> Error { + if err == Error::ClosedCriticalStream { + Error::ClosedCriticalStream + } else { + Error::EncoderStream + } + } + + impl Decoder { + /// Processes encoder stream data from a byte buffer. + /// This populates the dynamic table with header entries from encoder instructions. + /// + /// # Errors + /// + /// May return `Error::EncoderStream` if the encoder instructions are malformed. + pub fn receive_encoder_stream(&mut self, buf: &[u8]) -> Res<()> { + let mut recv = BufferReader::new(buf); + self.process_instructions(&mut recv).map_err(map_error) + } + } +} diff --git a/third_party/rust/neqo-qpack/src/lib.rs b/third_party/rust/neqo-qpack/src/lib.rs @@ -10,6 +10,7 @@ pub mod decoder; mod decoder_instructions; pub mod encoder; mod encoder_instructions; +mod fuzz; mod header_block; pub mod huffman; mod huffman_decode_helper; diff --git a/third_party/rust/neqo-qpack/src/table.rs b/third_party/rust/neqo-qpack/src/table.rs @@ -134,7 +134,7 @@ impl HeaderTable { /// `HeaderLookup` if the index does not exist in the static table. pub fn get_static(index: u64) -> Res<&'static StaticTableEntry> { let inx = usize::try_from(index).or(Err(Error::HeaderLookup))?; - if inx > HEADER_STATIC_TABLE.len() { + if inx >= HEADER_STATIC_TABLE.len() { return Err(Error::HeaderLookup); } Ok(&HEADER_STATIC_TABLE[inx]) 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":"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 +{"files":{"Cargo.toml":"f1bd4eed982713c99f98f95085fc786c089119249c4c3f32d0fbbf170b7b8224","benches/min_bandwidth.rs":"f81c8bccc45cbbecb5a3c4c28b406c5c49319dee3a9caed58feb43b2a2871955","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":"904f5a32f1026f6e0d684c3b3611886f60d566cd108cf3027ceb91c2d3f5b71b","src/cc/classic_cc.rs":"6ba58c166a1dca926b499920cdd67d4faa66a2f0bfe010787dd06e944415f005","src/cc/cubic.rs":"5969163642c2d7ac8593f8603fba93c80940e6eccfb2602f2912628e5c66d64c","src/cc/mod.rs":"c7e30bf6aff8cbd888e83f0c593188238dc3baec8d88d3393f068c1bcfa88c50","src/cc/new_reno.rs":"278e6caa6164e69a30e84f1eb32358d5f4fac05a05addfa129cc6006c1e856c9","src/cc/tests/cubic.rs":"53ff669eb2cc5052f2475db1c6ddc1dc5663da78b432259d191248c592b69ec3","src/cc/tests/mod.rs":"017bf402a9a8c71b5c43343677635644babb57a849d81d0affc328b4b4b9cebb","src/cc/tests/new_reno.rs":"a0aa6b8c6369f42237edbb2597bb0c68e80ae49bf282b6e06c2bb478e7ea5a0e","src/cid.rs":"e8d4437663d1a954b3af75b40ea6fa5552802556c461665647584f1bea5f8bbf","src/connection/idle.rs":"7fffb026530f17604ac41b28c71cf3b3554e6b1958db96436478d9332b65217c","src/connection/mod.rs":"4944eb8975816d984d54c9c44d8871e635eed0a8414685495105a254c782f6ac","src/connection/params.rs":"1007bf962dbae8629746c4e18e8651424b0cf41ea01c1fd46eae40bdd0d9faa2","src/connection/state.rs":"c1f18f21b8729cd523e4165bf7072f056c3b26ba8094f3c1adfcdd7beb3c0979","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":"35c865e9bdfeef2d58f9421370447f627210825f6a3b66a44e84fbce3a22efa3","src/connection/tests/ecn.rs":"f7ac4bab2f518132e0da6e46709c21a5c643a475c49026343fc99bd9bec9b8c4","src/connection/tests/handshake.rs":"cc539ccd4ee21e717f187da9edcfd56e6e8c6f73de3c997df93cadbe844c70b4","src/connection/tests/idle.rs":"f8607ee48bd28486c83e497ce9882239cd8dcf9bdf3a5b1e475d5f50836e6223","src/connection/tests/keys.rs":"3481eb3b24fd034b2b51bfaa02ce58d1890bc10213d3f9d0effdd4d7fceb677c","src/connection/tests/migration.rs":"a97dc409c0573eab65fa9ec7b6d4843d491a2942ce2a90e65a6c6cf750b09714","src/connection/tests/mod.rs":"48c04e7cfe70114f014ac4599a4a4ed5c059a9d56e742a1e975676863e34114a","src/connection/tests/null.rs":"d39d34c895c40ea88bcc137cba43c34386ef9759c6f66f3487ffd41a5099feb8","src/connection/tests/pmtud.rs":"39e9aa38c4d5ecfe8d3040ce268ce33fa63ad87a774f7cad45f5dcc6e43aaf82","src/connection/tests/priority.rs":"00fb90881299fb93b9ad628840585852422676a6db0dbeee348a51ea3029456d","src/connection/tests/recovery.rs":"b527fceb06ac306ac7504c494a831bf2997148a2b79b76fd890346764bf848d2","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":"18bf84e34fd05fd4c596f5db3b8f122b6685fbc4051ef0a9218e4542dc7618ed","src/ecn.rs":"66411888a79d3fbe7445418cafcc1ad7c1ca2a466341fb8de6240ff2cef25a77","src/events.rs":"0548408fd72c1d6c2458c9a682b6d63e2ce6dc699bce6fd7b72818b04bb9d61c","src/fc.rs":"fbc4b3c355459214408174a75bb0024063f32fcebd33db619d5aa2ac1c72002f","src/frame.rs":"fefd63e3b1fed22a606ca95538f21ad32e7222406d5a2b61ea297c165cf61101","src/lib.rs":"0c3c3610343593f10d0c75e75a01fcf9844c0a082d5a6071150358594cb91743","src/pace.rs":"e566b177c86edf8a8fda43863edef9058b74ed3c43109a5fa7e6a7a5e5f87b43","src/packet/metadata.rs":"7a3f520af2932e1ba26197df37bcc706293600cf94c7b559a5192a6f05f6b728","src/packet/mod.rs":"a2349bce7b55990896692a8646a434d058ae2f8f73507dbee5ca2c1af4cc8fc5","src/packet/retry.rs":"df1c7874ff3d3c478ca95a6c7adcb9b1d988894bf6467cee9afe9607ef0a7a20","src/path.rs":"7d4c01ebcbb901c11fb73fddb766c73c7a2ad64b74e02c565440f00e9c7140a2","src/pmtud.rs":"3d11140c7ef9661f3deba48218c7802c2ce89155692cd7f824fafb5c52613e72","src/qlog.rs":"e5a0a93f19959b235dab7f741b088d4a37aabde62b18d54493eac1418bae6321","src/quic_datagrams.rs":"4c2d37b30d202aac8c7bfbf433645338868a21c9bb7dd3bb746fef8351453557","src/recovery/mod.rs":"623f29a103c911ef78a6895b1d57b3c559b7c52f2919cb3a66211824a2bc459c","src/recovery/sent.rs":"882a105a30d09f43a059fb7472a4cfdeedd8dc1ea9607981ec0ffe09a2e42420","src/recovery/token.rs":"b8866bd2605e59d0c0eaa767275f972563c2fe58804e210b1bf78445c114f05b","src/recv_stream.rs":"40802b5ed4bcf301626a5ba447b05e61d0a222390d97448ed580c09a1a86b967","src/rtt.rs":"ab74c7618f1c200c095bb6f9feda5e8e164738de9196561673d0ea52e7117c90","src/saved.rs":"4ec4b1f45b95527fa37881e6542dc714855f9c66ca435d45de9343532bf89dbe","src/send_stream.rs":"ab8927cc461831cc3af86fad2d2a450bf548d74bc5301fe218549edfc768ac2d","src/sender.rs":"21f55fa440e3365ff8cc08e31868a45e5b4d9979be37579f4f5b23916d291821","src/server.rs":"bda1165f157cfdc4c7b9a02f8e2c32775cdb6a8b2962ee2524ae242869e378e6","src/sni.rs":"34361fd7a19fa7c8b33519ba1bdefc32a2cadec0b9cbcf1073a2e75f0c1730ae","src/stateless_reset.rs":"626e3c90682418d6390b18b0d6823171aa117d994f9762b7350eefd81a9ab4d8","src/stats.rs":"8aa2dd839a2669e2f8790b33509ac07dad56b955adcf86aa0b187ddd9b6167e8","src/stream_id.rs":"a6a44d6f02967feeed3250dadc658861f20e00373a0f239685776b2fa72a59e4","src/streams.rs":"b0727b9ba66b1064a6cf1c13bdae49e0b153728216f56eafbcbb96dfe7b9d012","src/tparams.rs":"7f8c4bd93bf7c6cdc78d8f6ac45ec4b52ca46d958d70543dba144e4e7f5148d0","src/tracking.rs":"991772d9be37740175fb16c5682591694182d51049c1f5d3135bd56ccb844aa1","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -275,4 +275,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"] diff --git a/third_party/rust/neqo-transport/benches/min_bandwidth.rs b/third_party/rust/neqo-transport/benches/min_bandwidth.rs @@ -43,7 +43,7 @@ fn gbit_bandwidth(ecn: bool) { /// How much of the theoretical bandwidth we will expect to deliver. /// Because we're not transferring a whole lot relative to the bandwidth, /// this ratio is relatively low. - const MINIMUM_EXPECTED_UTILIZATION: f64 = 0.3; + const MINIMUM_EXPECTED_UTILIZATION: f64 = 0.4; let gbit_link = || { let rate_byte = LINK_BANDWIDTH / 8; diff --git a/third_party/rust/neqo-transport/src/addr_valid.rs b/third_party/rust/neqo-transport/src/addr_valid.rs @@ -18,7 +18,13 @@ use neqo_crypto::{ }; use smallvec::SmallVec; -use crate::{cid::ConnectionId, frame::FrameType, packet, recovery, stats::FrameStats, Res}; +use crate::{ + cid::ConnectionId, + frame::{FrameEncoder as _, FrameType}, + packet, recovery, + stats::FrameStats, + Res, +}; /// A prefix we add to Retry tokens to distinguish them from `NEW_TOKEN` tokens. const TOKEN_IDENTIFIER_RETRY: &[u8] = &[0x52, 0x65, 0x74, 0x72, 0x79]; @@ -419,8 +425,9 @@ impl NewTokenSender { if t.needs_sending && t.len() <= builder.remaining() { t.needs_sending = false; - builder.encode_varint(FrameType::NewToken); - builder.encode_vvec(&t.token); + builder.encode_frame(FrameType::NewToken, |b| { + b.encode_vvec(&t.token); + }); tokens.push(recovery::Token::NewToken(t.seqno)); stats.new_token += 1; diff --git a/third_party/rust/neqo-transport/src/cc/classic_cc.rs b/third_party/rust/neqo-transport/src/cc/classic_cc.rs @@ -14,9 +14,13 @@ use std::{ use ::qlog::events::{quic::CongestionStateUpdated, EventData}; use neqo_common::{const_max, const_min, qdebug, qinfo, qlog::Qlog, qtrace}; +use rustc_hash::FxHashMap as HashMap; use super::CongestionControl; -use crate::{packet, qlog, recovery::sent, rtt::RttEstimate, sender::PACING_BURST_SIZE, Pmtud}; +use crate::{ + packet, qlog, recovery::sent, rtt::RttEstimate, sender::PACING_BURST_SIZE, + stats::CongestionControlStats, Pmtud, +}; pub const CWND_INITIAL_PKTS: usize = 10; const PERSISTENT_CONG_THRESH: u32 = 3; @@ -96,13 +100,25 @@ pub trait WindowAdjustment: Display + Debug { } #[derive(Debug)] +struct MaybeLostPacket { + time_sent: Instant, +} + +#[derive(Debug)] pub struct ClassicCongestionControl<T> { cc_algorithm: T, state: State, congestion_window: usize, // = kInitialWindow bytes_in_flight: usize, acked_bytes: usize, + /// Packets that have supposedly been lost. These are used for spurious congestion event + /// detection. Gets drained when the same packets are later acked and regularly purged from too + /// old packets in [`Self::cleanup_maybe_lost_packets`]. Needs a tuple of `(packet::Number, + /// packet::Type)` to identify packets across packet number spaces. + maybe_lost_packets: HashMap<(packet::Number, packet::Type), MaybeLostPacket>, ssthresh: usize, + /// Packet number of the first packet that was sent after a congestion event. When this one is + /// acked we will exit [`State::Recovery`] and enter [`State::CongestionAvoidance`]. recovery_start: Option<packet::Number>, /// `first_app_limited` indicates the packet number after which the application might be /// underutilizing the congestion window. When underutilizing the congestion window due to not @@ -174,9 +190,18 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> { acked_pkts: &[sent::Packet], rtt_est: &RttEstimate, now: Instant, + cc_stats: &mut CongestionControlStats, ) { let mut is_app_limited = true; let mut new_acked = 0; + + // Supplying `true` for `rtt_est.pto(true)` here is best effort not to have to track + // `recovery::Loss::confirmed()` all the way down to the congestion controller. Having too + // big a PTO does no harm here. + self.cleanup_maybe_lost_packets(now, rtt_est.pto(true)); + + self.detect_spurious_congestion_event(acked_pkts, cc_stats); + for pkt in acked_pkts { qtrace!( "packet_acked this={self:p}, pn={}, ps={}, ignored={}, lost={}, rtt_est={rtt_est:?}", @@ -275,21 +300,33 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> { pto: Duration, lost_packets: &[sent::Packet], now: Instant, + cc_stats: &mut CongestionControlStats, ) -> bool { if lost_packets.is_empty() { return false; } - for pkt in lost_packets.iter().filter(|pkt| pkt.cc_in_flight()) { - qdebug!( - "packet_lost this={self:p}, pn={}, ps={}", - pkt.pn(), - pkt.len() + for pkt in lost_packets { + if pkt.cc_in_flight() { + qdebug!( + "packet_lost this={self:p}, pn={}, ps={}", + pkt.pn(), + pkt.len() + ); + // bytes_in_flight is set to 0 on a path change, but in case that was because of a + // simple rebinding event, we may still declare packets lost that + // were sent before the rebinding. + self.bytes_in_flight = self.bytes_in_flight.saturating_sub(pkt.len()); + } + let present = self.maybe_lost_packets.insert( + (pkt.pn(), pkt.packet_type()), + MaybeLostPacket { + time_sent: pkt.time_sent(), + }, ); - // BIF is set to 0 on a path change, but in case that was because of a simple rebinding - // event, we may still declare packets lost that were sent before the rebinding. - self.bytes_in_flight = self.bytes_in_flight.saturating_sub(pkt.len()); + debug_assert!(present.is_none()); } + qlog::metrics_updated( &self.qlog, &[qlog::Metric::BytesInFlight(self.bytes_in_flight)], @@ -308,7 +345,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> { return false; }; - let congestion = self.on_congestion_event(last_lost_packet, now); + let congestion = self.on_congestion_event(last_lost_packet, false, now, cc_stats); let persistent_congestion = self.detect_persistent_congestion( first_rtt_sample_time, prev_largest_acked_sent, @@ -329,8 +366,13 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> { /// congestion event. /// /// See <https://datatracker.ietf.org/doc/html/rfc9002#section-b.7>. - fn on_ecn_ce_received(&mut self, largest_acked_pkt: &sent::Packet, now: Instant) -> bool { - self.on_congestion_event(largest_acked_pkt, now) + fn on_ecn_ce_received( + &mut self, + largest_acked_pkt: &sent::Packet, + now: Instant, + cc_stats: &mut CongestionControlStats, + ) -> bool { + self.on_congestion_event(largest_acked_pkt, true, now, cc_stats) } fn discard(&mut self, pkt: &sent::Packet, now: Instant) { @@ -404,6 +446,7 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> { congestion_window: cwnd_initial(pmtud.plpmtu()), bytes_in_flight: 0, acked_bytes: 0, + maybe_lost_packets: HashMap::default(), ssthresh: usize::MAX, recovery_start: None, qlog: Qlog::disabled(), @@ -466,6 +509,43 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> { } } + // NOTE: Maybe do tracking of lost packets per congestion epoch. Right now if we get a spurious + // event and then before the first was recovered get another (or even a real congestion event + // because of random loss, path change, ...), it will only be detected as spurious once the old + // and new lost packets are recovered. This means we'd have two spurious events counted as one + // and would also only be able to recover to the cwnd prior to the second event. + fn detect_spurious_congestion_event( + &mut self, + acked_packets: &[sent::Packet], + cc_stats: &mut CongestionControlStats, + ) { + if self.maybe_lost_packets.is_empty() { + return; + } + + // Removes all newly acked packets that are late acks from `maybe_lost_packets`. + for acked_packet in acked_packets { + self.maybe_lost_packets + .remove(&(acked_packet.pn(), acked_packet.packet_type())); + } + + // If all of them have been removed we detected a spurious congestion event. + if self.maybe_lost_packets.is_empty() { + cc_stats.congestion_events_spurious += 1; + // TODO: Implement spurious congestion event handling: <https://github.com/mozilla/neqo/issues/2694> + } + } + + /// Cleanup lost packets that we are fairly sure will never be getting a late acknowledgment + /// for. + fn cleanup_maybe_lost_packets(&mut self, now: Instant, pto: Duration) { + // The `pto * 2` maximum age of the lost packets is taken from msquic's implementation: + // <https://github.com/microsoft/msquic/blob/2623c07df62b4bd171f469fb29c2714b6735b676/src/core/loss_detection.c#L939-L943> + let max_age = pto * 2; + self.maybe_lost_packets + .retain(|_, packet| now.saturating_duration_since(packet.time_sent) <= max_age); + } + fn detect_persistent_congestion<'a>( &mut self, first_rtt_sample_time: Option<Instant>, @@ -538,7 +618,13 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> { /// Handle a congestion event. /// Returns true if this was a true congestion event. - fn on_congestion_event(&mut self, last_packet: &sent::Packet, now: Instant) -> bool { + fn on_congestion_event( + &mut self, + last_packet: &sent::Packet, + ecn: bool, + now: Instant, + cc_stats: &mut CongestionControlStats, + ) -> bool { // Start a new congestion event if lost or ECN CE marked packet was sent // after the start of the previous congestion recovery period. if !self.after_recovery_start(last_packet) { @@ -558,6 +644,14 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> { self.congestion_window, self.ssthresh ); + + if ecn { + cc_stats.congestion_events_ecn += 1; + } else { + cc_stats.congestion_events_loss += 1; + } + cc_stats.slow_start_exited |= self.state.in_slow_start(); + qlog::metrics_updated( &self.qlog, &[ @@ -606,11 +700,9 @@ mod tests { CongestionControl, CongestionControlAlgorithm, CWND_INITIAL_PKTS, }, packet, - recovery::{ - self, - sent::{self}, - }, + recovery::{self, sent}, rtt::RttEstimate, + stats::CongestionControlStats, Pmtud, }; @@ -664,11 +756,13 @@ mod tests { lost_packets: &[sent::Packet], persistent_expected: bool, ) { + let mut cc_stats = CongestionControlStats::default(); + for p in lost_packets { cc.on_packet_sent(p, now()); } - cc.on_packets_lost(Some(now()), None, PTO, lost_packets, now()); + cc.on_packets_lost(Some(now()), None, PTO, lost_packets, now(), &mut cc_stats); let persistent = if cc.cwnd() == reduced_cwnd { false @@ -1065,6 +1159,7 @@ mod tests { let cwnd = cc.congestion_window; let mut now = now(); let mut next_pn = 0; + let mut cc_stats = CongestionControlStats::default(); // simulate packet bursts below app_limit for packet_burst_size in 1..=BELOW_APP_LIMIT_PKTS { @@ -1088,7 +1183,12 @@ mod tests { packet_burst_size * cc.max_datagram_size() ); now += RTT; - cc.on_packets_acked(&pkts, &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &pkts, + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); assert_eq!(cc.bytes_in_flight(), 0); assert_eq!(cc.acked_bytes, 0); assert_eq!(cwnd, cc.congestion_window); // CWND doesn't grow because we're app limited @@ -1117,7 +1217,12 @@ mod tests { now += RTT; // Check if congestion window gets increased for all packets currently in flight for (i, pkt) in pkts.into_iter().enumerate() { - cc.on_packets_acked(&[pkt], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &[pkt], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); assert_eq!( cc.bytes_in_flight(), @@ -1137,6 +1242,10 @@ mod tests { } } + #[expect( + clippy::too_many_lines, + reason = "A lot of multiline function calls due to formatting" + )] #[test] fn app_limited_congestion_avoidance() { const CWND_PKTS_CA: usize = CWND_INITIAL_PKTS / 2; @@ -1145,6 +1254,7 @@ mod tests { let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); let mut now = now(); + let mut cc_stats = CongestionControlStats::default(); // Change state to congestion avoidance by introducing loss. @@ -1159,7 +1269,7 @@ mod tests { cc.on_packet_sent(&p_lost, now); cwnd_is_default(&cc); now += PTO; - cc.on_packets_lost(Some(now), None, PTO, &[p_lost], now); + cc.on_packets_lost(Some(now), None, PTO, &[p_lost], now, &mut cc_stats); cwnd_is_halved(&cc); let p_not_lost = sent::Packet::new( packet::Type::Short, @@ -1175,6 +1285,7 @@ mod tests { &[p_not_lost], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now, + &mut cc_stats, ); cwnd_is_halved(&cc); // cc is app limited therefore cwnd in not increased. @@ -1206,7 +1317,12 @@ mod tests { ); now += RTT; for (i, pkt) in pkts.into_iter().enumerate() { - cc.on_packets_acked(&[pkt], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &[pkt], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); assert_eq!( cc.bytes_in_flight(), @@ -1241,7 +1357,12 @@ mod tests { let mut last_acked_bytes = 0; // Check if congestion window gets increased for all packets currently in flight for (i, pkt) in pkts.into_iter().enumerate() { - cc.on_packets_acked(&[pkt], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &[pkt], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); assert_eq!( cc.bytes_in_flight(), @@ -1260,6 +1381,7 @@ mod tests { fn ecn_ce() { let now = now(); let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); let p_ce = sent::Packet::new( packet::Type::Short, 1, @@ -1271,10 +1393,173 @@ mod tests { cc.on_packet_sent(&p_ce, now); cwnd_is_default(&cc); assert_eq!(cc.state, State::SlowStart); + assert_eq!(cc_stats.congestion_events_ecn, 0); // Signal congestion (ECN CE) and thus change state to recovery start. - cc.on_ecn_ce_received(&p_ce, now); + cc.on_ecn_ce_received(&p_ce, now, &mut cc_stats); cwnd_is_halved(&cc); assert_eq!(cc.state, State::RecoveryStart); + assert_eq!(cc_stats.congestion_events_ecn, 1); + } + + /// This tests spurious congestion event detection and stat counting + /// + /// Send packets (1, 2) --> `SlowStart`, no events + /// Lose packets (1, 2) --> `RecoveryStart`, 1 event + /// Send packet (3) --> `Recovery`, 1 event + /// Ack packet (3) --> `CongestionAvoidance`, 1 event + /// Ack packet (1) --> `CongestionAvoidance`, 1 event, not a spurious event as not all lost + /// packets were recovered + /// Ack packet (2) --> all lost packets have been recovered so now we've detected a + /// spurious congestion event + #[test] + fn spurious_congestion_event_detection() { + let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let now = now(); + let mut cc_stats = CongestionControlStats::default(); + + let pkt1 = sent::make_packet(1, now, 1000); + let pkt2 = sent::make_packet(2, now, 1000); + + cc.on_packet_sent(&pkt1, now); + cc.on_packet_sent(&pkt2, now); + + // Verify initial state + assert_eq!(cc.state, State::SlowStart); + assert_eq!(cc_stats.congestion_events_loss, 0); + assert_eq!(cc_stats.congestion_events_spurious, 0); + + let mut lost_pkt1 = pkt1.clone(); + let mut lost_pkt2 = pkt2.clone(); + lost_pkt1.declare_lost(now); + lost_pkt2.declare_lost(now); + + cc.on_packets_lost( + Some(now), + None, + PTO, + &[lost_pkt1, lost_pkt2], + now, + &mut cc_stats, + ); + + // Verify congestion event + assert_eq!(cc.state, State::RecoveryStart); + assert_eq!(cc_stats.congestion_events_loss, 1); + + let pkt3 = sent::make_packet(3, now, 1000); + cc.on_packet_sent(&pkt3, now); + + assert_eq!(cc.state, State::Recovery); + assert_eq!(cc_stats.congestion_events_loss, 1); + + cc.on_packets_acked( + &[pkt3], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); + + assert_eq!(cc.state, State::CongestionAvoidance); + assert_eq!(cc_stats.congestion_events_loss, 1); + + cc.on_packets_acked( + &[pkt1], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); + + assert_eq!(cc.state, State::CongestionAvoidance); + assert_eq!(cc_stats.congestion_events_loss, 1); + assert_eq!(cc_stats.congestion_events_spurious, 0); + + cc.on_packets_acked( + &[pkt2], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); + + assert_eq!(cc.state, State::CongestionAvoidance); + assert_eq!(cc_stats.congestion_events_loss, 1); + assert_eq!(cc_stats.congestion_events_spurious, 1); + } + + #[test] + fn spurious_congestion_event_detection_cleanup() { + let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let mut now = now(); + let mut cc_stats = CongestionControlStats::default(); + let rtt_estimate = RttEstimate::new(crate::DEFAULT_INITIAL_RTT); + + let pkt1 = sent::make_packet(1, now, 1000); + cc.on_packet_sent(&pkt1, now); + + cc.on_packets_lost( + Some(now), + None, + rtt_estimate.pto(true), + &[pkt1], + now, + &mut cc_stats, + ); + + // The lost should be added now. + assert!(!cc.maybe_lost_packets.is_empty()); + + // Packets older than 2 * PTO are removed, so we increase by exactly that. + now += 2 * rtt_estimate.pto(true); + + // The cleanup is called when we ack packets, so we send and ack a new one. + let pkt2 = sent::make_packet(2, now, 1000); + cc.on_packet_sent(&pkt2, now); + cc.on_packets_acked(&[pkt2], &rtt_estimate, now, &mut cc_stats); + + // The packet is exactly the maximum age, so it shouldn't be removed yet. This assert makes + // sure we don't clean up too early. + assert!(!cc.maybe_lost_packets.is_empty()); + + // Increase by 1ms to get over the maximum age. + now += Duration::from_millis(1); + + // Send and ack another packet to trigger cleanup. + let pkt3 = sent::make_packet(3, now, 1000); + cc.on_packet_sent(&pkt3, now); + cc.on_packets_acked(&[pkt3], &rtt_estimate, now, &mut cc_stats); + + // Now the packet should be removed. + assert!(cc.maybe_lost_packets.is_empty()); + } + + fn slow_start_exit_stats(ecn: bool) { + let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let now = now(); + let mut cc_stats = CongestionControlStats::default(); + + assert!(cc.state.in_slow_start()); + assert!(!cc_stats.slow_start_exited); + + let pkt1 = sent::make_packet(1, now, 1000); + cc.on_packet_sent(&pkt1, now); + + if ecn { + cc.on_ecn_ce_received(&pkt1, now, &mut cc_stats); + } else { + cc.on_packets_lost(Some(now), None, PTO, &[pkt1], now, &mut cc_stats); + } + + assert!(!cc.state.in_slow_start()); + assert!(cc_stats.slow_start_exited); + } + + #[test] + fn slow_start_exit_stats_loss() { + slow_start_exit_stats(false); + } + + #[test] + fn slow_start_exit_stats_ecn_ce() { + slow_start_exit_stats(true); } } diff --git a/third_party/rust/neqo-transport/src/cc/mod.rs b/third_party/rust/neqo-transport/src/cc/mod.rs @@ -14,7 +14,7 @@ use std::{ use neqo_common::qlog::Qlog; -use crate::{recovery::sent, rtt::RttEstimate, Error, Pmtud}; +use crate::{recovery::sent, rtt::RttEstimate, stats::CongestionControlStats, Error, Pmtud}; mod classic_cc; mod cubic; @@ -56,6 +56,7 @@ pub trait CongestionControl: Display + Debug { acked_pkts: &[sent::Packet], rtt_est: &RttEstimate, now: Instant, + cc_stats: &mut CongestionControlStats, ); /// Returns true if the congestion window was reduced. @@ -66,10 +67,16 @@ pub trait CongestionControl: Display + Debug { pto: Duration, lost_packets: &[sent::Packet], now: Instant, + cc_stats: &mut CongestionControlStats, ) -> bool; /// Returns true if the congestion window was reduced. - fn on_ecn_ce_received(&mut self, largest_acked_pkt: &sent::Packet, now: Instant) -> bool; + fn on_ecn_ce_received( + &mut self, + largest_acked_pkt: &sent::Packet, + now: Instant, + cc_stats: &mut CongestionControlStats, + ) -> bool; #[must_use] fn recovery_packet(&self) -> bool; diff --git a/third_party/rust/neqo-transport/src/cc/tests/cubic.rs b/third_party/rust/neqo-transport/src/cc/tests/cubic.rs @@ -28,6 +28,7 @@ use crate::{ pmtud::Pmtud, recovery::{self, sent}, rtt::RttEstimate, + stats::CongestionControlStats, }; const fn cwnd_after_loss(cwnd: usize) -> usize { @@ -54,7 +55,12 @@ fn fill_cwnd(cc: &mut ClassicCongestionControl<Cubic>, mut next_pn: u64, now: In next_pn } -fn ack_packet(cc: &mut ClassicCongestionControl<Cubic>, pn: u64, now: Instant) { +fn ack_packet( + cc: &mut ClassicCongestionControl<Cubic>, + pn: u64, + now: Instant, + cc_stats: &mut CongestionControlStats, +) { let acked = sent::Packet::new( packet::Type::Short, pn, @@ -63,10 +69,14 @@ fn ack_packet(cc: &mut ClassicCongestionControl<Cubic>, pn: u64, now: Instant) { recovery::Tokens::new(), cc.max_datagram_size(), ); - cc.on_packets_acked(&[acked], &RttEstimate::new(RTT), now); + cc.on_packets_acked(&[acked], &RttEstimate::new(RTT), now, cc_stats); } -fn packet_lost(cc: &mut ClassicCongestionControl<Cubic>, pn: u64) { +fn packet_lost( + cc: &mut ClassicCongestionControl<Cubic>, + pn: u64, + cc_stats: &mut CongestionControlStats, +) { const PTO: Duration = Duration::from_millis(120); let p_lost = sent::Packet::new( packet::Type::Short, @@ -76,7 +86,7 @@ fn packet_lost(cc: &mut ClassicCongestionControl<Cubic>, pn: u64) { recovery::Tokens::new(), cc.max_datagram_size(), ); - cc.on_packets_lost(None, None, PTO, &[p_lost], now()); + cc.on_packets_lost(None, None, PTO, &[p_lost], now(), cc_stats); } fn expected_tcp_acks(cwnd_rtt_start: usize, mtu: usize) -> u64 { @@ -89,6 +99,7 @@ fn expected_tcp_acks(cwnd_rtt_start: usize, mtu: usize) -> u64 { #[test] fn tcp_phase() { let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); // change to congestion avoidance state. cubic.set_ssthresh(1); @@ -121,7 +132,7 @@ fn tcp_phase() { for _ in 0..acks { now += time_increase; - ack_packet(&mut cubic, next_pn_ack, now); + ack_packet(&mut cubic, next_pn_ack, now, &mut cc_stats); next_pn_ack += 1; next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); } @@ -140,7 +151,7 @@ fn tcp_phase() { while cwnd_rtt_start == cubic.cwnd() { num_acks += 1; now += time_increase; - ack_packet(&mut cubic, next_pn_ack, now); + ack_packet(&mut cubic, next_pn_ack, now, &mut cc_stats); next_pn_ack += 1; next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); } @@ -166,7 +177,7 @@ fn tcp_phase() { while cwnd_rtt_start_after_tcp == cubic.cwnd() { num_acks2 += 1; now += time_increase; - ack_packet(&mut cubic, next_pn_ack, now); + ack_packet(&mut cubic, next_pn_ack, now, &mut cc_stats); next_pn_ack += 1; next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); } @@ -196,6 +207,7 @@ fn tcp_phase() { #[test] fn cubic_phase() { let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); let cwnd_initial_f64 = convert_to_f64(cubic.cwnd_initial()); // Set w_max to a higher number make sure that cc is the cubic phase (cwnd is calculated // by the cubic equation). @@ -223,7 +235,7 @@ fn cubic_phase() { let time_increase = RTT / u32::try_from(acks).unwrap(); for _ in 0..acks { now += time_increase; - ack_packet(&mut cubic, next_pn_ack, now); + ack_packet(&mut cubic, next_pn_ack, now, &mut cc_stats); next_pn_ack += 1; next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now); } @@ -251,9 +263,10 @@ fn assert_within<T: Sub<Output = T> + PartialOrd + Copy>(value: T, expected: T, #[test] fn congestion_event_slow_start() { let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); _ = fill_cwnd(&mut cubic, 0, now()); - ack_packet(&mut cubic, 0, now()); + ack_packet(&mut cubic, 0, now(), &mut cc_stats); assert_within(cubic.cc_algorithm().w_max(), 0.0, f64::EPSILON); @@ -264,7 +277,7 @@ fn congestion_event_slow_start() { ); // Trigger a congestion_event in slow start phase - packet_lost(&mut cubic, 1); + packet_lost(&mut cubic, 1, &mut cc_stats); // w_max is equal to cwnd before decrease. let cwnd_initial_f64 = convert_to_f64(cubic.cwnd_initial()); @@ -277,11 +290,13 @@ fn congestion_event_slow_start() { cubic.cwnd(), cwnd_after_loss_slow_start(cubic.cwnd_initial(), cubic.max_datagram_size()) ); + assert_eq!(cc_stats.congestion_events_loss, 1); } #[test] fn congestion_event_congestion_avoidance() { let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); // Set ssthresh to something small to make sure that cc is in the congection avoidance phase. cubic.set_ssthresh(1); @@ -294,21 +309,23 @@ fn congestion_event_congestion_avoidance() { .set_w_max(3.0 * max_datagram_size_f64); _ = fill_cwnd(&mut cubic, 0, now()); - ack_packet(&mut cubic, 0, now()); + ack_packet(&mut cubic, 0, now(), &mut cc_stats); assert_eq!(cubic.cwnd(), cubic.cwnd_initial()); - // Trigger a congestion_event in slow start phase - packet_lost(&mut cubic, 1); + // Trigger a congestion_event in congestion avoidance phase. + packet_lost(&mut cubic, 1, &mut cc_stats); let cwnd_initial_f64 = convert_to_f64(cubic.cwnd_initial()); assert_within(cubic.cc_algorithm().w_max(), cwnd_initial_f64, f64::EPSILON); assert_eq!(cubic.cwnd(), cwnd_after_loss(cubic.cwnd_initial())); + assert_eq!(cc_stats.congestion_events_loss, 1); } #[test] fn congestion_event_congestion_avoidance_fast_convergence() { let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); // Set ssthresh to something small to make sure that cc is in the congection avoidance phase. cubic.set_ssthresh(1); @@ -318,7 +335,7 @@ fn congestion_event_congestion_avoidance_fast_convergence() { cubic.cc_algorithm_mut().set_w_max(cwnd_initial_f64 * 10.0); _ = fill_cwnd(&mut cubic, 0, now()); - ack_packet(&mut cubic, 0, now()); + ack_packet(&mut cubic, 0, now(), &mut cc_stats); assert_within( cubic.cc_algorithm().w_max(), @@ -328,7 +345,7 @@ fn congestion_event_congestion_avoidance_fast_convergence() { assert_eq!(cubic.cwnd(), cubic.cwnd_initial()); // Trigger a congestion_event. - packet_lost(&mut cubic, 1); + packet_lost(&mut cubic, 1, &mut cc_stats); assert_within( cubic.cc_algorithm().w_max(), @@ -336,12 +353,14 @@ fn congestion_event_congestion_avoidance_fast_convergence() { f64::EPSILON, ); assert_eq!(cubic.cwnd(), cwnd_after_loss(cubic.cwnd_initial())); + assert_eq!(cc_stats.congestion_events_loss, 1); } #[test] fn congestion_event_congestion_avoidance_no_overflow() { const PTO: Duration = Duration::from_millis(120); let mut cubic = ClassicCongestionControl::new(Cubic::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); // Set ssthresh to something small to make sure that cc is in the congection avoidance phase. cubic.set_ssthresh(1); @@ -351,7 +370,7 @@ fn congestion_event_congestion_avoidance_no_overflow() { cubic.cc_algorithm_mut().set_w_max(cwnd_initial_f64 * 10.0); _ = fill_cwnd(&mut cubic, 0, now()); - ack_packet(&mut cubic, 1, now()); + ack_packet(&mut cubic, 1, now(), &mut cc_stats); assert_within( cubic.cc_algorithm().w_max(), @@ -361,5 +380,10 @@ fn congestion_event_congestion_avoidance_no_overflow() { assert_eq!(cubic.cwnd(), cubic.cwnd_initial()); // Now ack packet that was send earlier. - ack_packet(&mut cubic, 0, now().checked_sub(PTO).unwrap()); + ack_packet( + &mut cubic, + 0, + now().checked_sub(PTO).unwrap(), + &mut cc_stats, + ); } diff --git a/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs b/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs @@ -6,6 +6,11 @@ // Congestion control +#![expect( + clippy::too_many_lines, + reason = "A lot of multiline function calls due to formatting" +)] + use std::time::Duration; use test_fixture::now; @@ -17,6 +22,7 @@ use crate::{ pmtud::Pmtud, recovery::{self, sent}, rtt::RttEstimate, + stats::CongestionControlStats, }; const PTO: Duration = RTT; @@ -34,6 +40,7 @@ fn cwnd_is_halved(cc: &ClassicCongestionControl<NewReno>) { #[test] fn issue_876() { let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); let now = now(); let before = now.checked_sub(Duration::from_millis(100)).unwrap(); let after = now + Duration::from_millis(150); @@ -105,7 +112,14 @@ fn issue_876() { cwnd_is_default(&cc); assert_eq!(cc.bytes_in_flight(), 6 * cc.max_datagram_size() - 3); - cc.on_packets_lost(Some(now), None, PTO, &sent_packets[0..1], now); + cc.on_packets_lost( + Some(now), + None, + PTO, + &sent_packets[0..1], + now, + &mut cc_stats, + ); // We are now in recovery assert!(cc.recovery_packet()); @@ -125,13 +139,21 @@ fn issue_876() { &sent_packets[6..], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now, + &mut cc_stats, ); assert_eq!(cc.acked_bytes(), sent_packets[6].len()); cwnd_is_halved(&cc); assert_eq!(cc.bytes_in_flight(), 5 * cc.max_datagram_size() - 2); // Packet from before is lost. Should not hurt cwnd. - cc.on_packets_lost(Some(now), None, PTO, &sent_packets[1..2], now); + cc.on_packets_lost( + Some(now), + None, + PTO, + &sent_packets[1..2], + now, + &mut cc_stats, + ); assert!(!cc.recovery_packet()); assert_eq!(cc.acked_bytes(), sent_packets[6].len()); cwnd_is_halved(&cc); @@ -142,6 +164,7 @@ fn issue_876() { // https://github.com/mozilla/neqo/pull/1465 fn issue_1465() { let mut cc = ClassicCongestionControl::new(NewReno::default(), Pmtud::new(IP_ADDR, MTU)); + let mut cc_stats = CongestionControlStats::default(); let mut pn = 0; let mut now = now(); let max_datagram_size = cc.max_datagram_size(); @@ -174,7 +197,7 @@ fn issue_1465() { // advance one rtt to detect lost packet there this simplifies the timers, because // on_packet_loss would only be called after RTO, but that is not relevant to the problem now += RTT; - cc.on_packets_lost(Some(now), None, PTO, &[p1], now); + cc.on_packets_lost(Some(now), None, PTO, &[p1], now, &mut cc_stats); // We are now in recovery assert!(cc.recovery_packet()); @@ -183,14 +206,19 @@ fn issue_1465() { assert_eq!(cc.bytes_in_flight(), 2 * cc.max_datagram_size()); // Don't reduce the cwnd again on second packet loss - cc.on_packets_lost(Some(now), None, PTO, &[p3], now); + cc.on_packets_lost(Some(now), None, PTO, &[p3], now, &mut cc_stats); assert_eq!(cc.acked_bytes(), 0); cwnd_is_halved(&cc); // still the same as after first packet loss assert_eq!(cc.bytes_in_flight(), cc.max_datagram_size()); // the acked packets before on_packet_sent were the cause of // https://github.com/mozilla/neqo/pull/1465 - cc.on_packets_acked(&[p2], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &[p2], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); assert_eq!(cc.bytes_in_flight(), 0); @@ -198,7 +226,12 @@ fn issue_1465() { let p4 = send_next(&mut cc, now); cc.on_packet_sent(&p4, now); now += RTT; - cc.on_packets_acked(&[p4], &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), now); + cc.on_packets_acked( + &[p4], + &RttEstimate::new(crate::DEFAULT_INITIAL_RTT), + now, + &mut cc_stats, + ); // do the same as in the first rtt but now the bug appears let p5 = send_next(&mut cc, now); @@ -206,7 +239,7 @@ fn issue_1465() { now += RTT; let cur_cwnd = cc.cwnd(); - cc.on_packets_lost(Some(now), None, PTO, &[p5], now); + cc.on_packets_lost(Some(now), None, PTO, &[p5], now, &mut cc_stats); // go back into recovery assert!(cc.recovery_packet()); @@ -215,6 +248,6 @@ fn issue_1465() { assert_eq!(cc.bytes_in_flight(), 2 * cc.max_datagram_size()); // this shouldn't introduce further cwnd reduction, but it did before https://github.com/mozilla/neqo/pull/1465 - cc.on_packets_lost(Some(now), None, PTO, &[p6], now); + cc.on_packets_lost(Some(now), None, PTO, &[p6], now, &mut cc_stats); assert_eq!(cc.cwnd(), cur_cwnd / 2); } diff --git a/third_party/rust/neqo-transport/src/cid.rs b/third_party/rust/neqo-transport/src/cid.rs @@ -20,8 +20,11 @@ use neqo_crypto::{random, randomize}; use smallvec::{smallvec, SmallVec}; use crate::{ - frame::FrameType, packet, recovery, stateless_reset::Token as Srt, stats::FrameStats, Error, - Res, + frame::{FrameEncoder as _, FrameType}, + packet, recovery, + stateless_reset::Token as Srt, + stats::FrameStats, + Error, Res, }; #[derive(Clone, Default, Eq, Hash, PartialEq)] @@ -288,11 +291,12 @@ impl ConnectionIdEntry<Srt> { return false; } - builder.encode_varint(FrameType::NewConnectionId); - builder.encode_varint(self.seqno); - builder.encode_varint(0u64); - builder.encode_vec(1, &self.cid); - builder.encode(&self.srt); + builder.encode_frame(FrameType::NewConnectionId, |b| { + b.encode_varint(self.seqno); + b.encode_varint(0u64); + b.encode_vec(1, &self.cid); + b.encode(&self.srt); + }); stats.new_connection_id += 1; true } diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs @@ -39,7 +39,7 @@ use crate::{ crypto::{Crypto, CryptoDxState, Epoch}, ecn, events::{ConnectionEvent, ConnectionEvents, OutgoingDatagramOutcome}, - frame::{CloseError, Frame, FrameType}, + frame::{CloseError, Frame, FrameEncoder as _, FrameType}, packet::{self}, path::{Path, PathRef, Paths}, qlog, @@ -70,7 +70,7 @@ use crate::{ mod idle; pub mod params; mod state; -#[cfg(test)] +#[cfg(any(test, feature = "build-fuzzing-corpus"))] #[cfg_attr(coverage_nightly, coverage(off))] pub mod test_internal; @@ -327,7 +327,7 @@ pub struct Connection { /// For testing purposes it is sometimes necessary to inject frames that wouldn't /// otherwise be sent, just to see how a connection handles them. Inserting them /// into packets proper mean that the frames follow the entire processing path. - #[cfg(test)] + #[cfg(any(test, feature = "build-fuzzing-corpus"))] test_frame_writer: Option<Box<dyn test_internal::FrameWriter>>, } @@ -470,7 +470,7 @@ impl Connection { conn_params, hrtime: hrtime::Time::get(Self::LOOSE_TIMER_RESOLUTION), quic_datagrams, - #[cfg(test)] + #[cfg(any(test, feature = "build-fuzzing-corpus"))] test_frame_writer: None, }; c.stats.borrow_mut().init(format!("{c}")); @@ -1226,7 +1226,7 @@ impl Connection { /// A test-only output function that uses the provided writer to /// pack something extra into the output. - #[cfg(test)] + #[cfg(any(test, feature = "build-fuzzing-corpus"))] pub fn test_write_frames<W>(&mut self, writer: W, now: Instant) -> Output where W: test_internal::FrameWriter + 'static, @@ -1264,7 +1264,7 @@ impl Connection { self.process_saved(now); } let output = self.process_multiple_output(now, max_datagrams); - #[cfg(all(feature = "build-fuzzing-corpus", test))] + #[cfg(feature = "build-fuzzing-corpus")] if self.test_frame_writer.is_none() { if let OutputBatch::DatagramBatch(batch) = &output { for dgram in batch.iter() { @@ -1735,7 +1735,11 @@ impl Connection { let slc_len = slc.len(); let (mut packet, remainder) = match packet::Public::decode(slc, self.cid_manager.decoder().as_ref()) { - Ok((packet, remainder)) => (packet, remainder), + Ok((packet, remainder)) => { + #[cfg(feature = "build-fuzzing-corpus")] + neqo_common::write_item_to_fuzzing_corpus("packet", packet.data()); + (packet, remainder) + } Err(e) => { qinfo!("[{self}] Garbage packet: {e}"); self.stats.borrow_mut().pkt_dropped("Garbage packet"); @@ -2395,7 +2399,7 @@ impl Connection { if probe { // Nothing ack-eliciting and we need to probe; send PING. debug_assert_ne!(builder.remaining(), 0); - builder.encode_varint(FrameType::Ping); + builder.encode_frame(FrameType::Ping, |_| {}); let stats = &mut self.stats.borrow_mut().frame_tx; stats.ping += 1; } diff --git a/third_party/rust/neqo-transport/src/connection/params.rs b/third_party/rust/neqo-transport/src/connection/params.rs @@ -76,8 +76,6 @@ pub const INITIAL_LOCAL_MAX_DATA: u64 = INITIAL_LOCAL_MAX_STREAM_DATA as u64 * C /// - 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 diff --git a/third_party/rust/neqo-transport/src/connection/state.rs b/third_party/rust/neqo-transport/src/connection/state.rs @@ -8,7 +8,12 @@ use std::{cmp::min, rc::Rc, time::Instant}; use neqo_common::{Buffer, Encoder}; -use crate::{frame::FrameType, packet, path::PathRef, recovery, CloseReason, Error}; +use crate::{ + frame::{FrameEncoder as _, FrameType}, + packet, + path::PathRef, + recovery, CloseReason, Error, +}; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] /// The state of the Connection. @@ -121,17 +126,6 @@ impl ClosingFrame { if builder.remaining() < Self::MIN_LENGTH { return; } - match &self.error { - CloseReason::Transport(e) => { - builder.encode_varint(FrameType::ConnectionCloseTransport); - builder.encode_varint(e.code()); - builder.encode_varint(self.frame_type); - } - CloseReason::Application(code) => { - builder.encode_varint(FrameType::ConnectionCloseApplication); - builder.encode_varint(*code); - } - } // Truncate the reason phrase if it doesn't fit. As we send this frame in // multiple packet number spaces, limit the overall size to 256. let available = min(256, builder.remaining()); @@ -140,7 +134,21 @@ impl ClosingFrame { } else { &self.reason_phrase }; - builder.encode_vvec(reason); + match &self.error { + CloseReason::Transport(e) => { + builder.encode_frame(FrameType::ConnectionCloseTransport, |b| { + b.encode_varint(e.code()); + b.encode_varint(self.frame_type); + b.encode_vvec(reason); + }); + } + CloseReason::Application(code) => { + builder.encode_frame(FrameType::ConnectionCloseApplication, |b| { + b.encode_varint(*code); + b.encode_vvec(reason); + }); + } + } } } @@ -179,7 +187,7 @@ impl StateSignaling { ) -> Option<recovery::Token> { (matches!(self, Self::HandshakeDone) && builder.remaining() >= 1).then(|| { *self = Self::Idle; - builder.encode_varint(FrameType::HandshakeDone); + builder.encode_frame(FrameType::HandshakeDone, |_| {}); recovery::Token::HandshakeDone }) } diff --git a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs @@ -17,7 +17,7 @@ use crate::{ events::{ConnectionEvent, OutgoingDatagramOutcome}, frame::FrameType, packet, - quic_datagrams::MAX_QUIC_DATAGRAM, + quic_datagrams::QuicDatagram, send_stream::{RetransmissionPriority, TransmissionPriority}, CloseReason, Connection, ConnectionParameters, Error, Pmtud, StreamType, MIN_INITIAL_PACKET_SIZE, @@ -132,10 +132,11 @@ fn datagram_enabled_on_server() { fn connect_datagram() -> (Connection, Connection) { let mut client = new_client( ConnectionParameters::default() - .datagram_size(MAX_QUIC_DATAGRAM) + .datagram_size(QuicDatagram::MAX_SIZE) .outgoing_datagram_queue(OUTGOING_QUEUE), ); - let mut server = new_server(ConnectionParameters::default().datagram_size(MAX_QUIC_DATAGRAM)); + let mut server = + new_server(ConnectionParameters::default().datagram_size(QuicDatagram::MAX_SIZE)); connect_force_idle(&mut client, &mut server); (client, server) } diff --git a/third_party/rust/neqo-transport/src/connection/tests/ecn.rs b/third_party/rust/neqo-transport/src/connection/tests/ecn.rs @@ -21,7 +21,7 @@ use crate::{ send_with_modifier_and_receive, DEFAULT_RTT, }, ecn, packet, - path::MAX_PATH_PROBES, + path::Path, ConnectionEvent, ConnectionId, ConnectionParameters, Output, StreamType, }; @@ -180,10 +180,10 @@ fn migration_delay_to_ecn_blackhole() { .migrate(Some(DEFAULT_ADDR_V4), Some(DEFAULT_ADDR_V4), false, now) .unwrap(); - // The client should send MAX_PATH_PROBES path challenges with ECN enabled, and then another - // MAX_PATH_PROBES without ECN. + // The client should send Path::MAX_PROBES path challenges with ECN enabled, and then another + // Path::MAX_PROBES without ECN. let mut probes = 0; - while probes < MAX_PATH_PROBES * 2 { + while probes < Path::MAX_PROBES * 2 { match client.process_output(now) { Output::Callback(t) => { now += t; @@ -195,7 +195,7 @@ fn migration_delay_to_ecn_blackhole() { probes += 1; assert_eq!(client.stats().frame_tx.path_challenge, probes); assert_path_challenge_min_len(&client, &d, now); - if probes <= MAX_PATH_PROBES { + if probes <= Path::MAX_PROBES { // The first probes should be sent with ECN. assert_ecn_enabled(d.tos()); } else { @@ -217,6 +217,8 @@ fn debug() { "stats for\u{0020} rx: 0 drop 0 dup 0 saved 0 tx: 0 lost 0 lateack 0 ptoack 0 unackdrop 0 + cc: ce_loss 0 ce_ecn 0 ce_spurious 0 + ss_exit: false pmtud: 0 sent 0 acked 0 lost 0 change 0 iface_mtu 0 pmtu resumed: false frames rx: diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs @@ -817,7 +817,7 @@ fn extra_initial_hs() { // Feed that undecryptable packet into the client a few times. // Do that MAX_SAVED_DATAGRAMS times and each time the client will emit // another Initial packet. - for _ in 0..crate::saved::MAX_SAVED_DATAGRAMS { + for _ in 0..crate::saved::SavedDatagrams::CAPACITY { let c_init = match client.process(Some(undecryptable.clone()), now) { Output::None => unreachable!(), Output::Datagram(c_init) => Some(c_init), diff --git a/third_party/rust/neqo-transport/src/connection/tests/migration.rs b/third_party/rust/neqo-transport/src/connection/tests/migration.rs @@ -36,7 +36,7 @@ use crate::{ }, frame::FrameType, packet, - path::MAX_PATH_PROBES, + path::Path, pmtud::Pmtud, stats::FrameStats, tparams::{PreferredAddress, TransportParameter, TransportParameterId}, @@ -498,7 +498,7 @@ fn migrate_immediate_fail() { assert_path_challenge_min_len(&client, &probe, now); // -1 because first PATH_CHALLENGE already sent above - for _ in 0..MAX_PATH_PROBES * 2 - 1 { + for _ in 0..Path::MAX_PROBES * 2 - 1 { let cb = client.process_output(now).callback(); assert_ne!(cb, Duration::new(0, 0)); now += cb; @@ -578,7 +578,7 @@ fn migrate_same_fail() { assert_path_challenge_min_len(&client, &probe, now); // -1 because first PATH_CHALLENGE already sent above - for _ in 0..MAX_PATH_PROBES * 2 - 1 { + for _ in 0..Path::MAX_PROBES * 2 - 1 { let cb = client.process_output(now).callback(); assert_ne!(cb, Duration::new(0, 0)); now += cb; diff --git a/third_party/rust/neqo-transport/src/connection/tests/mod.rs b/third_party/rust/neqo-transport/src/connection/tests/mod.rs @@ -28,7 +28,7 @@ use crate::{ packet, pmtud::Pmtud, recovery::ACK_ONLY_SIZE_LIMIT, - stats::{FrameStats, Stats, MAX_PTO_COUNTS}, + stats::{FrameStats, Stats}, tparams::TransportParameterId::*, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamId, StreamType, Version, MIN_INITIAL_PACKET_SIZE, @@ -514,7 +514,7 @@ fn induce_persistent_congestion( qtrace!("[{client}] induce_persistent_congestion"); now += AT_LEAST_PTO; - let mut pto_counts = [0; MAX_PTO_COUNTS]; + let mut pto_counts = [0; Stats::MAX_PTO_COUNTS]; assert_eq!(client.stats.borrow().pto_counts, pto_counts); qtrace!("[{client}] first PTO"); diff --git a/third_party/rust/neqo-transport/src/connection/tests/recovery.rs b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs @@ -28,10 +28,9 @@ use crate::{ FAST_PTO_SCALE, MAX_OUTSTANDING_UNACK, MAX_PTO_PACKET_COUNT, MIN_OUTSTANDING_UNACK, }, rtt::GRANULARITY, - stats::MAX_PTO_COUNTS, tparams::{TransportParameter, TransportParameterId::*}, tracking::{DEFAULT_LOCAL_ACK_DELAY, DEFAULT_REMOTE_ACK_DELAY}, - CloseReason, Error, Pmtud, StreamType, + CloseReason, Error, Pmtud, Stats, StreamType, }; #[test] @@ -256,7 +255,7 @@ fn pto_handshake_complete() { let cb = client.process_output(now).callback(); assert_eq!(cb, pto); - let mut pto_counts = [0; MAX_PTO_COUNTS]; + let mut pto_counts = [0; Stats::MAX_PTO_COUNTS]; assert_eq!(client.stats.borrow().pto_counts, pto_counts); // Wait for PTO to expire and resend a handshake packet. @@ -481,7 +480,7 @@ fn handshake_ack_pto() { let delay = client.process_output(now).callback(); assert_eq!(delay, RTT * 3); - let mut pto_counts = [0; MAX_PTO_COUNTS]; + let mut pto_counts = [0; Stats::MAX_PTO_COUNTS]; assert_eq!(client.stats.borrow().pto_counts, pto_counts); // Wait for the PTO and ensure that the client generates a packet. diff --git a/third_party/rust/neqo-transport/src/crypto.rs b/third_party/rust/neqo-transport/src/crypto.rs @@ -27,7 +27,7 @@ use neqo_crypto::{ use crate::{ cid::ConnectionIdRef, - frame::FrameType, + frame::{FrameEncoder as _, FrameType}, packet::{self}, recovery, recv_stream::RxStreamOrderer, @@ -188,6 +188,10 @@ impl Crypto { data: Option<&[u8]>, ) -> Res<&HandshakeState> { let input = data.map(|d| { + #[cfg(feature = "build-fuzzing-corpus")] + if space == PacketNumberSpace::Initial && matches!(self.tls, Agent::Server(_)) { + neqo_common::write_item_to_fuzzing_corpus("find_sni", d); + } let rec = Record { ct: TLS_CT_HANDSHAKE, epoch: space.into(), @@ -1605,9 +1609,10 @@ impl CryptoStreams { Encoder::varint_len(u64::try_from(length).expect("usize fits in u64")) - 1; let length = min(data.len(), builder.remaining() - header_len); - builder.encode_varint(FrameType::Crypto); - builder.encode_varint(offset); - builder.encode_vvec(&data[..length]); + builder.encode_frame(FrameType::Crypto, |b| { + b.encode_varint(offset); + b.encode_vvec(&data[..length]); + }); Some((offset, length)) } @@ -1662,7 +1667,7 @@ impl CryptoStreams { return; }; while let Some((offset, data)) = cs.tx.next_bytes() { - #[cfg(all(feature = "build-fuzzing-corpus", test))] + #[cfg(feature = "build-fuzzing-corpus")] if offset == 0 { neqo_common::write_item_to_fuzzing_corpus("find_sni", data); } diff --git a/third_party/rust/neqo-transport/src/fc.rs b/third_party/rust/neqo-transport/src/fc.rs @@ -375,9 +375,42 @@ where return; }; + // Scale the max_active window down by + // [(F-1) / F]; where F=WINDOW_UPDATE_FRACTION. + // + // In the ideal case, each byte sent would trigger a flow control + // update. However, in practice we only send updates every + // WINDOW_UPDATE_FRACTION of the window. Thus, when not application + // limited, in a steady state transfer it takes 1 RTT after sending 1 / + // F bytes for the sender to receive the next update. The sender is + // effectively limited to [(F-1) / F] bytes per RTT. + // + // By calculating with this effective window instead of the full + // max_active, we account for the inherent delay between when the sender + // would ideally receive flow control updates and when they actually + // arrive due to our batched update strategy. + // + // Example with F=4 without adjustment: + // + // t=0 start sending + // t=RTT/4 sent 1/4 of window total + // t=RTT sent 1 window total + // sender blocked for RTT/4 + // t=RTT+RTT/4 receive update for 1/4 of window + // + // Example with F=4 with adjustment: + // + // t=0 start sending + // t=RTT/4 sent 1/4 of window total + // t=RTT sent 1 window total + // t=RTT+RTT/4 sent 1+1/4 window total; receive update for 1/4 of window (just in time) + let effective_window = + (self.max_active * (WINDOW_UPDATE_FRACTION - 1)) / (WINDOW_UPDATE_FRACTION); + // 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_expected = (effective_window * 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. @@ -1201,6 +1234,7 @@ mod test { /// Allow auto-tuning algorithm to be off from actual bandwidth-delay /// product by up to 1KiB. const TOLERANCE: u64 = 1024; + const BW_TOLERANCE: f64 = 0.8; test_fixture::fixture_init(); @@ -1211,6 +1245,7 @@ mod test { u64::from(u16::from_be_bytes(random::<2>()) % 1_000 + 1) * 1_000 * 1_000; // Random delay between 1 ms and 256 ms. let rtt = Duration::from_millis(u64::from(random::<1>()[0]) + 1); + let half_rtt = rtt / 2; let bdp = bandwidth * u64::try_from(rtt.as_millis()).unwrap() / 1_000 / 8; let mut now = test_fixture::now(); @@ -1225,6 +1260,11 @@ mod test { let mut fc = ReceiverFlowControl::new(StreamId::new(0), INITIAL_LOCAL_MAX_STREAM_DATA as u64); + let mut bytes_received: u64 = 0; + let start_time = now; + + // Track when sender can next send. + let mut next_send_time = now; loop { // Sender receives window updates. if recv_to_send.front().is_some_and(|(at, _)| *at <= now) { @@ -1235,9 +1275,14 @@ mod test { // Sender sends data frames. let sender_progressed = if sender_window > 0 { let to_send = min(DATA_FRAME_SIZE, sender_window); - send_to_recv.push_back((now, to_send)); sender_window -= to_send; - now += Duration::from_secs_f64(to_send as f64 * 8.0 / bandwidth as f64); + let time_to_send = + Duration::from_secs_f64(to_send as f64 * 8.0 / bandwidth as f64); + + let send_start = next_send_time.max(now); + next_send_time = send_start + time_to_send; + + send_to_recv.push_back((send_start + time_to_send + half_rtt, to_send)); true } else { false @@ -1247,13 +1292,14 @@ mod test { let mut receiver_progressed = false; if send_to_recv.front().is_some_and(|(at, _)| *at <= now) { let (_, data) = send_to_recv.pop_front().unwrap(); + bytes_received += data; let consumed = fc.set_consumed(fc.retired() + data)?; fc.add_retired(consumed); // Receiver sends window updates. let prev_max_allowed = fc.max_allowed; if write_frames(&mut fc, rtt, now) == 1 { - recv_to_send.push_front((now, fc.max_allowed - prev_max_allowed)); + recv_to_send.push_back((now + half_rtt, fc.max_allowed - prev_max_allowed)); receiver_progressed = true; if last_max_active < fc.max_active() { last_max_active = fc.max_active(); @@ -1272,29 +1318,46 @@ mod test { .expect("both are None"); } - // Consider auto-tuning done once receive window hasn't changed for 4 RTT. - if now.duration_since(last_max_active_changed) > 4 * rtt { + // Consider auto-tuning done once receive window hasn't changed for 8 RTT. + // A large amount to allow the observed bandwidth average to stabilize. + if now.duration_since(last_max_active_changed) > 8 * rtt { break; } } + // See comment in [`ReceiverFlowControl::auto_tune_inner`] for an + // explanation of the effective window. + let effective_window = + (fc.max_active() * (WINDOW_UPDATE_FRACTION - 1)) / WINDOW_UPDATE_FRACTION; + let at_max_stream_data = fc.max_active() == MAX_LOCAL_MAX_STREAM_DATA; + + let observed_bw = + (8 * bytes_received) as f64 / now.duration_since(start_time).as_secs_f64(); let summary = format!( - "Got receive window of {} MiB on connection with bandwidth {} MBit/s ({bandwidth} Bit/s), delay {rtt:?}, bdp {} MiB.", - fc.max_active() / 1024 / 1024, + "Got receive window of {} KiB (effectively {} KiB) on connection with observed bandwidth {} MBit/s. Expected: bandwidth {} MBit/s ({bandwidth} Bit/s), rtt {rtt:?}, bdp {} KiB.", + fc.max_active() / 1024, + effective_window / 1024, + observed_bw / 1_000.0 / 1_000.0, bandwidth / 1_000 / 1_000, - bdp / 1024 / 1024, + bdp / 1024, ); assert!( - fc.max_active() + TOLERANCE >= bdp || fc.max_active() == MAX_LOCAL_MAX_STREAM_DATA, + effective_window + TOLERANCE >= bdp || at_max_stream_data, "{summary} Receive window is smaller than the bdp." ); + assert!( - fc.max_active - TOLERANCE <= bdp + effective_window - TOLERANCE <= bdp || fc.max_active == INITIAL_LOCAL_MAX_STREAM_DATA as u64, "{summary} Receive window is larger than the bdp." ); + assert!( + (bandwidth as f64) * BW_TOLERANCE <= observed_bw || at_max_stream_data, + "{summary} Observed bandwidth is smaller than the link rate." + ); + qdebug!("{summary}"); } diff --git a/third_party/rust/neqo-transport/src/frame.rs b/third_party/rust/neqo-transport/src/frame.rs @@ -8,7 +8,7 @@ use std::ops::RangeInclusive; -use neqo_common::{qtrace, Decoder, Encoder, MAX_VARINT}; +use neqo_common::{qtrace, Buffer, Decoder, Encoder, MAX_VARINT}; use strum::FromRepr; use crate::{ @@ -684,6 +684,43 @@ impl<'a> Frame<'a> { } } +/// Extension trait for [`Encoder`] that automates writing to fuzzing corpus. +pub trait FrameEncoder { + /// Encode a frame with the given type and encoding closure. + /// + /// This method: + /// 1. Encodes the frame type as a varint + /// 2. Calls the provided closure to encode the frame-specific data + /// 3. When fuzzing corpus collection is enabled, saves the frame to the corpus + /// + /// # Example + /// ```ignore + /// builder.encode_frame(FrameType::NewToken, |b| { + /// b.encode_vvec(&token); + /// }); + /// ``` + fn encode_frame<T, F>(&mut self, frame_type: T, encode_fn: F) -> &mut Self + where + T: Into<u64>, + F: FnOnce(&mut Self); +} + +impl<B: Buffer> FrameEncoder for Encoder<B> { + fn encode_frame<T, F>(&mut self, frame_type: T, encode_fn: F) -> &mut Self + where + T: Into<u64>, + F: FnOnce(&mut Self), + { + #[cfg(feature = "build-fuzzing-corpus")] + let frame_start = self.len(); + self.encode_varint(frame_type.into()); + encode_fn(self); + #[cfg(feature = "build-fuzzing-corpus")] + neqo_common::write_item_to_fuzzing_corpus("frame", &self.as_ref()[frame_start..]); + self + } +} + #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { diff --git a/third_party/rust/neqo-transport/src/pace.rs b/third_party/rust/neqo-transport/src/pace.rs @@ -16,16 +16,6 @@ use neqo_common::qtrace; use crate::rtt::GRANULARITY; -/// This value determines how much faster the pacer operates than the -/// congestion window. -/// -/// A value of 1 would cause all packets to be spaced over the entire RTT, -/// which is a little slow and might act as an additional restriction in -/// the case the congestion controller increases the congestion window. -/// This value spaces packets over half the congestion window, which matches -/// our current congestion controller, which double the window every RTT. -const PACER_SPEEDUP: usize = 2; - /// A pacer that uses a leaky bucket. pub struct Pacer { /// Whether pacing is enabled. @@ -42,6 +32,16 @@ pub struct Pacer { } impl Pacer { + /// This value determines how much faster the pacer operates than the + /// congestion window. + /// + /// A value of 1 would cause all packets to be spaced over the entire RTT, + /// which is a little slow and might act as an additional restriction in + /// the case the congestion controller increases the congestion window. + /// This value spaces packets over half the congestion window, which matches + /// our current congestion controller, which double the window every RTT. + const SPEEDUP: usize = 2; + /// Create a new `Pacer`. This takes the current time, the maximum burst size, /// and the packet size. /// @@ -85,12 +85,12 @@ impl Pacer { } // This is the inverse of the function in `spend`: - // self.t + rtt * (self.p - self.c) / (PACER_SPEEDUP * cwnd) + // self.t + rtt * (self.p - self.c) / (Self::SPEEDUP * cwnd) let r = rtt.as_nanos(); 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 add = d / u128::try_from(cwnd * Self::SPEEDUP).expect("usize fits into u128"); let w = u64::try_from(add).map(Duration::from_nanos).unwrap_or(rtt); // If the increment is below the timer granularity, send immediately. @@ -119,12 +119,12 @@ impl Pacer { qtrace!("[{self}] spend {count} over {cwnd}, {rtt:?}"); // Increase the capacity by: - // `(now - self.t) * PACER_SPEEDUP * cwnd / rtt` + // `(now - self.t) * Self::SPEEDUP * cwnd / rtt` // That is, the elapsed fraction of the RTT times rate that data is added. let incr = now .saturating_duration_since(self.t) .as_nanos() - .saturating_mul(u128::try_from(cwnd * PACER_SPEEDUP).expect("usize fits into u128")) + .saturating_mul(u128::try_from(cwnd * Self::SPEEDUP).expect("usize fits into u128")) .checked_div(rtt.as_nanos()) .and_then(|i| usize::try_from(i).ok()) .unwrap_or(self.m); diff --git a/third_party/rust/neqo-transport/src/packet/mod.rs b/third_party/rust/neqo-transport/src/packet/mod.rs @@ -21,7 +21,7 @@ use strum::{EnumIter, FromRepr}; use crate::{ cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef}, crypto::{CryptoDxState, CryptoStates, Epoch}, - frame::FrameType, + frame::{FrameEncoder as _, FrameType}, version::{self, Version}, Error, Res, }; @@ -52,7 +52,7 @@ pub use metadata::MetaData; pub type Number = u64; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum, EnumIter, FromRepr)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum, EnumIter, FromRepr, Hash)] #[repr(u8)] pub enum Type { Initial = 0, @@ -453,8 +453,12 @@ impl<B: Buffer> Builder<B> { .map(|&v| Encoder::varint_len(v)) .sum::<usize>(); if write { - for v in values { - self.encode_varint(*v); + if let Some((frame_type, rest)) = values.split_first() { + self.encode_frame(*frame_type, |enc| { + for v in rest { + enc.encode_varint(*v); + } + }); } debug_assert!(self.len() <= self.limit()); } diff --git a/third_party/rust/neqo-transport/src/path.rs b/third_party/rust/neqo-transport/src/path.rs @@ -22,7 +22,7 @@ use crate::{ ackrate::{AckRate, PeerAckDelay}, cid::{ConnectionId, ConnectionIdRef, ConnectionIdStore, RemoteConnectionIdEntry}, ecn, - frame::FrameType, + frame::{FrameEncoder as _, FrameType}, packet, pmtud::Pmtud, recovery::{self, sent}, @@ -33,11 +33,6 @@ use crate::{ ConnectionParameters, Stats, }; -/// The number of times that a path will be probed before it is considered failed. -/// -/// Note that with [`crate::ecn`], a path is probed [`MAX_PATH_PROBES`] with ECN -/// marks and [`MAX_PATH_PROBES`] without. -pub const MAX_PATH_PROBES: usize = 3; /// The maximum number of paths that `Paths` will track. const MAX_PATHS: usize = 15; @@ -381,8 +376,9 @@ impl Paths { self.to_retire.push(seqno); break; } - builder.encode_varint(FrameType::RetireConnectionId); - builder.encode_varint(seqno); + builder.encode_frame(FrameType::RetireConnectionId, |b| { + b.encode_varint(seqno); + }); tokens.push(recovery::Token::RetireConnectionId(seqno)); stats.retire_connection_id += 1; } @@ -530,6 +526,12 @@ pub struct Path { } impl Path { + /// The number of times that a path will be probed before it is considered failed. + /// + /// Note that with [`crate::ecn`], a path is probed [`Self::MAX_PROBES`] with ECN + /// marks and [`Self::MAX_PROBES`] without. + pub const MAX_PROBES: usize = 3; + /// Create a path from addresses and a remote connection ID. /// This is used for migration and for new datagrams. pub fn temporary( @@ -766,7 +768,7 @@ impl Path { ProbeState::ProbeNeeded { probe_count, .. } => *probe_count, _ => 0, }; - self.state = if probe_count >= MAX_PATH_PROBES { + self.state = if probe_count >= Self::MAX_PROBES { if self.ecn_info.is_marking() { // The path validation failure may be due to ECN blackholing, try again without ECN. qinfo!("[{self}] Possible ECN blackhole, disabling ECN and re-probing path"); @@ -801,8 +803,9 @@ impl Path { // Send PATH_RESPONSE. let resp_sent = if let Some(challenge) = self.challenge.take() { qtrace!("[{self}] Responding to path challenge {}", hex(challenge)); - builder.encode_varint(FrameType::PathResponse); - builder.encode(&challenge[..]); + builder.encode_frame(FrameType::PathResponse, |b| { + b.encode(&challenge[..]); + }); // These frames are not retransmitted in the usual fashion. stats.path_response += 1; @@ -819,8 +822,9 @@ impl Path { if let ProbeState::ProbeNeeded { probe_count } = self.state { qtrace!("[{self}] Initiating path challenge {probe_count}"); let data = random::<8>(); - builder.encode_varint(FrameType::PathChallenge); - builder.encode(data); + builder.encode_frame(FrameType::PathChallenge, |b| { + b.encode(data); + }); // As above, no recovery token. stats.path_challenge += 1; @@ -883,9 +887,9 @@ impl Path { true } else if matches!(self.state, ProbeState::Valid) { // Retire validated, non-primary paths. - // Allow more than `2 * MAX_PATH_PROBES` times the PTO so that an old + // Allow more than `2 * Self::MAX_PROBES` times the PTO so that an old // path remains around until after a previous path fails. - let count = u32::try_from(2 * MAX_PATH_PROBES + 1).expect("result fits in u32"); + let count = u32::try_from(2 * Self::MAX_PROBES + 1).expect("result fits in u32"); self.validated .is_some_and(|validated| validated + (pto * count) > now) } else { @@ -1010,9 +1014,11 @@ impl Path { let ecn_ce_received = self.ecn_info.on_packets_acked(acked_pkts, ack_ecn, stats); if ecn_ce_received { - let cwnd_reduced = self - .sender - .on_ecn_ce_received(acked_pkts.first().expect("must be there"), now); + let cwnd_reduced = self.sender.on_ecn_ce_received( + acked_pkts.first().expect("must be there"), + now, + &mut stats.cc, + ); if cwnd_reduced { self.rtt.update_ack_delay(self.sender.cwnd(), self.plpmtu()); } diff --git a/third_party/rust/neqo-transport/src/pmtud.rs b/third_party/rust/neqo-transport/src/pmtud.rs @@ -13,7 +13,12 @@ use std::{ use neqo_common::{qdebug, qinfo, Buffer}; use static_assertions::const_assert; -use crate::{frame::FrameType, packet, recovery::sent, Stats}; +use crate::{ + frame::{FrameEncoder as _, FrameType}, + packet, + recovery::sent, + Stats, +}; // Values <= 1500 based on: A. Custura, G. Fairhurst and I. Learmonth, "Exploring Usable Path MTU in // the Internet," 2018 Network Traffic Measurement and Analysis Conference (TMA), Vienna, Austria, @@ -120,7 +125,7 @@ impl Pmtud { pub fn send_probe<B: Buffer>(&mut self, builder: &mut packet::Builder<B>, stats: &mut Stats) { // The packet may include ACK-eliciting data already, but rather than check for that, it // seems OK to burn one byte here to simply include a PING. - builder.encode_varint(FrameType::Ping); + builder.encode_frame(FrameType::Ping, |_| {}); stats.frame_tx.ping += 1; stats.pmtud_tx += 1; self.probe_count += 1; @@ -367,7 +372,7 @@ mod tests { crypto::CryptoDxState, packet, pmtud::{Probe, PMTU_RAISE_TIMER, SEARCH_TABLE_LEN}, - recovery::{self, sent, SendProfile}, + recovery::{sent, SendProfile}, Pmtud, Stats, }; @@ -381,17 +386,6 @@ mod tests { Some(u16::MAX as usize), ]; - fn make_sent_packet(pn: u64, now: Instant, len: usize) -> sent::Packet { - sent::Packet::new( - packet::Type::Short, - pn, - now, - true, - recovery::Tokens::new(), - len, - ) - } - /// Asserts that the PMTUD process has stopped at the given MTU. #[cfg(test)] fn assert_mtu(pmtud: &Pmtud, mtu: usize) { @@ -439,7 +433,7 @@ mod tests { assert!(!pmtud.needs_probe()); assert_eq!(stats_before.pmtud_tx + 1, stats.pmtud_tx); - let packet = make_sent_packet(pn, now, encoder.len()); + let packet = sent::make_packet(pn, now, encoder.len()); if encoder.len() + Pmtud::header_size(addr) <= mtu { pmtud.on_packets_acked(&[packet], now, stats); assert_eq!(stats_before.pmtud_ack + 1, stats.pmtud_ack); @@ -604,12 +598,12 @@ mod tests { // A packet of size 100 was lost, which is smaller than all probe sizes. // Loss counts should be unchanged. - pmtud.on_packets_lost(&[make_sent_packet(0, now, 100)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 100)], &mut stats, now); assert_eq!([0; SEARCH_TABLE_LEN], pmtud.loss_counts); // A packet of size 100_000 was lost, which is larger than all probe sizes. // Loss counts should be unchanged. - pmtud.on_packets_lost(&[make_sent_packet(0, now, 100_000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 100_000)], &mut stats, now); assert_eq!([0; SEARCH_TABLE_LEN], pmtud.loss_counts); pmtud.loss_counts.fill(0); // Reset the loss counts. @@ -617,18 +611,18 @@ mod tests { // A packet of size 1500 was lost, which should increase loss counts >= 1500 by one. let plen = MTU - pmtud.header_size; let mut expected_lc = search_table_inc(&pmtud, &pmtud.loss_counts, plen); - pmtud.on_packets_lost(&[make_sent_packet(0, now, plen)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, plen)], &mut stats, now); assert_eq!(expected_lc, pmtud.loss_counts); // A packet of size 2000 was lost, which should increase loss counts >= 2000 by one. expected_lc = search_table_inc(&pmtud, &expected_lc, 2000); - pmtud.on_packets_lost(&[make_sent_packet(0, now, 2000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 2000)], &mut stats, now); assert_eq!(expected_lc, pmtud.loss_counts); // A packet of size 5000 was lost, which should increase loss counts >= 5000 by one. There // have now been MAX_PROBES losses of packets >= 5000. That should stop PMTUD. expected_lc = search_table_inc(&pmtud, &expected_lc, 5000); - pmtud.on_packets_lost(&[make_sent_packet(0, now, 5000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 5000)], &mut stats, now); assert_mtu(&pmtud, 4095); expected_lc.fill(0); // Reset the loss counts. @@ -636,8 +630,8 @@ mod tests { expected_lc = search_table_inc(&pmtud, &expected_lc, 4000); pmtud.on_packets_lost( &[ - make_sent_packet(0, now, 4000), - make_sent_packet(1, now, 4000), + sent::make_packet(0, now, 4000), + sent::make_packet(1, now, 4000), ], &mut stats, now, @@ -648,8 +642,8 @@ mod tests { expected_lc = search_table_inc(&pmtud, &expected_lc, 2000); pmtud.on_packets_lost( &[ - make_sent_packet(0, now, 2000), - make_sent_packet(1, now, 2000), + sent::make_packet(0, now, 2000), + sent::make_packet(1, now, 2000), ], &mut stats, now, @@ -661,8 +655,8 @@ mod tests { let plen = MTU - pmtud.header_size; pmtud.on_packets_lost( &[ - make_sent_packet(0, now, plen), - make_sent_packet(1, now, plen), + sent::make_packet(0, now, plen), + sent::make_packet(1, now, plen), ], &mut stats, now, @@ -697,12 +691,12 @@ mod tests { // A packet of size 100 was ACKed, which is smaller than all probe sizes. // Loss counts should be unchanged. - pmtud.on_packets_acked(&[make_sent_packet(0, now, 100)], now, &mut stats); + pmtud.on_packets_acked(&[sent::make_packet(0, now, 100)], now, &mut stats); assert_eq!([0; SEARCH_TABLE_LEN], pmtud.loss_counts); // A packet of size 100_000 was ACKed, which is larger than all probe sizes. // Loss counts should be unchanged. - pmtud.on_packets_acked(&[make_sent_packet(0, now, 100_000)], now, &mut stats); + pmtud.on_packets_acked(&[sent::make_packet(0, now, 100_000)], now, &mut stats); assert_eq!([0; SEARCH_TABLE_LEN], pmtud.loss_counts); pmtud.loss_counts.fill(0); // Reset the loss counts. @@ -713,39 +707,39 @@ mod tests { // One packet of size 4000 was lost, which should increase loss counts >= 4000 by one. let mut expected_lc = search_table_inc(&pmtud, &pmtud.loss_counts, 4000); - pmtud.on_packets_lost(&[make_sent_packet(0, now, 4000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 4000)], &mut stats, now); assert_eq!(expected_lc, pmtud.loss_counts); // Now a packet of size 5000 is ACKed, which should reset all loss counts <= 5000. - pmtud.on_packets_acked(&[make_sent_packet(0, now, 5000)], now, &mut stats); + pmtud.on_packets_acked(&[sent::make_packet(0, now, 5000)], now, &mut stats); expected_lc = search_table_zero(&pmtud, &pmtud.loss_counts, 5000); assert_eq!(expected_lc, pmtud.loss_counts); // Now, one more packets of size 4000 was lost, which should increase loss counts >= 4000 // by one. expected_lc = search_table_inc(&pmtud, &expected_lc, 4000); - pmtud.on_packets_lost(&[make_sent_packet(0, now, 4000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 4000)], &mut stats, now); assert_eq!(expected_lc, pmtud.loss_counts); // Now a packet of size 8000 is ACKed, which should reset all loss counts <= 8000. - pmtud.on_packets_acked(&[make_sent_packet(0, now, 8000)], now, &mut stats); + pmtud.on_packets_acked(&[sent::make_packet(0, now, 8000)], now, &mut stats); expected_lc = search_table_zero(&pmtud, &pmtud.loss_counts, 8000); assert_eq!(expected_lc, pmtud.loss_counts); // Now, one more packets of size 9000 was lost, which should increase loss counts >= 9000 // by one. There have now been MAX_PROBES losses of packets >= 8191, but that is larger than // the current MTU, so nothing will happen. - pmtud.on_packets_lost(&[make_sent_packet(0, now, 9000)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 9000)], &mut stats, now); for _ in 0..2 { // One packet of size 1400 was lost, which should increase loss counts >= 1400 by one. expected_lc = search_table_inc(&pmtud, &pmtud.loss_counts, 1400); - pmtud.on_packets_lost(&[make_sent_packet(0, now, 1400)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 1400)], &mut stats, now); assert_eq!(expected_lc, pmtud.loss_counts); } // One packet of size 1400 was lost, which should increase loss counts >= 1400 by one. - pmtud.on_packets_lost(&[make_sent_packet(0, now, 1400)], &mut stats, now); + pmtud.on_packets_lost(&[sent::make_packet(0, now, 1400)], &mut stats, now); // This was the third loss of a packet <= the current MTU, which should trigger a PMTUD // restart. diff --git a/third_party/rust/neqo-transport/src/quic_datagrams.rs b/third_party/rust/neqo-transport/src/quic_datagrams.rs @@ -11,12 +11,11 @@ use std::{cmp::min, collections::VecDeque}; use neqo_common::{qdebug, Buffer, Encoder}; use crate::{ - events::OutgoingDatagramOutcome, frame::FrameType, packet, recovery, ConnectionEvents, Error, - Res, Stats, + events::OutgoingDatagramOutcome, + frame::{FrameEncoder as _, FrameType}, + packet, recovery, ConnectionEvents, Error, Res, Stats, }; -pub const MAX_QUIC_DATAGRAM: u64 = 65535; - /// Length of a [`FrameType::Datagram`] or [`FrameType::DatagramWithLen`] in /// QUIC varint encoding. pub const DATAGRAM_FRAME_TYPE_VARINT_LEN: usize = 1; @@ -41,12 +40,14 @@ impl From<Option<u64>> for DatagramTracking { } } -struct QuicDatagram { +pub struct QuicDatagram { data: Vec<u8>, tracking: DatagramTracking, } impl QuicDatagram { + pub const MAX_SIZE: u64 = 65535; + const fn tracking(&self) -> &DatagramTracking { &self.tracking } @@ -94,7 +95,7 @@ impl QuicDatagrams { } pub fn set_remote_datagram_size(&mut self, v: u64) { - self.remote_datagram_size = min(v, MAX_QUIC_DATAGRAM); + self.remote_datagram_size = min(v, QuicDatagram::MAX_SIZE); } /// This function tries to write a datagram frame into a packet. If the @@ -119,11 +120,13 @@ impl QuicDatagrams { + len + packet::Builder::MINIMUM_FRAME_SIZE { - builder.encode_varint(FrameType::DatagramWithLen); - builder.encode_vvec(dgram.as_ref()); + builder.encode_frame(FrameType::DatagramWithLen, |b| { + b.encode_vvec(dgram.as_ref()); + }); } else { - builder.encode_varint(FrameType::Datagram); - builder.encode(dgram.as_ref()); + builder.encode_frame(FrameType::Datagram, |b| { + b.encode(dgram.as_ref()); + }); builder.mark_full(); } debug_assert!(builder.len() <= builder.limit()); diff --git a/third_party/rust/neqo-transport/src/recovery/sent.rs b/third_party/rust/neqo-transport/src/recovery/sent.rs @@ -304,6 +304,20 @@ impl Packets { } } +/// Test helper to create a sent packet. +#[cfg(test)] +#[must_use] +pub fn make_packet(pn: packet::Number, sent_time: Instant, len: usize) -> Packet { + Packet::new( + packet::Type::Short, + pn, + sent_time, + true, + recovery::Tokens::new(), + len, + ) +} + #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { diff --git a/third_party/rust/neqo-transport/src/saved.rs b/third_party/rust/neqo-transport/src/saved.rs @@ -10,10 +10,6 @@ use neqo_common::{qdebug, qinfo, Datagram}; use crate::crypto::Epoch; -/// The number of datagrams that are saved during the handshake when -/// keys to decrypt them are not yet available. -pub const MAX_SAVED_DATAGRAMS: usize = 4; - pub struct SavedDatagram { /// The datagram. pub d: Datagram, @@ -29,6 +25,10 @@ pub struct SavedDatagrams { } impl SavedDatagrams { + /// The number of datagrams that are saved during the handshake when + /// keys to decrypt them are not yet available. + pub const CAPACITY: usize = 4; + fn store(&mut self, epoch: Epoch) -> &mut Vec<SavedDatagram> { match epoch { Epoch::Handshake => &mut self.handshake, @@ -39,14 +39,13 @@ impl SavedDatagrams { /// Return whether either store of datagrams is currently full. pub fn is_either_full(&self) -> bool { - self.handshake.len() == MAX_SAVED_DATAGRAMS - || self.application_data.len() == MAX_SAVED_DATAGRAMS + self.handshake.len() == Self::CAPACITY || self.application_data.len() == Self::CAPACITY } pub fn save(&mut self, epoch: Epoch, d: Datagram, t: Instant) { let store = self.store(epoch); - if store.len() < MAX_SAVED_DATAGRAMS { + if store.len() < Self::CAPACITY { qdebug!("saving {epoch:?} datagram of {} bytes", d.len()); store.push(SavedDatagram { d, t }); } else { diff --git a/third_party/rust/neqo-transport/src/send_stream.rs b/third_party/rust/neqo-transport/src/send_stream.rs @@ -20,11 +20,12 @@ use std::{ use indexmap::IndexMap; use neqo_common::{qdebug, qerror, qtrace, Buffer, Encoder, Role}; use smallvec::SmallVec; +use static_assertions::const_assert; use crate::{ events::ConnectionEvents, fc::SenderFlowControl, - frame::{Frame, FrameType}, + frame::{Frame, FrameEncoder as _, FrameType}, packet, recovery::{self, StreamRecoveryToken}, stats::FrameStats, @@ -34,17 +35,9 @@ use crate::{ TransportParameterId::{InitialMaxStreamDataBidiRemote, InitialMaxStreamDataUni}, TransportParameters, }, - AppError, Error, Res, + AppError, Error, Res, MAX_LOCAL_MAX_STREAM_DATA, }; -/// The maximum stream send buffer size. -/// -/// See [`crate::MAX_LOCAL_MAX_STREAM_DATA`] for an explanation of this -/// concrete value. -/// -/// 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. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)] pub enum TransmissionPriority { @@ -471,7 +464,16 @@ pub struct TxBuffer { ranges: RangeTracker, // ranges in buffer that have been sent or acked } +const_assert!(MAX_LOCAL_MAX_STREAM_DATA <= usize::MAX as u64); + impl TxBuffer { + /// The maximum stream send buffer size. + /// + /// See [`MAX_LOCAL_MAX_STREAM_DATA`] for an explanation of this + /// concrete value. + #[expect(clippy::cast_possible_truncation, reason = "Checked by const_assert!")] + pub const MAX_SIZE: usize = MAX_LOCAL_MAX_STREAM_DATA as usize; + #[must_use] pub fn new() -> Self { Self::default() @@ -479,10 +481,10 @@ impl TxBuffer { /// Attempt to add some or all of the passed-in buffer to the `TxBuffer`. pub fn send(&mut self, buf: &[u8]) -> usize { - let can_buffer = min(MAX_SEND_BUFFER_SIZE - self.buffered(), buf.len()); + let can_buffer = min(Self::MAX_SIZE - self.buffered(), buf.len()); if can_buffer > 0 { self.send_buf.extend(&buf[..can_buffer]); - debug_assert!(self.send_buf.len() <= MAX_SEND_BUFFER_SIZE); + debug_assert!(self.send_buf.len() <= Self::MAX_SIZE); } can_buffer } @@ -564,7 +566,7 @@ impl TxBuffer { } fn avail(&self) -> usize { - MAX_SEND_BUFFER_SIZE - self.buffered() + Self::MAX_SIZE - self.buffered() } fn used(&self) -> u64 { @@ -622,7 +624,7 @@ impl State { fn tx_avail(&self) -> usize { match self { // In Ready, TxBuffer not yet allocated but size is known - Self::Ready { .. } => MAX_SEND_BUFFER_SIZE, + Self::Ready { .. } => TxBuffer::MAX_SIZE, Self::Send { send_buf, .. } | Self::DataSent { send_buf, .. } => send_buf.avail(), Self::DataRecvd { .. } | Self::ResetSent { .. } | Self::ResetRecvd { .. } => 0, } @@ -940,16 +942,20 @@ impl SendStream { } // Write the stream out. - builder.encode_varint(Frame::stream_type(fin, offset > 0, fill)); - builder.encode_varint(id.as_u64()); - if offset > 0 { - builder.encode_varint(offset); - } + let frame_type = Frame::stream_type(fin, offset > 0, fill); + builder.encode_frame(frame_type, |b| { + b.encode_varint(id.as_u64()); + if offset > 0 { + b.encode_varint(offset); + } + if fill { + b.encode(&data[..length]); + } else { + b.encode_vvec(&data[..length]); + } + }); if fill { - builder.encode(&data[..length]); builder.mark_full(); - } else { - builder.encode_vvec(&data[..length]); } debug_assert!(builder.len() <= builder.limit()); @@ -1787,10 +1793,7 @@ mod tests { fc::SenderFlowControl, packet, recovery::{self, StreamRecoveryToken}, - send_stream::{ - RangeState, RangeTracker, SendStream, SendStreams, State, TxBuffer, - MAX_SEND_BUFFER_SIZE, - }, + send_stream::{RangeState, RangeTracker, SendStream, SendStreams, State, TxBuffer}, stats::FrameStats, ConnectionEvents, StreamId, INITIAL_LOCAL_MAX_STREAM_DATA, }; @@ -2790,7 +2793,7 @@ mod tests { connection_fc(FC_LIMIT), ConnectionEvents::default(), ); - assert_eq!(s.avail(), MAX_SEND_BUFFER_SIZE); + assert_eq!(s.avail(), TxBuffer::MAX_SIZE); } #[test] diff --git a/third_party/rust/neqo-transport/src/sender.rs b/third_party/rust/neqo-transport/src/sender.rs @@ -16,6 +16,7 @@ use crate::{ pmtud::Pmtud, recovery::sent, rtt::RttEstimate, + stats::CongestionControlStats, ConnectionParameters, Stats, }; @@ -96,7 +97,8 @@ impl PacketSender { now: Instant, stats: &mut Stats, ) { - self.cc.on_packets_acked(acked_pkts, rtt_est, now); + self.cc + .on_packets_acked(acked_pkts, rtt_est, now, &mut stats.cc); self.pmtud_mut().on_packets_acked(acked_pkts, now, stats); self.maybe_update_pacer_mtu(); } @@ -117,6 +119,7 @@ impl PacketSender { pto, lost_packets, now, + &mut stats.cc, ); // Call below may change the size of MTU probes, so it needs to happen after the CC // reaction above, which needs to ignore probes based on their size. @@ -126,8 +129,13 @@ impl PacketSender { } /// Called when ECN CE mark received. Returns true if the congestion window was reduced. - pub fn on_ecn_ce_received(&mut self, largest_acked_pkt: &sent::Packet, now: Instant) -> bool { - self.cc.on_ecn_ce_received(largest_acked_pkt, now) + pub fn on_ecn_ce_received( + &mut self, + largest_acked_pkt: &sent::Packet, + now: Instant, + cc_stats: &mut CongestionControlStats, + ) -> bool { + self.cc.on_ecn_ce_received(largest_acked_pkt, now, cc_stats) } pub fn discard(&mut self, pkt: &sent::Packet, now: Instant) { diff --git a/third_party/rust/neqo-transport/src/stats.rs b/third_party/rust/neqo-transport/src/stats.rs @@ -20,8 +20,6 @@ use strum::IntoEnumIterator as _; use crate::{ecn, packet}; -pub const MAX_PTO_COUNTS: usize = 16; - #[derive(Default, Clone, PartialEq, Eq)] pub struct FrameStats { pub ack: usize, @@ -136,6 +134,20 @@ pub struct DatagramStats { pub dropped_queue_full: usize, } +/// Congestion Control stats +#[derive(Default, Clone, PartialEq, Eq)] +pub struct CongestionControlStats { + /// Total number of congestion events caused by packet loss. + pub congestion_events_loss: usize, + /// Total number of congestion events caused by ECN-CE marked packets. + pub congestion_events_ecn: usize, + /// Number of spurious congestion events, where congestion was incorrectly inferred due to + /// packets initially considered lost but subsequently acknowledged. This indicates + /// instances where the congestion control algorithm overreacted to perceived losses. + pub congestion_events_spurious: usize, + /// Whether this connection has exited slow start. + pub slow_start_exited: bool, +} /// ECN counts by QUIC [`packet::Type`]. #[derive(Default, Clone, PartialEq, Eq)] pub struct EcnCount(EnumMap<packet::Type, ecn::Count>); @@ -283,7 +295,7 @@ pub struct Stats { /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted /// separately. - pub pto_counts: [usize; MAX_PTO_COUNTS], + pub pto_counts: [usize; Self::MAX_PTO_COUNTS], /// Count frames received. pub frame_rx: FrameStats, @@ -296,6 +308,8 @@ pub struct Stats { pub datagram_tx: DatagramStats, + pub cc: CongestionControlStats, + /// ECN path validation count, indexed by validation outcome. pub ecn_path_validation: ecn::ValidationCount, /// ECN counts for outgoing UDP datagrams, recorded locally. For coalesced packets, @@ -325,6 +339,8 @@ pub struct Stats { } impl Stats { + pub const MAX_PTO_COUNTS: usize = 16; + pub fn init(&mut self, info: String) { self.info = info; } @@ -344,7 +360,7 @@ impl Stats { /// When preconditions are violated. pub fn add_pto_count(&mut self, count: usize) { debug_assert!(count > 0); - if count >= MAX_PTO_COUNTS { + if count >= Self::MAX_PTO_COUNTS { // We can't move this count any further, so stop. return; } @@ -371,6 +387,14 @@ impl Debug for Stats { )?; writeln!( f, + " cc: ce_loss {} ce_ecn {} ce_spurious {}", + self.cc.congestion_events_loss, + self.cc.congestion_events_ecn, + self.cc.congestion_events_spurious, + )?; + writeln!(f, " ss_exit: {}", self.cc.slow_start_exited)?; + writeln!( + f, " pmtud: {} sent {} acked {} lost {} change {} iface_mtu {} pmtu", self.pmtud_tx, self.pmtud_ack, diff --git a/third_party/rust/neqo-transport/src/tparams.rs b/third_party/rust/neqo-transport/src/tparams.rs @@ -355,7 +355,13 @@ impl TransportParameters { /// Decode is a static function that parses transport parameters /// using the provided decoder. - pub(crate) fn decode(d: &mut Decoder) -> Res<Self> { + /// + /// # Errors + /// When the transport parameters are malformed. + pub fn decode(d: &mut Decoder) -> Res<Self> { + #[cfg(feature = "build-fuzzing-corpus")] + neqo_common::write_item_to_fuzzing_corpus("tparams", d.as_ref()); + let mut tps = Self::default(); qtrace!("Parsed fixed TP header"); @@ -376,7 +382,11 @@ impl TransportParameters { } pub(crate) fn encode<B: Buffer>(&self, enc: &mut Encoder<B>) { + #[cfg(feature = "build-fuzzing-corpus")] + let start = enc.len(); self.encode_filtered(Self::retain_all, enc); + #[cfg(feature = "build-fuzzing-corpus")] + neqo_common::write_item_to_fuzzing_corpus("tparams", &enc.as_ref()[start..]); } fn encode_filtered<F, B: Buffer>(&self, f: F, enc: &mut Encoder<B>) diff --git a/third_party/rust/neqo-transport/src/tracking.rs b/third_party/rust/neqo-transport/src/tracking.rs @@ -23,7 +23,7 @@ use strum::{Display, EnumIter}; use crate::{ ecn, - frame::FrameType, + frame::{FrameEncoder as _, FrameType}, packet, recovery::{self}, stats::FrameStats, @@ -457,14 +457,8 @@ impl RecvdPackets { return; } - builder.encode_varint(if self.ecn_count.is_some() { - FrameType::AckEcn - } else { - FrameType::Ack - }); let mut iter = ranges.iter(); let Some(first) = iter.next() else { return }; - builder.encode_varint(first.largest); stats.largest_acknowledged = first.largest; stats.ack += 1; @@ -475,27 +469,38 @@ impl RecvdPackets { // We use the default exponent, so delay is in multiples of 8 microseconds. let ack_delay = u64::try_from(elapsed.as_micros() / 8).unwrap_or(u64::MAX); let ack_delay = min(MAX_VARINT, ack_delay); - builder.encode_varint(ack_delay); let Ok(extra_ranges) = u64::try_from(ranges.len() - 1) else { return; }; - builder.encode_varint(extra_ranges); // extra ranges - builder.encode_varint(first.len() - 1); // first range - - let mut last = first.smallest; - for r in iter { - // the difference must be at least 2 because 0-length gaps, - // (difference 1) are illegal. - builder.encode_varint(last - r.largest - 2); // Gap - builder.encode_varint(r.len() - 1); // Range - last = r.smallest; - } - if self.ecn_count.is_some() { - builder.encode_varint(self.ecn_count[Ecn::Ect0]); - builder.encode_varint(self.ecn_count[Ecn::Ect1]); - builder.encode_varint(self.ecn_count[Ecn::Ce]); - } + builder.encode_frame( + if self.ecn_count.is_some() { + FrameType::AckEcn + } else { + FrameType::Ack + }, + |b| { + b.encode_varint(first.largest); + b.encode_varint(ack_delay); + b.encode_varint(extra_ranges); // extra ranges + b.encode_varint(first.len() - 1); // first range + + let mut last = first.smallest; + for r in iter { + // The difference must be at least 2 because 0-length gaps, + // (difference 1) are illegal. + b.encode_varint(last - r.largest - 2); // Gap + b.encode_varint(r.len() - 1); // Range + last = r.smallest; + } + + if self.ecn_count.is_some() { + b.encode_varint(self.ecn_count[Ecn::Ect0]); + b.encode_varint(self.ecn_count[Ecn::Ect1]); + b.encode_varint(self.ecn_count[Ecn::Ce]); + } + }, + ); // We've sent an ACK, reset the timer. self.ack_time = None; 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":"f6338dbe90fdc85795d1faa5e955f2cc1a3d00936bb4d5a206f7aa1ba8fbebe7","build.rs":"bf57cd35a78f636c14c442c1926abc2deca3d137e9d207e4f2f960f5b8363b07","src/lib.rs":"bb87c16ab8587eb2e3a68b948de6cc0d36a469194f243bfd6b33fcf31c9e6a2d"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"db443fbc30c0baf702a98b4611d16828055df2d167817a55d0f75830536e26e7","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.19.0" +version = "0.20.0" authors = ["The Neqo Authors <necko@mozilla.com>"] build = "build.rs" autolib = false @@ -168,4 +168,4 @@ unused_qualifications = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 -check-cfg = ["cfg(coverage,coverage_nightly)"] +check-cfg = ["cfg(coverage,coverage_nightly,fuzzing)"]