Bug 1959128 - update neqo to v0.13 r=leggert,kershaw,necko-reviewers,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D245341
This commit is contained in:
Max Leonard Inden
2025-04-14 14:56:15 +00:00
parent a148a1cdd9
commit d18a9f5c74
199 changed files with 6373 additions and 3638 deletions

View File

@@ -100,9 +100,9 @@ git = "https://github.com/mozilla/mp4parse-rust"
rev = "e64650a686e5c5732395cd059e17cfd3b1e5b63b"
replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/neqo?tag=v0.12.2"]
[source."git+https://github.com/mozilla/neqo?tag=v0.13.1"]
git = "https://github.com/mozilla/neqo"
tag = "v0.12.2"
tag = "v0.13.1"
replace-with = "vendored-sources"
[source."git+https://github.com/servo/rust-cssparser?rev=958a3f098acb92ddacdce18a7ef2c4a87ac3326f"]

43
Cargo.lock generated
View File

@@ -4522,8 +4522,8 @@ dependencies = [
[[package]]
name = "neqo-bin"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"clap",
"clap-verbosity-flag",
@@ -4544,35 +4544,38 @@ dependencies = [
[[package]]
name = "neqo-common"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"enum-map",
"env_logger",
"log",
"qlog",
"strum",
"windows",
]
[[package]]
name = "neqo-crypto"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"bindgen 0.69.4",
"enum-map",
"log",
"mozbuild",
"neqo-common",
"semver",
"serde",
"serde_derive",
"strum",
"toml",
]
[[package]]
name = "neqo-http3"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"enumset",
"log",
@@ -4582,13 +4585,14 @@ dependencies = [
"neqo-transport",
"qlog",
"sfv",
"strum",
"url",
]
[[package]]
name = "neqo-qpack"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"log",
"neqo-common",
@@ -4599,10 +4603,11 @@ dependencies = [
[[package]]
name = "neqo-transport"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"enum-map",
"enumset",
"indexmap",
"log",
"mtu",
@@ -4611,12 +4616,13 @@ dependencies = [
"qlog",
"smallvec",
"static_assertions",
"strum",
]
[[package]]
name = "neqo-udp"
version = "0.12.2"
source = "git+https://github.com/mozilla/neqo?tag=v0.12.2#f8946d5187271b3e63e8d0209343510bdeac1451"
version = "0.13.1"
source = "git+https://github.com/mozilla/neqo?tag=v0.13.1#0a356afeb45631036df6b3028951a02d16fa8d27"
dependencies = [
"cfg_aliases",
"log",
@@ -5284,12 +5290,11 @@ dependencies = [
[[package]]
name = "qlog"
version = "0.13.0"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b5f65b920fa913ce92267bb3c4ed3b9c2f81d05f8e1376c3bbc95455eedb7df"
checksum = "0f15b83c59e6b945f2261c95a1dd9faf239187f32ff0a96af1d1d28c4557f919"
dependencies = [
"serde",
"serde_derive",
"serde_json",
"serde_with",
"smallvec",
@@ -5303,9 +5308,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quinn-udp"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944"
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
dependencies = [
"cfg_aliases",
"libc",

View File

@@ -10,17 +10,17 @@ name = "neqo_glue"
[dependencies]
firefox-on-glean = { path = "../../../toolkit/components/glean/api" }
neqo-udp = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo", features = ["gecko"] }
neqo-common = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-udp = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo", features = ["gecko"] }
neqo-common = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
xpcom = { path = "../../../xpcom/rust/xpcom" }
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
log = "0.4.0"
qlog = "0.13"
qlog = "0.15.1"
libc = "0.2.0"
static_assertions = "1.1"
static_prefs = { path = "../../../modules/libpref/init/static_prefs"}
@@ -30,7 +30,7 @@ uuid = { version = "1.0", features = ["v4"] }
winapi = {version = "0.3", features = ["ws2def"] }
[dependencies.neqo-crypto]
tag = "v0.12.2"
tag = "v0.13.1"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@@ -261,7 +261,10 @@ impl NeqoHttp3Conn {
.max_stream_data(StreamType::BiDi, false, max_stream_data)
.grease(static_prefs::pref!("security.tls.grease_http3_enable"))
.sni_slicing(static_prefs::pref!("network.http.http3.sni-slicing"))
.idle_timeout(Duration::from_secs(idle_timeout.into()));
.idle_timeout(Duration::from_secs(idle_timeout.into()))
// Disabled on OpenBSD. See <https://bugzilla.mozilla.org/show_bug.cgi?id=1952304>.
.pmtud_iface_mtu(cfg!(not(target_os = "openbsd")))
.mlkem(false);
// Set a short timeout when fuzzing.
#[cfg(feature = "fuzzing")]
@@ -417,10 +420,10 @@ impl NeqoHttp3Conn {
}
if static_prefs::pref!("network.http.http3.ecn") && stats.frame_rx.handshake_done != 0 {
if stats.ecn_tx[IpTosEcn::Ect0] > 0 {
if let Ok(ratio) = i64::try_from(
(stats.ecn_tx[IpTosEcn::Ce] * PRECISION_FACTOR) / stats.ecn_tx[IpTosEcn::Ect0],
) {
let tx_ect0_sum: u64 = stats.ecn_tx.into_values().map(|v| v[IpTosEcn::Ect0]).sum();
let tx_ce_sum: u64 = stats.ecn_tx.into_values().map(|v| v[IpTosEcn::Ce]).sum();
if tx_ect0_sum > 0 {
if let Ok(ratio) = i64::try_from((tx_ce_sum * PRECISION_FACTOR) / tx_ect0_sum) {
glean::http_3_ecn_ce_ect0_ratio_sent.accumulate_single_sample_signed(ratio);
} else {
let msg = "Failed to convert ratio to i64 for use with glean";
@@ -428,10 +431,10 @@ impl NeqoHttp3Conn {
debug_assert!(false, "{msg}");
}
}
if stats.ecn_rx[IpTosEcn::Ect0] > 0 {
if let Ok(ratio) = i64::try_from(
(stats.ecn_rx[IpTosEcn::Ce] * PRECISION_FACTOR) / stats.ecn_rx[IpTosEcn::Ect0],
) {
let rx_ect0_sum: u64 = stats.ecn_rx.into_values().map(|v| v[IpTosEcn::Ect0]).sum();
let rx_ce_sum: u64 = stats.ecn_rx.into_values().map(|v| v[IpTosEcn::Ce]).sum();
if rx_ect0_sum > 0 {
if let Ok(ratio) = i64::try_from((rx_ce_sum * PRECISION_FACTOR) / rx_ect0_sum) {
glean::http_3_ecn_ce_ect0_ratio_received.accumulate_single_sample_signed(ratio);
} else {
let msg = "Failed to convert ratio to i64 for use with glean";
@@ -1159,7 +1162,6 @@ impl From<TransportError> for CloseError {
TransportError::ConnectionIdLimitExceeded => Self::TransportInternalErrorOther(1),
TransportError::ConnectionIdsExhausted => Self::TransportInternalErrorOther(2),
TransportError::ConnectionState => Self::TransportInternalErrorOther(3),
TransportError::DecodingFrame => Self::TransportInternalErrorOther(4),
TransportError::DecryptError => Self::TransportInternalErrorOther(5),
TransportError::IntegerOverflow => Self::TransportInternalErrorOther(7),
TransportError::InvalidInput => Self::TransportInternalErrorOther(8),
@@ -1184,6 +1186,7 @@ impl From<TransportError> for CloseError {
TransportError::QlogError => Self::TransportInternalErrorOther(27),
TransportError::NotAvailable => Self::TransportInternalErrorOther(28),
TransportError::DisabledVersion => Self::TransportInternalErrorOther(29),
TransportError::UnknownTransportParameter => Self::TransportInternalErrorOther(30),
}
}
}
@@ -1213,7 +1216,6 @@ const fn transport_error_to_glean_label(error: &TransportError) -> &'static str
TransportError::ConnectionIdLimitExceeded => "ConnectionIdLimitExceeded",
TransportError::ConnectionIdsExhausted => "ConnectionIdsExhausted",
TransportError::ConnectionState => "ConnectionState",
TransportError::DecodingFrame => "DecodingFrame",
TransportError::DecryptError => "DecryptError",
TransportError::DisabledVersion => "DisabledVersion",
TransportError::IdleTimeout => "IdleTimeout",
@@ -1242,6 +1244,7 @@ const fn transport_error_to_glean_label(error: &TransportError) -> &'static str
TransportError::UnknownFrameType => "UnknownFrameType",
TransportError::VersionNegotiation => "VersionNegotiation",
TransportError::WrongRole => "WrongRole",
TransportError::UnknownTransportParameter => "UnknownTransportParameter",
}
}

View File

@@ -6,11 +6,11 @@ edition = "2021"
license = "MPL-2.0"
[dependencies]
neqo-bin = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo", features = ["gecko"] }
neqo-common = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.12.2", git = "https://github.com/mozilla/neqo" }
neqo-bin = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo", features = ["gecko"] }
neqo-common = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.13.1", git = "https://github.com/mozilla/neqo" }
log = "0.4.0"
base64 = "0.22"
cfg-if = "1.0"
@@ -20,7 +20,7 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
mozilla-central-workspace-hack = { version = "0.1", features = ["http3server"], optional = true }
[dependencies.neqo-crypto]
tag = "v0.12.2"
tag = "v0.13.1"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@@ -170,7 +170,7 @@ impl Http3TestServer {
}
impl HttpServer for Http3TestServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output {
let output = self.server.process(dgram, now);
let output = if self.sessions_to_close.is_empty() {
@@ -662,7 +662,7 @@ impl ::std::fmt::Display for Server {
}
impl HttpServer for Server {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output {
self.0.process(dgram, now)
}
@@ -898,7 +898,7 @@ impl Http3ProxyServer {
}
impl HttpServer for Http3ProxyServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output {
let output = self.server.process(dgram, now);
#[cfg(not(target_os = "android"))]
@@ -1027,7 +1027,7 @@ impl ::std::fmt::Display for NonRespondingServer {
}
impl HttpServer for NonRespondingServer {
fn process(&mut self, _dgram: Option<Datagram<&[u8]>>, _now: Instant) -> Output {
fn process(&mut self, _dgram: Option<Datagram<&mut [u8]>>, _now: Instant) -> Output {
Output::None
}

View File

@@ -4278,6 +4278,11 @@ who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.12.0 -> 0.13.0"
[[audits.qlog]]
who = "Max Leonard Inden <mail@max-inden.de>"
criteria = "safe-to-deploy"
delta = "0.13.0 -> 0.15.2"
[[audits.quinn-udp]]
who = "Kershaw Chang <kershaw@mozilla.com>"
criteria = "safe-to-run"
@@ -4309,6 +4314,11 @@ who = "Max Leonard Inden <mail@max-inden.de>"
criteria = "safe-to-deploy"
delta = "0.5.9 -> 0.5.10"
[[audits.quinn-udp]]
who = "Max Leonard Inden <mail@max-inden.de>"
criteria = "safe-to-deploy"
delta = "0.5.10 -> 0.5.11"
[[audits.quote]]
who = "Nika Layzell <nika@thelayzells.com>"
criteria = "safe-to-deploy"

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"699f5a52f9533f1d44ad6c43324ec957d7ede0299657690146b8854536ce41e7","benches/main.rs":"fcfd00665a1c4eb2118c74dd3e066e451bed655a42ae5c92daee38a31b69c2fb","src/bin/client.rs":"d7dc368d9b3a3a7a391fd982509ce66afa09ede9ca54dc1328bfc222e69379dc","src/bin/server.rs":"f297d7d00613291e5a058772c7e416b55ad8a25e6f0cf438c7b7f836fb470135","src/client/http09.rs":"86726bac98edbcab5a41ee5e4614637944730032fb31354cb68df917992da9bd","src/client/http3.rs":"820d359d83c6d6df1ce463cf2780b4541d0bd880338b8bf7840b6f01f14732ec","src/client/mod.rs":"3a8d426736c892a69acf543ab469540b631a43ab7c18e8f4e1f74aa0f56806e2","src/lib.rs":"68936ae531d5ab903b0063e3c8a7cd49511c2bf87c42706f23e8968e8a5c3d89","src/send_data.rs":"7537b7a0f67e9077cf121cc3d0fcd42edb085801dd4baf25b14c22a01f1bf04f","src/server/http09.rs":"d71cd79bba792799f303abdc15abe10e843bc0dd4de3efa27f3d11bf82798de2","src/server/http3.rs":"56f45922eebcfa30d7d9ceb6bac97d6f725d4c803c7aa0836c6e4d21b8d4588e","src/server/mod.rs":"6286666e692c43e52ddb3e837c9630694a446101c0c8cff520a816ce25c247df","src/udp.rs":"9fbd463a87c7a1f0873a003c36631b3b02af6e1a3d215b5a12a1e2822c207362"},"package":null}
{"files":{"Cargo.toml":"632822ea728db24db8ccbba6a7b38431bba66b1c7f090e50c9bc254f16c7fa16","benches/main.rs":"7bd395227024b062e87eff0e7e2afa4a2dc70d7497c999aa665709577818f466","src/bin/client.rs":"71952208bcee80e8ff37a5dfe7cac19940274157e8ecf8acc6edc4d918c813b7","src/bin/server.rs":"88d3b2310f4a899053603cce4fd03703f26a8b91ff487bf5942b6e45c135a9fa","src/client/http09.rs":"b0b3ff496dbf35fe582a3f02780792bba34cb510d2acbcb43023180bec27cae1","src/client/http3.rs":"3f7bd425fa3bbdea005c91be6f25c861dc20819c2aeee42d4ffab7a7e3ef7282","src/client/mod.rs":"9185f1039d1e920a720330ec8b63f8b42d3924c26e95b6636436c9c5e1ae20a1","src/lib.rs":"ced2561bbebce6440b8ee902316afe7ca9df8dfeb632053587854838815e7c74","src/send_data.rs":"7537b7a0f67e9077cf121cc3d0fcd42edb085801dd4baf25b14c22a01f1bf04f","src/server/http09.rs":"b5236d9cc7b3c6c46cef9be521bc85e1b618960c0315465760a3d58e92c82c32","src/server/http3.rs":"2c826fb81a792221878a6d16bcaf6f1bed88fe0b424315c262eb08479ac5b4c4","src/server/mod.rs":"1c4c453452e5788ea3b3ab0c2d2193a291a39426466a28d8a0b384206392d485","src/udp.rs":"d68cd0fba6a0e679f79cce5c443bceba48c6ddbd2b5f492a075c9da8765b3047"},"package":null}

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.76.0"
rust-version = "1.82.0"
name = "neqo-bin"
version = "0.12.2"
version = "0.13.1"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = false
autolib = false
@@ -72,7 +72,7 @@ harness = false
required-features = ["bench"]
[dependencies.clap]
version = "4.4"
version = "4.5"
features = [
"std",
"help",
@@ -118,11 +118,11 @@ path = "./../neqo-transport"
path = "./../neqo-udp"
[dependencies.qlog]
version = "0.13"
version = "0.15.1"
default-features = false
[dependencies.quinn-udp]
version = "0.5.6"
version = "0.5.11"
features = [
"direct-log",
"fast-apple-datapath",
@@ -141,12 +141,11 @@ features = [
"time",
"macros",
"rt",
"rt-multi-thread",
]
default-features = false
[dependencies.url]
version = "2.5.3"
version = "2.5"
features = ["std"]
default-features = false
@@ -173,22 +172,56 @@ features = ["sync"]
default-features = false
[lints.clippy]
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
cfg_not_test = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
infinite_loop = "warn"
large_include_file = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
literal_string_with_formatting_args = "allow"
lossy_float_literal = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
module_name_repetitions = "warn"
multiple_crate_versions = "allow"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
pathbuf_init_then_push = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
self_named_module_files = "warn"
semicolon_inside_block = "warn"
string_lit_chars_any = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
try_err = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[lints.clippy.cargo]
level = "warn"
@@ -205,7 +238,6 @@ priority = -1
[lints.rust]
absolute_paths_not_starting_with_crate = "warn"
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
@@ -217,3 +249,4 @@ unit_bindings = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

View File

@@ -4,11 +4,13 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::unwrap_used, reason = "OK in a bench.")]
use std::{env, path::PathBuf, str::FromStr as _};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput};
use neqo_bin::{client, server};
use tokio::runtime::Runtime;
use tokio::runtime::Builder;
struct Benchmark {
name: String,
@@ -42,7 +44,7 @@ fn transfer(c: &mut Criterion) {
upload: false,
},
Benchmark {
name: format!("1-conn/1-100mb-resp{mtu} (aka. Upload)"),
name: format!("1-conn/1-100mb-req{mtu} (aka. Upload)"),
requests: vec![100 * 1024 * 1024],
upload: true,
},
@@ -55,7 +57,8 @@ fn transfer(c: &mut Criterion) {
Throughput::Elements(requests.len() as u64)
});
group.bench_function("client", |b| {
b.to_async(Runtime::new().unwrap()).iter_batched(
b.to_async(Builder::new_current_thread().enable_all().build().unwrap())
.iter_batched(
|| client::client(client::Args::new(&requests, upload)),
|client| async move {
client.await.unwrap();
@@ -69,11 +72,19 @@ fn transfer(c: &mut Criterion) {
done_sender.send(()).unwrap();
}
#[allow(clippy::redundant_pub_crate)] // Bug in clippy nursery? Not sure how this lint could fire here.
#[allow(
clippy::allow_attributes,
clippy::redundant_pub_crate,
reason = "TODO: Bug in clippy nursery?"
)]
fn spawn_server() -> tokio::sync::oneshot::Sender<()> {
let (done_sender, mut done_receiver) = tokio::sync::oneshot::channel();
std::thread::spawn(move || {
Runtime::new().unwrap().block_on(async {
Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let mut server = Box::pin(server::server(server::Args::default()));
tokio::select! {
_ = &mut done_receiver => {}

View File

@@ -6,7 +6,7 @@
use clap::Parser as _;
#[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), neqo_bin::client::Error> {
let args = neqo_bin::client::Args::parse();

View File

@@ -6,7 +6,7 @@
use clap::Parser as _;
#[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), neqo_bin::server::Error> {
let args = neqo_bin::server::Args::parse();

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::unwrap_used, reason = "This is example code.")]
//! An [HTTP 0.9](https://www.w3.org/Protocols/HTTP/AsImplemented.html) client implementation.
use std::{
@@ -159,7 +161,11 @@ pub fn create_client(
client.set_ciphers(&ciphers)?;
}
client.set_qlog(qlog_new(args, hostname, client.odcid().unwrap())?);
client.set_qlog(qlog_new(
args,
hostname,
client.odcid().ok_or(Error::InternalError)?,
)?);
Ok(client)
}
@@ -189,7 +195,7 @@ impl super::Client for Connection {
fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
) {
self.process_multiple_input(dgrams, now);
@@ -302,7 +308,7 @@ impl<'b> Handler<'b> {
} else {
qdebug!(
"READ[{stream_id}]: {}",
std::str::from_utf8(read_buffer).unwrap()
std::str::from_utf8(read_buffer).map_err(|_| Error::InvalidInput)?
);
}
if fin {

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::unwrap_used, reason = "This is example code.")]
//! An HTTP 3 client implementation.
use std::{
@@ -18,7 +20,7 @@ use std::{
time::Instant,
};
use neqo_common::{event::Provider, hex, qdebug, qinfo, qwarn, Datagram, Header};
use neqo_common::{event::Provider, hex, qdebug, qerror, qinfo, qwarn, Datagram, Header};
use neqo_crypto::{AuthenticationStatus, ResumptionToken};
use neqo_http3::{Error, Http3Client, Http3ClientEvent, Http3Parameters, Http3State, Priority};
use neqo_transport::{
@@ -31,7 +33,7 @@ use super::{get_output_file, qlog_new, Args, CloseState, Res};
use crate::{send_data::SendData, STREAM_IO_BUFFER_SIZE};
pub struct Handler<'a> {
#[allow(clippy::struct_field_names)]
#[expect(clippy::struct_field_names, reason = "This name is more descriptive.")]
url_handler: UrlHandler<'a>,
token: Option<ResumptionToken>,
output_read_data: bool,
@@ -137,7 +139,7 @@ impl super::Client for Http3Client {
fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
) {
self.process_multiple_input(dgrams, now);
@@ -200,9 +202,11 @@ impl super::Handler for Handler<'_> {
qwarn!("Data on unexpected stream: {stream_id}");
}
Some(handler) => loop {
let (sz, fin) = client
.read_data(Instant::now(), stream_id, &mut self.read_buffer)
.expect("Read should succeed");
let (sz, fin) = client.read_data(
Instant::now(),
stream_id,
&mut self.read_buffer,
)?;
handler.process_data_readable(
stream_id,
@@ -270,7 +274,7 @@ trait StreamHandler {
fin: bool,
data: &[u8],
output_read_data: bool,
) -> Res<bool>;
) -> Res<()>;
fn process_data_writable(&mut self, client: &mut Http3Client, stream_id: StreamId);
}
@@ -291,12 +295,12 @@ impl StreamHandler for DownloadStreamHandler {
fin: bool,
data: &[u8],
output_read_data: bool,
) -> Res<bool> {
) -> Res<()> {
if let Some(out_file) = &mut self.out_file {
if !data.is_empty() {
out_file.write_all(data)?;
}
return Ok(true);
return Ok(());
} else if !output_read_data {
qdebug!("READ[{stream_id}]: {} bytes", data.len());
} else if let Ok(txt) = std::str::from_utf8(data) {
@@ -313,7 +317,7 @@ impl StreamHandler for DownloadStreamHandler {
}
}
Ok(true)
Ok(())
}
fn process_data_writable(&mut self, _client: &mut Http3Client, _stream_id: StreamId) {}
@@ -335,18 +339,19 @@ impl StreamHandler for UploadStreamHandler {
_fin: bool,
data: &[u8],
_output_read_data: bool,
) -> Res<bool> {
) -> Res<()> {
if let Ok(txt) = std::str::from_utf8(data) {
let trimmed_txt = txt.trim_end_matches(char::from(0));
let parsed: usize = trimmed_txt.parse().unwrap();
let parsed: usize = trimmed_txt.parse().map_err(|_| Error::InvalidInput)?;
if parsed == self.data.len() {
let upload_time = Instant::now().duration_since(self.start);
qinfo!("Stream ID: {stream_id:?}, Upload time: {upload_time:?}");
}
Ok(())
} else {
panic!("Unexpected data [{stream_id}]: 0x{}", hex(data));
qerror!("Unexpected data [{stream_id}]: 0x{}", hex(data));
Err(crate::client::Error::Http3Error(Error::InvalidInput))
}
Ok(true)
}
fn process_data_writable(&mut self, client: &mut Http3Client, stream_id: StreamId) {

View File

@@ -4,7 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::future_not_send)]
#![expect(clippy::unwrap_used, reason = "This is example code.")]
use std::{
collections::{HashMap, VecDeque},
@@ -104,7 +104,10 @@ type Res<T> = Result<T, Error>;
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
#[allow(clippy::struct_excessive_bools)] // Not a good use of that lint.
#[expect(
clippy::struct_excessive_bools,
reason = "Not a good use of that lint."
)]
pub struct Args {
#[command(flatten)]
shared: SharedArgs,
@@ -180,7 +183,7 @@ pub struct Args {
impl Args {
#[must_use]
#[cfg(any(test, feature = "bench"))]
#[allow(clippy::missing_panics_doc)]
#[expect(clippy::missing_panics_doc, reason = "This is example code.")]
pub fn new(requests: &[usize], upload: bool) -> Self {
use std::str::FromStr as _;
Self {
@@ -383,7 +386,7 @@ trait Client {
fn process_output(&mut self, now: Instant) -> Output;
fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
);
fn has_events(&self) -> bool;
@@ -431,17 +434,14 @@ impl<'a, H: Handler> Runner<'a, H> {
continue;
}
#[allow(clippy::match_same_arms)]
match (handler_done, self.client.is_closed()?) {
// more work
(false, _) => {}
// more work; or no more work, already closing connection
(true, CloseState::Closing) | (false, _) => {}
// no more work, closing connection
(true, CloseState::NotClosing) => {
self.client.close(Instant::now(), 0, "kthxbye!");
continue;
}
// no more work, already closing connection
(true, CloseState::Closing) => {}
// no more work, connection closed, terminating
(true, CloseState::Closed) => break,
}
@@ -464,10 +464,19 @@ impl<'a, H: Handler> Runner<'a, H> {
async fn process_output(&mut self) -> Result<(), io::Error> {
loop {
match self.client.process_output(Instant::now()) {
Output::Datagram(dgram) => {
Output::Datagram(dgram) => loop {
// Optimistically attempt sending datagram. In case the OS
// buffer is full, wait till socket is writable then try
// again.
match self.socket.send(&dgram) {
Ok(()) => break,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
self.socket.writable().await?;
self.socket.send(&dgram)?;
// Now try again.
}
e @ Err(_) => return e,
}
},
Output::Callback(new_timeout) => {
qdebug!("Setting timeout of {new_timeout:?}");
self.timeout = Some(Box::pin(tokio::time::sleep(new_timeout)));
@@ -542,6 +551,12 @@ fn urls_by_origin(urls: &[Url]) -> impl Iterator<Item = ((Host, u16), VecDeque<U
})
}
#[expect(
clippy::future_not_send,
clippy::missing_panics_doc,
clippy::missing_errors_doc,
reason = "This is example code."
)]
pub async fn client(mut args: Args) -> Res<()> {
neqo_common::log::init(
args.shared

View File

@@ -4,9 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_errors_doc)]
use std::{
fmt::{self, Display},
net::{SocketAddr, ToSocketAddrs as _},
@@ -168,8 +165,8 @@ impl QuicParameters {
assert_eq!(
opt.is_some(),
addr.is_some(),
"unable to resolve '{}' to an {v} address",
opt.as_ref().unwrap(),
"unable to resolve '{:?}' to an {v} address",
opt.as_ref()?,
);
addr
}

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::unwrap_used, reason = "This is example code.")]
use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant};
use neqo_common::{event::Provider as _, hex, qdebug, qerror, qinfo, qwarn, Datagram};
@@ -55,10 +57,10 @@ impl HttpServer {
server.set_validation(ValidateAddress::Always);
}
if args.ech {
let (sk, pk) = generate_ech_keys().expect("generate ECH keys");
let (sk, pk) = generate_ech_keys().map_err(|_| Error::Internal)?;
server
.enable_ech(random::<1>()[0], "public.example", &sk, &pk)
.expect("enable ECH");
.map_err(|_| Error::Internal)?;
let cfg = server.ech_config();
qinfo!("ECHConfigList: {}", hex(cfg));
}
@@ -70,9 +72,9 @@ impl HttpServer {
read_state: HashMap::new(),
is_qns_test,
regex: if is_qns_test {
Regex::new(r"GET +/(\S+)(?:\r)?\n").unwrap()
Regex::new(r"GET +/(\S+)(?:\r)?\n").map_err(|_| Error::Internal)?
} else {
Regex::new(r"GET +/(\d+)(?:\r)?\n").unwrap()
Regex::new(r"GET +/(\d+)(?:\r)?\n").map_err(|_| Error::Internal)?
},
read_buffer: vec![0; STREAM_IO_BUFFER_SIZE],
})
@@ -185,14 +187,15 @@ impl HttpServer {
}
impl super::HttpServer for HttpServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output {
self.server.process(dgram, now)
}
fn process_events(&mut self, now: Instant) {
// `ActiveConnectionRef` `Hash` implementation doesnt access any of the interior mutable
// types.
#[allow(clippy::mutable_key_type)]
#[expect(
clippy::mutable_key_type,
reason = "ActiveConnectionRef::Hash doesn't access any of the interior mutable types"
)]
let active_conns = self.server.active_connections();
for acr in active_conns {
loop {

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::unwrap_used, reason = "This is example code.")]
use std::{
cell::RefCell,
collections::HashMap,
@@ -80,7 +82,7 @@ impl Display for HttpServer {
}
impl super::HttpServer for HttpServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> neqo_http3::Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> neqo_http3::Output {
self.server.process(dgram, now)
}

View File

@@ -4,7 +4,17 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::future_not_send)]
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
#![expect(
clippy::unwrap_used,
clippy::future_not_send,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
reason = "This is example code."
)]
use std::{
cell::RefCell,
@@ -190,14 +200,12 @@ fn qns_read_response(filename: &str) -> Result<Vec<u8>, io::Error> {
fs::read(path)
}
#[allow(clippy::module_name_repetitions)]
pub trait HttpServer: Display {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output;
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output;
fn process_events(&mut self, now: Instant);
fn has_events(&self) -> bool;
}
#[allow(clippy::module_name_repetitions)]
pub struct ServerRunner {
now: Box<dyn Fn() -> Instant>,
server: Box<dyn HttpServer>,
@@ -242,14 +250,25 @@ impl ServerRunner {
timeout: &mut Option<Pin<Box<Sleep>>>,
sockets: &mut [(SocketAddr, crate::udp::Socket)],
now: &dyn Fn() -> Instant,
mut input_dgram: Option<Datagram<&[u8]>>,
mut input_dgram: Option<Datagram<&mut [u8]>>,
) -> Result<(), io::Error> {
loop {
match server.process(input_dgram.take(), now()) {
Output::Datagram(dgram) => {
let socket = Self::find_socket(sockets, dgram.source());
loop {
// Optimistically attempt sending datagram. In case the
// OS buffer is full, wait till socket is writable then
// try again.
match socket.send(&dgram) {
Ok(()) => break,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
socket.writable().await?;
socket.send(&dgram)?;
// Now try again.
}
e @ Err(_) => return e,
}
}
}
Output::Callback(new_timeout) => {
qdebug!("Setting timeout of {new_timeout:?}");

View File

@@ -4,9 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::missing_errors_doc, reason = "Passing up tokio errors.")]
use std::{io, net::SocketAddr};
use neqo_common::Datagram;
use neqo_common::{qdebug, Datagram};
use neqo_udp::{DatagramIter, RecvBuf};
/// Ideally this would live in [`neqo-udp`]. [`neqo-udp`] is used in Firefox.
@@ -24,10 +26,36 @@ pub struct Socket {
impl Socket {
/// Create a new [`Socket`] bound to the provided address, not managed externally.
pub fn bind<A: std::net::ToSocketAddrs>(addr: A) -> Result<Self, io::Error> {
const ONE_MB: usize = 1 << 20;
let socket = std::net::UdpSocket::bind(addr)?;
let state = quinn_udp::UdpSocketState::new((&socket).into())?;
let send_buf_before = state.send_buffer_size((&socket).into())?;
// FIXME: We need to experiment if increasing this actually improves performance.
// Also, on BSD and Apple targets, this seems to increase the `net.inet.udp.maxdgram`
// sysctl, which is not the same as the socket buffer.
// if send_buf_before < ONE_MB {
// state.set_send_buffer_size((&socket).into(), ONE_MB)?;
// let send_buf_after = state.send_buffer_size((&socket).into())?;
// qdebug!("Increasing socket send buffer size from {send_buf_before} to {ONE_MB}, now:
// {send_buf_after}"); } else {
// qdebug!("Default socket send buffer size is {send_buf_before}, not changing");
// }
qdebug!("Default socket send buffer size is {send_buf_before}");
let recv_buf_before = state.recv_buffer_size((&socket).into())?;
if recv_buf_before < ONE_MB {
// Same as Firefox.
// <https://searchfox.org/mozilla-central/rev/fa5b44a4ea5c98b6a15f39638ea4cd04dc271f3d/modules/libpref/init/StaticPrefList.yaml#13474-13477>
state.set_recv_buffer_size((&socket).into(), ONE_MB)?;
let recv_buf_after = state.recv_buffer_size((&socket).into())?;
qdebug!("Increasing socket recv buffer size from {recv_buf_before} to {ONE_MB}, now: {recv_buf_after}");
} else {
qdebug!("Default socket receive buffer size is {recv_buf_before}, not changing");
}
Ok(Self {
state: quinn_udp::UdpSocketState::new((&socket).into())?,
state,
inner: tokio::net::UdpSocket::from_std(socket)?,
})
}

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"34eca5cf0f6408d36c215d7ca439aad5a496fb154bd4d6cb3faf0ddbcf7d2c34","benches/decoder.rs":"e7fab506974d7b6b4f41fe51036151b15f0d693b7e45b80cf9e2141bb1f6298f","build.rs":"ee5dc521f3d8e18c2b617192d6b6e678f7f2f9886fdd34c0c1c5ef841419b248","src/codec.rs":"8aea4a5041d1b06e4928d29fefcc8166d515b72f9a744e572d3c92b90e9ea268","src/datagram.rs":"e8bf176d3b120028731388c17344d03b8195e5fd70f4d03e37144ac5ae5951f5","src/event.rs":"289cf8e265c33e7cded58820ac81e5b575e3f84dd52fa18b0761f4094fb361c0","src/fuzz.rs":"9e0f2dca1832ef49b93b214e8d5f1ca2f5f8cb84a856fead344f62a722c370db","src/header.rs":"a316f48735375da268142ad9ac3430d240162c9df67114546f4a094c25a10024","src/hrtime.rs":"754f90ae3f3839e59ccaa34a9f07a2742ec7080f2df539f4b7691c7771dd4856","src/incrdecoder.rs":"2128bb6aa737932b484cdff8213efe8eac46004ef2d9219b6f48ac4d664297ac","src/lib.rs":"002f6a740464b618036e699db4ebea1fc803b9c9c154c0478f6f8721932f4300","src/log.rs":"2ea3f00b1776e085cbf6b46dc7489259b4e58a2dada7edaf94e746de3defb222","src/qlog.rs":"f335b4f4340ae0bfaeb6742bd5a2cb8b6f16292a40b410ed3f72ce309b171106","src/tos.rs":"206410cf0d164a0c1a1c3de19ddcd224e311601707bedc7af100a30a950d12f9","tests/log.rs":"671a963f938599b76c0678f3931592e1782765ed4ea13e6cbfec2ab97c36e2c9"},"package":null}
{"files":{"Cargo.toml":"eb681eeb4daecac76e7fc32d2ec1b1d28d37acbfbf4e800e28533a3f9426db58","benches/decoder.rs":"3b03abd08c064d11c07e6d4eaf041873c282adf93e9bc76e40f2f0137e342929","build.rs":"d9accad1f92a1d82aff73a588269342db882918173e8d9b2b914c514e42e2839","src/codec.rs":"b56abcf6fc76d7c24a6f25e0df556fa3c47a37249b43ae97451944082d344a30","src/datagram.rs":"88ef237b95b4ce10abe5e8e4740a4a8178f19146fc82c49053d3b62819ac428a","src/event.rs":"289cf8e265c33e7cded58820ac81e5b575e3f84dd52fa18b0761f4094fb361c0","src/fuzz.rs":"9e0f2dca1832ef49b93b214e8d5f1ca2f5f8cb84a856fead344f62a722c370db","src/header.rs":"b561dffd52ec8909297ec67ac5f75150dfca73399111e709cf6ea7a34998fca5","src/hrtime.rs":"7096fd12ccc33566aac58f19a4e7439690b2fa38c258584946f76a422ab98087","src/incrdecoder.rs":"029081f0ae19db49c3278c865f22584fc24315169e5ffc1bd5a0087e8c0d38b9","src/lib.rs":"5f15b8b96cd8935e4cc231bb37d45458794ffe23eea0db1b37e42ea7346ac10d","src/log.rs":"61a9b24bf6bf1493da67082bcf7fef8fe55f0a23d7f2a9ad13748982c54c85e2","src/qlog.rs":"3c6f403f0ad2b34fc7ba63b1338bc0855bc232a2b5dd5c1039846b10af2b1d35","src/tos.rs":"108ebfcadf172aae1ba1c722316080b70ec017bf9c866f1ab8d315231d04fd09","tests/log.rs":"671a963f938599b76c0678f3931592e1782765ed4ea13e6cbfec2ab97c36e2c9"},"package":null}

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.76.0"
rust-version = "1.82.0"
name = "neqo-common"
version = "0.12.2"
version = "0.13.1"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = "build.rs"
autolib = false
@@ -44,7 +44,7 @@ bench = [
"neqo-crypto/bench",
"test-fixture/bench",
]
build-fuzzing-corpus = ["hex"]
build-fuzzing-corpus = ["hex/alloc"]
ci = []
test-fixture = []
@@ -73,7 +73,6 @@ default-features = false
[dependencies.hex]
version = "0.4"
features = ["alloc"]
optional = true
default-features = false
@@ -82,7 +81,12 @@ version = "0.4"
default-features = false
[dependencies.qlog]
version = "0.13"
version = "0.15.1"
default-features = false
[dependencies.strum]
version = "0.26"
features = ["derive"]
default-features = false
[dev-dependencies.criterion]
@@ -94,7 +98,6 @@ path = "../neqo-crypto"
[dev-dependencies.regex]
version = "1.9"
features = ["unicode-perl"]
default-features = false
[dev-dependencies.test-fixture]
@@ -106,22 +109,56 @@ features = ["Win32_Media"]
default-features = false
[lints.clippy]
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
cfg_not_test = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
infinite_loop = "warn"
large_include_file = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
literal_string_with_formatting_args = "allow"
lossy_float_literal = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
module_name_repetitions = "warn"
multiple_crate_versions = "allow"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
pathbuf_init_then_push = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
self_named_module_files = "warn"
semicolon_inside_block = "warn"
string_lit_chars_any = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
try_err = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[lints.clippy.cargo]
level = "warn"
@@ -138,7 +175,6 @@ priority = -1
[lints.rust]
absolute_paths_not_starting_with_crate = "warn"
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
@@ -150,3 +186,4 @@ unit_bindings = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

View File

@@ -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 a bench.")]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use neqo_common::Decoder;
use neqo_crypto::{init, randomize};

View File

@@ -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 a build script.")]
use std::env;
fn main() {

View File

@@ -47,6 +47,7 @@ impl<'a> Decoder<'a> {
/// Only use this for tests because we panic rather than reporting a result.
#[cfg(any(test, feature = "test-fixture"))]
fn skip_inner(&mut self, n: Option<u64>) {
#[expect(clippy::unwrap_used, reason = "Only used in tests.")]
self.skip(usize::try_from(n.expect("invalid length")).unwrap());
}
@@ -111,7 +112,7 @@ impl<'a> Decoder<'a> {
/// unsigned integer types: `u8`, `u16`, `u32`, or `u64`.
/// Signed types will fail if the high bit is set.
pub fn decode_uint<T: TryFrom<u64>>(&mut self) -> Option<T> {
let v = self.decode_n(std::mem::size_of::<T>());
let v = self.decode_n(size_of::<T>());
v.and_then(|v| T::try_from(v).ok())
}
@@ -161,7 +162,6 @@ impl<'a> Decoder<'a> {
// Implement `AsRef` for `Decoder` so that values can be examined without
// moving the cursor.
impl<'a> AsRef<[u8]> for Decoder<'a> {
#[must_use]
fn as_ref(&self) -> &'a [u8] {
&self.buf[self.offset..]
}
@@ -174,7 +174,6 @@ impl Debug for Decoder<'_> {
}
impl<'a> From<&'a [u8]> for Decoder<'a> {
#[must_use]
fn from(buf: &'a [u8]) -> Self {
Decoder::new(buf)
}
@@ -184,14 +183,12 @@ impl<'a, T> From<&'a T> for Decoder<'a>
where
T: AsRef<[u8]>,
{
#[must_use]
fn from(buf: &'a T) -> Self {
Decoder::new(buf.as_ref())
}
}
impl<'b> PartialEq<Decoder<'b>> for Decoder<'_> {
#[must_use]
fn eq(&self, other: &Decoder<'b>) -> bool {
self.buf == other.buf
}
@@ -227,7 +224,7 @@ impl Encoder {
/// When `len` doesn't fit in a `u64`.
#[must_use]
pub fn vvec_len(len: usize) -> usize {
Self::varint_len(u64::try_from(len).unwrap()) + len
Self::varint_len(u64::try_from(len).expect("usize should fit into u64")) + len
}
/// Default construction of an empty buffer.
@@ -286,6 +283,7 @@ impl Encoder {
let mut enc = Self::with_capacity(cap);
for i in 0..cap {
#[expect(clippy::unwrap_used, reason = "Only used in tests.")]
let v = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
enc.encode_byte(v);
}
@@ -341,7 +339,10 @@ impl Encoder {
///
/// When `v` is longer than 2^64.
pub fn encode_vec(&mut self, n: usize, v: &[u8]) -> &mut Self {
self.encode_uint(n, u64::try_from(v.as_ref().len()).unwrap())
self.encode_uint(
n,
u64::try_from(v.as_ref().len()).expect("v is longer than 2^64"),
)
.encode(v)
}
@@ -350,7 +351,10 @@ impl Encoder {
/// # Panics
///
/// When `f()` returns a length larger than `2^8n`.
#[allow(clippy::cast_possible_truncation)]
#[expect(
clippy::cast_possible_truncation,
reason = "AND'ing with 0xff makes this OK."
)]
pub fn encode_vec_with<F: FnOnce(&mut Self)>(&mut self, n: usize, f: F) -> &mut Self {
let start = self.buf.len();
self.buf.resize(self.buf.len() + n, 0);
@@ -369,7 +373,7 @@ impl Encoder {
///
/// When `v` is longer than 2^64.
pub fn encode_vvec(&mut self, v: &[u8]) -> &mut Self {
self.encode_varint(u64::try_from(v.as_ref().len()).unwrap())
self.encode_varint(u64::try_from(v.as_ref().len()).expect("v is longer than 2^64"))
.encode(v)
}
@@ -447,14 +451,12 @@ impl AsMut<[u8]> for Encoder {
}
impl<'a> From<Decoder<'a>> for Encoder {
#[must_use]
fn from(dec: Decoder<'a>) -> Self {
Self::from(&dec.buf[dec.offset..])
}
}
impl From<&[u8]> for Encoder {
#[must_use]
fn from(buf: &[u8]) -> Self {
Self {
buf: Vec::from(buf),
@@ -463,7 +465,6 @@ impl From<&[u8]> for Encoder {
}
impl From<Encoder> for Vec<u8> {
#[must_use]
fn from(buf: Encoder) -> Self {
buf.buf
}
@@ -664,6 +665,7 @@ mod tests {
}
#[test]
#[cfg(target_pointer_width = "64")] // Test does not compile on 32-bit targets.
#[should_panic(expected = "Varint value too large")]
fn encoded_vvec_length_oob() {
_ = Encoder::vvec_len(1 << 62);

View File

@@ -4,7 +4,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{net::SocketAddr, ops::Deref};
use std::{
net::SocketAddr,
ops::{Deref, DerefMut},
};
use crate::{hex_with_len, IpTos};
@@ -47,7 +50,6 @@ impl<D: AsRef<[u8]>> Datagram<D> {
}
}
#[cfg(test)]
impl<D: AsMut<[u8]> + AsRef<[u8]>> AsMut<[u8]> for Datagram<D> {
fn as_mut(&mut self) -> &mut [u8] {
self.d.as_mut()
@@ -65,9 +67,14 @@ impl Datagram<Vec<u8>> {
}
}
impl<D: AsRef<[u8]> + AsMut<[u8]>> DerefMut for Datagram<D> {
fn deref_mut(&mut self) -> &mut Self::Target {
AsMut::<[u8]>::as_mut(self)
}
}
impl<D: AsRef<[u8]>> Deref for Datagram<D> {
type Target = [u8];
#[must_use]
fn deref(&self) -> &Self::Target {
AsRef::<[u8]>::as_ref(self)
}
@@ -86,9 +93,9 @@ impl<D: AsRef<[u8]>> std::fmt::Debug for Datagram<D> {
}
}
impl<'a> Datagram<&'a [u8]> {
impl<'a> Datagram<&'a mut [u8]> {
#[must_use]
pub const fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a [u8]) -> Self {
pub fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a mut [u8]) -> Self {
Self { src, dst, tos, d }
}

View File

@@ -36,11 +36,21 @@ impl Header {
)
}
#[allow(
clippy::allow_attributes,
clippy::missing_const_for_fn,
reason = "TODO: False positive on nightly."
)]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[allow(
clippy::allow_attributes,
clippy::missing_const_for_fn,
reason = "TODO: False positive on nightly."
)]
#[must_use]
pub fn value(&self) -> &str {
&self.value

View File

@@ -70,7 +70,7 @@ impl PeriodSet {
fn min(&self) -> Option<Period> {
for (i, v) in self.counts.iter().enumerate() {
if *v > 0 {
return Some(Period(u8::try_from(i).unwrap() + Period::MIN.0));
return Some(Period(u8::try_from(i).ok()? + Period::MIN.0));
}
}
None
@@ -78,7 +78,7 @@ impl PeriodSet {
}
#[cfg(target_os = "macos")]
#[allow(non_camel_case_types)]
#[expect(non_camel_case_types, reason = "These are C types.")]
mod mac {
use std::ptr::addr_of_mut;
@@ -124,9 +124,9 @@ mod mac {
}
const THREAD_TIME_CONSTRAINT_POLICY: thread_policy_flavor_t = 2;
#[allow(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_truncation, reason = "These are C types.")]
const THREAD_TIME_CONSTRAINT_POLICY_COUNT: mach_msg_type_number_t =
(std::mem::size_of::<thread_time_constraint_policy>() / std::mem::size_of::<integer_t>())
(size_of::<thread_time_constraint_policy>() / size_of::<integer_t>())
as mach_msg_type_number_t;
// These function definitions are taken from a comment in <thread_policy.h>.
@@ -179,7 +179,11 @@ mod mac {
/// Create a realtime policy and set it.
pub fn set_realtime(base: f64) {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "These are C types."
)]
let policy = thread_time_constraint_policy {
period: base as u32, // Base interval
computation: (base * 0.5) as u32,
@@ -302,7 +306,10 @@ impl Time {
}
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
#[allow(clippy::unused_self)]
#[expect(
clippy::unused_self,
reason = "Not used on platforms other than macOS and Windows."
)]
const fn start(&self) {}
#[cfg(windows)]
@@ -313,7 +320,10 @@ impl Time {
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::unused_self)]
#[expect(
clippy::unused_self,
reason = "Not used on platforms other than Windows."
)]
const fn stop(&self) {}
fn update(&mut self) {
@@ -372,7 +382,8 @@ impl Drop for Time {
// Only run these tests in CI on Linux, where the timer accuracies are OK enough to pass the tests,
// but only when not running sanitizers.
#[cfg(all(test, target_os = "linux", not(neqo_sanitize)))]
#[cfg(all(target_os = "linux", not(neqo_sanitize)))]
#[cfg(test)]
mod test {
use std::{
thread::{sleep, spawn},

View File

@@ -31,7 +31,7 @@ impl IncrementalDecoderUint {
if amount < 8 {
self.v <<= amount * 8;
}
self.v |= dv.decode_n(amount).unwrap();
self.v |= dv.decode_n(amount)?;
*r -= amount;
(*r == 0).then_some(self.v)
} else {
@@ -89,7 +89,7 @@ impl IncrementalDecoderBuffer {
/// Never; but rust doesn't know that.
pub fn consume(&mut self, dv: &mut Decoder) -> Option<Vec<u8>> {
let amount = min(self.remaining, dv.remaining());
let b = dv.decode(amount).unwrap();
let b = dv.decode(amount)?;
self.v.extend_from_slice(b);
self.remaining -= amount;
(self.remaining == 0).then(|| mem::take(&mut self.v))

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)] // This lint doesn't work here.
mod codec;
mod datagram;
pub mod event;
@@ -21,6 +19,7 @@ pub mod tos;
use std::fmt::Write as _;
use enum_map::Enum;
use strum::Display;
#[cfg(feature = "build-fuzzing-corpus")]
pub use self::fuzz::write_item_to_fuzzing_corpus;
@@ -81,7 +80,7 @@ pub const fn const_min(a: usize, b: usize) -> usize {
[a, b][(a >= b) as usize]
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Enum)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Enum, Display)]
/// Client or Server.
pub enum Role {
Client,
@@ -98,12 +97,6 @@ impl Role {
}
}
impl ::std::fmt::Display for Role {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageType {
Request,

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::{
io::Write as _,
sync::{Once, OnceLock},

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use std::{
cell::RefCell,
fmt::{self, Display},
@@ -20,7 +25,6 @@ use qlog::{
use crate::Role;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Default)]
pub struct NeqoQlog {
inner: Rc<RefCell<Option<NeqoQlogShared>>>,

View File

@@ -4,26 +4,29 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use std::fmt::Debug;
use enum_map::Enum;
use strum::{EnumIter, FromRepr};
/// ECN (Explicit Congestion Notification) codepoints mapped to the
/// lower 2 bits of the TOS field.
/// <https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml>
#[derive(Copy, Clone, PartialEq, Eq, Enum, Default, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, Enum, Default, Debug, FromRepr, EnumIter)]
#[repr(u8)]
pub enum IpTosEcn {
#[default]
/// Not-ECT, Not ECN-Capable Transport, RFC3168
NotEct = 0b00,
/// ECT(1), ECN-Capable Transport(1), RFC8311 and RFC9331
Ect1 = 0b01,
/// ECT(0), ECN-Capable Transport(0), RFC3168
Ect0 = 0b10,
/// CE, Congestion Experienced, RFC3168
Ce = 0b11,
}
@@ -36,13 +39,7 @@ impl From<IpTosEcn> for u8 {
impl From<u8> for IpTosEcn {
fn from(v: u8) -> Self {
match v & 0b0000_0011 {
0b00 => Self::NotEct,
0b01 => Self::Ect1,
0b10 => Self::Ect0,
0b11 => Self::Ce,
_ => unreachable!(),
}
Self::from_repr(v & 0b0000_0011).expect("all ECN values are covered")
}
}
@@ -64,76 +61,54 @@ impl IpTosEcn {
/// Diffserv codepoints, mapped to the upper six bits of the TOS field.
/// <https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml>
#[derive(Copy, Clone, PartialEq, Eq, Enum, Default, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, Enum, Default, Debug, FromRepr)]
#[repr(u8)]
pub enum IpTosDscp {
#[default]
/// Class Selector 0, RFC2474
Cs0 = 0b0000_0000,
/// Class Selector 1, RFC2474
Cs1 = 0b0010_0000,
/// Class Selector 2, RFC2474
Cs2 = 0b0100_0000,
/// Class Selector 3, RFC2474
Cs3 = 0b0110_0000,
/// Class Selector 4, RFC2474
Cs4 = 0b1000_0000,
/// Class Selector 5, RFC2474
Cs5 = 0b1010_0000,
/// Class Selector 6, RFC2474
Cs6 = 0b1100_0000,
/// Class Selector 7, RFC2474
Cs7 = 0b1110_0000,
/// Assured Forwarding 11, RFC2597
Af11 = 0b0010_1000,
/// Assured Forwarding 12, RFC2597
Af12 = 0b0011_0000,
/// Assured Forwarding 13, RFC2597
Af13 = 0b0011_1000,
/// Assured Forwarding 21, RFC2597
Af21 = 0b0100_1000,
/// Assured Forwarding 22, RFC2597
Af22 = 0b0101_0000,
/// Assured Forwarding 23, RFC2597
Af23 = 0b0101_1000,
/// Assured Forwarding 31, RFC2597
Af31 = 0b0110_1000,
/// Assured Forwarding 32, RFC2597
Af32 = 0b0111_0000,
/// Assured Forwarding 33, RFC2597
Af33 = 0b0111_1000,
/// Assured Forwarding 41, RFC2597
Af41 = 0b1000_1000,
/// Assured Forwarding 42, RFC2597
Af42 = 0b1001_0000,
/// Assured Forwarding 43, RFC2597
Af43 = 0b1001_1000,
/// Expedited Forwarding, RFC3246
Ef = 0b1011_1000,
/// Capacity-Admitted Traffic, RFC5865
VoiceAdmit = 0b1011_0000,
/// Lower-Effort, RFC8622
Le = 0b0000_0100,
}
@@ -146,32 +121,7 @@ impl From<IpTosDscp> for u8 {
impl From<u8> for IpTosDscp {
fn from(v: u8) -> Self {
match v & 0b1111_1100 {
0b0000_0000 => Self::Cs0,
0b0010_0000 => Self::Cs1,
0b0100_0000 => Self::Cs2,
0b0110_0000 => Self::Cs3,
0b1000_0000 => Self::Cs4,
0b1010_0000 => Self::Cs5,
0b1100_0000 => Self::Cs6,
0b1110_0000 => Self::Cs7,
0b0010_1000 => Self::Af11,
0b0011_0000 => Self::Af12,
0b0011_1000 => Self::Af13,
0b0100_1000 => Self::Af21,
0b0101_0000 => Self::Af22,
0b0101_1000 => Self::Af23,
0b0110_1000 => Self::Af31,
0b0111_0000 => Self::Af32,
0b0111_1000 => Self::Af33,
0b1000_1000 => Self::Af41,
0b1001_0000 => Self::Af42,
0b1001_1000 => Self::Af43,
0b1011_1000 => Self::Ef,
0b1011_0000 => Self::VoiceAdmit,
0b0000_0100 => Self::Le,
_ => unreachable!(),
}
Self::from_repr(v & 0b1111_1100).expect("all DCSP values are covered")
}
}

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"483cd040a7d91cc03ea3e497db33cc0fbafb7123520ea342a7825df079a6e72f","bindings/bindings.toml":"e7e4b75736cfcf4d52febacb99a6f6c6c7b1d648ed8bdc424648be876c850e91","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":"dfc3b2c8038c4b1c69d7a10bd06c4226e36935e7597225aba26039f700cc8f4f","min_version.txt":"04b271df436ebebd03df52ef009d6814f6a64e55203988790a6fcee7b2dc27af","src/aead.rs":"018b826284ea4ddba7d65ff56f3ce9744e24ac8d26b1364b08f6e8b12b259be5","src/aead_null.rs":"81163fafef59bd2800bd0a078d53d0f05ee114f0e22165717823a5ff1cb908af","src/agent.rs":"6b6bcd735cb12d3e3ddebbb04ebd705d192a9cbce6e2a967e86e7b41cccf0a8d","src/agentio.rs":"237f85faacdb6ee5b3a6efa8a620fcf109094f5fd9dfb69d2c74986f11ccdb0e","src/auth.rs":"ced1a18f691894984244088020ea25dc1ee678603317f0c7dfc8b8842fa750b4","src/cert.rs":"4fdaa3834d8a72f41198449010fd5c3f6be6a54e429427c37bde5aab9421585c","src/constants.rs":"58e296e314825753b2ab1d6effe9a1386421dc568f6ebfa8e95a95acb87205da","src/ech.rs":"869e36414418b8747e6ebb45af24159c1908fc829439c16d79cdd89e355d2111","src/err.rs":"2366501e0b48933a6a2e1c5b934aa55108c093729c84878b45e1e012e4e45d51","src/exp.rs":"d953873e87430b1c84d4a83c8eb3815041f5585b210bbaf59ae2c4d0057f5edd","src/ext.rs":"cbf7d9f5ecabf4b8c9efd6c334637ab1596ec5266d38ab8d2d6ceae305283deb","src/hkdf.rs":"8745ba761be821c1819cedf6dfd91f8b3148c6718053a4a74f33eb50c7d0cc40","src/hp.rs":"510a4a7f278203aa306ead05608f99397edc3806dc22b0af9e28c665b43ae56c","src/lib.rs":"cfbf92759c6d1b888d89b9c28adbd96839bff60fc8106169a043eafce0f8a21e","src/min_version.rs":"c6e1f98b9f56db0622ac38c1be131c55acf4a0f09ed0d6283f4d6308e2d1301a","src/p11.rs":"120c45dc037c243a1551b79c7a32613acf22e2961f2ea1e11c73bbce9d1f76dc","src/prio.rs":"5cf0105e78b1db43c65283208174abc3714a41dbb4d5cd80ac547a5a5a7c627c","src/replay.rs":"5cda39bc8fa8a07c493b761b8dfb5cbc9f669f97a2df7832a028ab366b3426be","src/result.rs":"5a76688787741de7a935dbbab4bcb917d481d1c9c50a34df7e510036feb3da17","src/secrets.rs":"c09caca2bf7b60fc7855fc2ad56094a291cb0596308eadf46ab51af0e271a9f0","src/selfencrypt.rs":"80efac0f116b22d86f88f07d694ff2f822bd87b792b3dff8d6376a251394de39","src/ssl.rs":"450cf9fb327515de612b29e3a0f6e15499e6c0b523790e5986fd71a9ea5e76af","src/time.rs":"27d0185c16e150dc381cd6c01ef85fdf7881e5349386466e175d641aab9ab30f","tests/aead.rs":"e36ae77802df1ea6d17cfd1bd2178a3706089577d6fd1554ca86e748b8b235b9","tests/agent.rs":"cbd0011f1d33281883a45d433228221062424c94e86decade5697731c08a1c52","tests/ext.rs":"57af4e2df211fa8afdb73125d4344ef5c70c1ea4579107c3e6f5746308ee3e7b","tests/handshake.rs":"efae67a40f87419aea30c7ab7eb1007faf7f56a83dbf1a18e24fbc0cb92ec901","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"c74070a8f877aaa6305b9411f6e60d8b509b0ac1bef2988cc44b8a48552c953c","tests/init.rs":"3e15150c4b324c06ca5e8935618e4008da53dc0ef4b69325d150831e87dc0b63","tests/selfencrypt.rs":"8d10840b41629bf449a6b3a551377315e8a05ca26c6b041548748196652c5909"},"package":null}
{"files":{"Cargo.toml":"2dd521b9c64bcc1916751c27e89a542528652f742092feecb8866178cf9a8a72","bindings/bindings.toml":"e7e4b75736cfcf4d52febacb99a6f6c6c7b1d648ed8bdc424648be876c850e91","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":"97fb6b8f8bb76dc1ed47988025645da618f3301871827d2a98d57254f7c62d5b","min_version.txt":"0f9ddf9ddaeb5137a5ab3d238d06286822f9579b1f46ba76312a8c6d76176500","src/aead.rs":"08d7cad82e3bec32661cfd1689e6611b30ae328ec88481cb32201dd255777365","src/aead_null.rs":"a766e2f71fd8b77a8f81bc60aaaafcffb6aef1f0a1f39ea07fef45b3696718ce","src/agent.rs":"19d9ee826e30c29ca8680e9ec767d19eac2326323f61b2a19bb62874990bf349","src/agentio.rs":"82f0cdececcb475c7b8639c993d4479bce7ac5fdf64b0d560f4b2fd356d03b03","src/auth.rs":"7a1524bef0a0c71616f5ee8b3976d66201210b809271bcf5d06c0e560ae482af","src/cert.rs":"4fdaa3834d8a72f41198449010fd5c3f6be6a54e429427c37bde5aab9421585c","src/constants.rs":"83606aeb646b2833a8094f9d980c266ecc3e8cb40c93a4820da221988319dd1a","src/ech.rs":"19d16af5a30e2060a8942a72487bd820c0d9c62ff1d3c490871752c56781c44b","src/err.rs":"b1e76e595326598a9bac45859f337ad9fb9a92ba9461d8c87250556a2629e7b6","src/exp.rs":"30f91d321439edc75c7f9a595ef5a0064918826d51ab15642db211472694009c","src/ext.rs":"7486595a24d39bd6c573e2229be345d13dbcd6911755ab6ab96d89ce7e263dd3","src/hkdf.rs":"76c5abc8b2d6ee12d8a86cd730af2cf47a59b2fbfd3b8a635a1826636156794d","src/hp.rs":"6adf4ad78b5a065ab7310c69ad239eec156256043e2c185bf60b9d1f12ab1be4","src/lib.rs":"3ab979c264a909e663c5ef140cd57013180745b99937671c73a9003ca6347f41","src/min_version.rs":"c6e1f98b9f56db0622ac38c1be131c55acf4a0f09ed0d6283f4d6308e2d1301a","src/p11.rs":"5c0759bb8ea5faad1a3d06fa7afb3dba114c93ea21981c85824d707f654d7c3d","src/prio.rs":"1858088afd2668e8fbff56959765b7d4df09342371b9282ade27bb4d7bd6ce69","src/replay.rs":"594ce92f368cbc5fb71ebfb62214f07d1e86df8e5ce94255d5593ffabb91cd03","src/result.rs":"5a76688787741de7a935dbbab4bcb917d481d1c9c50a34df7e510036feb3da17","src/secrets.rs":"f01ac665e3c65674865d0645fb1bf3e4579891bf392661e3de3c9f4621e454f0","src/selfencrypt.rs":"4f106465f582c38d3bb04cb5cbcbf65a349e3186784726d9f2bf511a4a4a35ee","src/ssl.rs":"04950bb534b5304eb417909a3a39ebaa9be234c7c13eacdc41c00a8edab1b09f","src/time.rs":"22989caf3dab85cfe955cc279fcca98a6df02d14fcd0e93cac7b39374b8b5763","tests/aead.rs":"e36ae77802df1ea6d17cfd1bd2178a3706089577d6fd1554ca86e748b8b235b9","tests/agent.rs":"fb95a2d5c86ce3fafcb127cd0a2a163e5ee70baf09b2c8483e4d1fb25644cee2","tests/ext.rs":"57af4e2df211fa8afdb73125d4344ef5c70c1ea4579107c3e6f5746308ee3e7b","tests/handshake.rs":"c06ed3d27b0f84b03ff0d0356a16601768821d9860cab81a49fea3eebb3091f2","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"1ad88108b6d8c5d078fc7bb2a4337a54f637d4e149db750f4916032d90aa64a7","tests/init.rs":"3cfe8411ca31ad7dfb23822bb1570e1a5b2b334857173bdd7df086b65b81d95a","tests/selfencrypt.rs":"b65aed70e83dce660017159fc8a956d3b52e0807b590ad8d0a3a4265caa8c1fa"},"package":null}

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.76.0"
rust-version = "1.82.0"
name = "neqo-crypto"
version = "0.12.2"
version = "0.13.1"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = "build.rs"
autolib = false
@@ -82,6 +82,10 @@ path = "tests/init.rs"
name = "selfencrypt"
path = "tests/selfencrypt.rs"
[dependencies.enum-map]
version = "2.7"
default-features = false
[dependencies.log]
version = "0.4"
default-features = false
@@ -89,6 +93,11 @@ default-features = false
[dependencies.neqo-common]
path = "../neqo-common"
[dependencies.strum]
version = "0.26"
features = ["derive"]
default-features = false
[dev-dependencies.test-fixture]
path = "../test-fixture"
@@ -119,22 +128,56 @@ version = "0.5"
default-features = false
[lints.clippy]
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
cfg_not_test = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
infinite_loop = "warn"
large_include_file = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
literal_string_with_formatting_args = "allow"
lossy_float_literal = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
module_name_repetitions = "warn"
multiple_crate_versions = "allow"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
pathbuf_init_then_push = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
self_named_module_files = "warn"
semicolon_inside_block = "warn"
string_lit_chars_any = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
try_err = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[lints.clippy.cargo]
level = "warn"
@@ -151,7 +194,6 @@ priority = -1
[lints.rust]
absolute_paths_not_starting_with_crate = "warn"
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
@@ -163,3 +205,4 @@ unit_bindings = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

View File

@@ -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 a build script.")]
use std::{
collections::HashMap,
env, fs,
@@ -54,13 +56,6 @@ struct Bindings {
cplusplus: bool,
}
fn is_debug() -> bool {
// Check the build profile and not whether debug symbols are enabled (i.e.,
// `env::var("DEBUG")`), because we enable those for benchmarking/profiling and still want
// to build NSS in release mode.
env::var("PROFILE").unwrap_or_default() == "debug"
}
// bindgen needs access to libclang.
// On windows, this doesn't just work, you have to set LIBCLANG_PATH.
// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
@@ -136,21 +131,19 @@ fn build_nss(dir: PathBuf, nsstarget: &str) {
}
fn dynamic_link() {
let libs = if env::consts::OS == "windows" {
&["nssutil3.dll", "nss3.dll", "ssl3.dll"]
let dynamic_libs = if env::consts::OS == "windows" {
[
"nssutil3.dll",
"nss3.dll",
"ssl3.dll",
"libplds4.dll",
"libplc4.dll",
"libnspr4.dll",
]
} else {
&["nssutil3", "nss3", "ssl3"]
["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"]
};
dynamic_link_both(libs);
}
fn dynamic_link_both(extra_libs: &[&str]) {
let nspr_libs = if env::consts::OS == "windows" {
&["libplds4", "libplc4", "libnspr4"]
} else {
&["plds4", "plc4", "nspr4"]
};
for lib in nspr_libs.iter().chain(extra_libs) {
for lib in dynamic_libs {
println!("cargo:rustc-link-lib=dylib={lib}");
}
}
@@ -160,35 +153,71 @@ fn static_link() {
"certdb",
"certhi",
"cryptohi",
"freebl",
"freebl_static",
if env::consts::OS == "windows" {
"libnspr4"
} else {
"nspr4"
},
"nss_static",
"nssb",
"nssdev",
"nsspki",
"nssutil",
"pk11wrap",
"pkcs12",
"pkcs7",
"smime",
"pk11wrap_static",
if env::consts::OS == "windows" {
"libplc4"
} else {
"plc4"
},
if env::consts::OS == "windows" {
"libplds4"
} else {
"plds4"
},
"softokn_static",
"ssl",
];
if env::consts::OS != "macos" {
// macOS always dynamically links against the system sqlite library.
// See https://github.com/nss-dev/nss/blob/a8c22d8fc0458db3e261acc5e19b436ab573a961/coreconf/Darwin.mk#L130-L135
if env::consts::OS == "macos" {
println!("cargo:rustc-link-lib=dylib=sqlite3");
} else {
static_libs.push("sqlite");
}
// Hardware specific libs.
// See https://github.com/mozilla/application-services/blob/0a2dac76f979b8bcfb6bacb5424b50f58520b8fe/components/support/rc_crypto/nss/nss_build_common/src/lib.rs#L127-L157
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
// https://searchfox.org/nss/rev/0d5696b3edce5124353f03159d2aa15549db8306/lib/freebl/freebl.gyp#508-542
if target_arch == "arm" || target_arch == "aarch64" {
static_libs.push("armv8_c_lib");
}
if target_arch == "x86_64" || target_arch == "x86" {
static_libs.push("gcm-aes-x86_c_lib");
static_libs.push("sha-x86_c_lib");
}
if target_arch == "arm" {
static_libs.push("gcm-aes-arm32-neon_c_lib");
}
if target_arch == "aarch64" {
static_libs.push("gcm-aes-aarch64_c_lib");
}
if target_arch == "x86_64" {
static_libs.push("hw-acc-crypto-avx");
static_libs.push("hw-acc-crypto-avx2");
}
// https://searchfox.org/nss/rev/08c4d05078d00089f8d7540651b0717a9d66f87e/lib/freebl/freebl.gyp#315-324
if (target_os == "android" || target_os == "linux") && target_arch == "x86_64" {
static_libs.push("intel-gcm-wrap_c_lib");
// https://searchfox.org/nss/rev/08c4d05078d00089f8d7540651b0717a9d66f87e/lib/freebl/freebl.gyp#43-47
if (target_os == "android" || target_os == "linux") && target_arch == "x86_64" {
static_libs.push("intel-gcm-s_lib");
}
}
for lib in static_libs {
println!("cargo:rustc-link-lib=static={lib}");
}
// Dynamic libs that aren't transitively included by NSS libs.
let mut other_libs = Vec::new();
if env::consts::OS != "windows" {
other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]);
}
if env::consts::OS == "macos" {
other_libs.push("sqlite3");
}
dynamic_link_both(&other_libs);
}
fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
@@ -336,7 +365,11 @@ fn setup_standalone(nss: &str) -> Vec<String> {
"cargo:rustc-link-search=native={}",
nsslibdir.to_str().unwrap()
);
if is_debug() || env::consts::OS == "windows" {
if env::var("CARGO_CFG_FUZZING").is_ok()
|| env::var("PROFILE").unwrap_or_default() == "debug"
// FIXME: NSPR doesn't build proper dynamic libraries on Windows.
|| env::consts::OS == "windows"
{
static_link();
} else {
dynamic_link();
@@ -352,7 +385,10 @@ fn setup_standalone(nss: &str) -> Vec<String> {
#[cfg(feature = "gecko")]
fn setup_for_gecko() -> Vec<String> {
use mozbuild::TOPOBJDIR;
use mozbuild::{
config::{BINDGEN_SYSTEM_FLAGS, NSPR_CFLAGS, NSS_CFLAGS},
TOPOBJDIR,
};
let fold_libs = mozbuild::config::MOZ_FOLD_LIBS;
let libs = if fold_libs {
@@ -396,13 +432,11 @@ fn setup_for_gecko() -> Vec<String> {
);
}
let flags_path = TOPOBJDIR.join("netwerk/socket/neqo/extra-bindgen-flags");
println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap());
let mut flags = fs::read_to_string(flags_path)
.expect("Failed to read extra-bindgen-flags file")
.split_whitespace()
.map(String::from)
let mut flags = BINDGEN_SYSTEM_FLAGS
.iter()
.chain(&NSPR_CFLAGS)
.chain(&NSS_CFLAGS)
.map(|s| s.to_string())
.collect::<Vec<_>>();
flags.push(String::from("-include"));

View File

@@ -1 +1 @@
3.105
3.110

View File

@@ -123,15 +123,46 @@ impl RealAead {
c_uint::try_from(output.len())?,
)
}?;
Ok(&output[0..(l.try_into()?)])
Ok(&output[..l.try_into()?])
}
/// Encrypt `data` consisting of `aad` and plaintext `data` in place.
///
/// The last `Aead::expansion` of `data` is overwritten by the AEAD tag by this function.
/// Therefore, a buffer should be provided that is that much larger than the plaintext.
///
/// # Panics
///
/// If `data` is shorter than `<self as Aead>::expansion()`.
///
/// # Errors
///
/// If the input can't be protected or any input is too large for NSS.
pub fn encrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let mut l: c_uint = 0;
unsafe {
SSL_AeadEncrypt(
*self.ctx,
count,
aad.as_ptr(),
c_uint::try_from(aad.len())?,
data.as_ptr(),
c_uint::try_from(data.len() - self.expansion())?,
data.as_ptr(),
&mut l,
c_uint::try_from(data.len())?,
)
}?;
Ok(&mut data[..l.try_into()?])
}
/// Decrypt a ciphertext.
///
/// Note that NSS insists upon having extra space available for decryption, so
/// the buffer for `output` should be the same length as `input`, even though
/// the final result will be shorter.
///
/// # Errors
///
/// If the input isn't authenticated or any input is too large for NSS.
@@ -144,6 +175,9 @@ impl RealAead {
) -> Res<&'a [u8]> {
let mut l: c_uint = 0;
unsafe {
// Note that NSS insists upon having extra space available for decryption, so
// the buffer for `output` should be the same length as `input`, even though
// the final result will be shorter.
SSL_AeadDecrypt(
*self.ctx,
count,
@@ -156,7 +190,41 @@ impl RealAead {
c_uint::try_from(output.len())?,
)
}?;
Ok(&output[0..(l.try_into()?)])
Ok(&output[..l.try_into()?])
}
/// Decrypt a ciphertext in place.
/// Returns a subslice of `data` (without the last `<self as Aead>::expansion()` bytes),
/// that has been decrypted in place.
///
/// # Errors
///
/// If the input isn't authenticated or any input is too large for NSS.
pub fn decrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let mut l: c_uint = 0;
unsafe {
// Note that NSS insists upon having extra space available for decryption, so
// the buffer for `output` should be the same length as `input`, even though
// the final result will be shorter.
SSL_AeadDecrypt(
*self.ctx,
count,
aad.as_ptr(),
c_uint::try_from(aad.len())?,
data.as_ptr(),
c_uint::try_from(data.len())?,
data.as_ptr(),
&mut l,
c_uint::try_from(data.len())?,
)
}?;
debug_assert_eq!(usize::try_from(l)?, data.len() - self.expansion());
Ok(&mut data[..l.try_into()?])
}
}

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(clippy::missing_errors_doc, reason = "OK here.")]
use std::fmt;
use crate::{
@@ -17,7 +19,6 @@ pub const AEAD_NULL_TAG: &[u8] = &[0x0a; 16];
pub struct AeadNull {}
impl AeadNull {
#[allow(clippy::missing_errors_doc)]
pub const fn new(
_version: Version,
_cipher: Cipher,
@@ -32,7 +33,6 @@ impl AeadNull {
AEAD_NULL_TAG.len()
}
#[allow(clippy::missing_errors_doc)]
pub fn encrypt<'a>(
&self,
_count: u64,
@@ -42,23 +42,30 @@ impl AeadNull {
) -> Res<&'a [u8]> {
let l = input.len();
output[..l].copy_from_slice(input);
output[l..l + 16].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..l + 16])
output[l..l + self.expansion()].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..l + self.expansion()])
}
#[allow(clippy::missing_errors_doc)]
pub fn decrypt<'a>(
pub fn encrypt_in_place<'a>(
&self,
_count: u64,
_aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
if input.len() < AEAD_NULL_TAG.len() {
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let pos = data.len() - self.expansion();
data[pos..].copy_from_slice(AEAD_NULL_TAG);
Ok(data)
}
fn decrypt_check(&self, _count: u64, _aad: &[u8], input: &[u8]) -> Res<usize> {
if input.len() < self.expansion() {
return Err(Error::from(SEC_ERROR_BAD_DATA));
}
let len_encrypted = input.len() - AEAD_NULL_TAG.len();
let len_encrypted = input
.len()
.checked_sub(self.expansion())
.ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?;
// Check that:
// 1) expansion is all zeros and
// 2) if the encrypted data is also supplied that at least some values are no zero
@@ -66,12 +73,34 @@ impl AeadNull {
if &input[len_encrypted..] == AEAD_NULL_TAG
&& (len_encrypted == 0 || input[..len_encrypted].iter().any(|x| *x != 0x0))
{
output[..len_encrypted].copy_from_slice(&input[..len_encrypted]);
Ok(&output[..len_encrypted])
Ok(len_encrypted)
} else {
Err(Error::from(SEC_ERROR_BAD_DATA))
}
}
pub fn decrypt<'a>(
&self,
count: u64,
aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
self.decrypt_check(count, aad, input).map(|len| {
output[..len].copy_from_slice(&input[..len]);
&output[..len]
})
}
pub fn decrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
self.decrypt_check(count, aad, data)
.map(move |len| &mut data[..len])
}
}
impl fmt::Debug for AeadNull {

View File

@@ -4,6 +4,15 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
#![expect(
clippy::unwrap_used,
reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed."
)]
use std::{
cell::RefCell,
ffi::{CStr, CString},
@@ -122,7 +131,11 @@ macro_rules! preinfo_arg {
pub fn $v(&self) -> Option<$t> {
match self.info.valuesSet & ssl::$m {
0 => None,
_ => Some(<$t>::try_from(self.info.$f).unwrap()),
_ => Some(
<$t>::try_from(self.info.$f)
.inspect_err(|e| qdebug!("Invalid value in preinfo: {e:?}"))
.ok()?,
),
}
}
};
@@ -135,7 +148,7 @@ impl SecretAgentPreInfo {
ssl::SSL_GetPreliminaryChannelInfo(
fd,
info.as_mut_ptr(),
c_uint::try_from(std::mem::size_of::<ssl::SSLPreliminaryChannelInfo>())?,
c_uint::try_from(size_of::<ssl::SSLPreliminaryChannelInfo>())?,
)
})?;
@@ -158,12 +171,11 @@ impl SecretAgentPreInfo {
self.info.canSendEarlyData != 0
}
/// # Panics
/// # Errors
///
/// If `usize` is less than 32 bits and the value is too large.
#[must_use]
pub fn max_early_data(&self) -> usize {
usize::try_from(self.info.maxEarlyDataSize).unwrap()
pub fn max_early_data(&self) -> Res<usize> {
Ok(usize::try_from(self.info.maxEarlyDataSize)?)
}
/// Was ECH accepted.
@@ -222,7 +234,7 @@ impl SecretAgentInfo {
ssl::SSL_GetChannelInfo(
fd,
info.as_mut_ptr(),
c_uint::try_from(std::mem::size_of::<ssl::SSLChannelInfo>())?,
c_uint::try_from(size_of::<ssl::SSLChannelInfo>())?,
)
})?;
let info = unsafe { info.assume_init() };
@@ -273,7 +285,6 @@ impl SecretAgentInfo {
/// `SecretAgent` holds the common parts of client and server.
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct SecretAgent {
fd: *mut ssl::PRFileDesc,
secrets: SecretHolder,
@@ -345,7 +356,6 @@ impl SecretAgent {
Ok(fd)
}
#[allow(clippy::missing_const_for_fn)]
unsafe extern "C" fn auth_complete_hook(
arg: *mut c_void,
_fd: *mut ssl::PRFileDesc,
@@ -544,9 +554,7 @@ impl SecretAgent {
// NSS inherited an idiosyncratic API as a result of having implemented NPN
// before ALPN. For that reason, we need to put the "best" option last.
let (first, rest) = protocols
.split_first()
.expect("at least one ALPN value needed");
let (first, rest) = protocols.split_first().ok_or(Error::InternalError)?;
for v in rest {
add(v.as_ref());
}
@@ -585,15 +593,17 @@ impl SecretAgent {
// This function tracks whether handshake() or handshake_raw() was used
// and prevents the other from being used.
fn set_raw(&mut self, r: bool) -> Res<()> {
if self.raw.is_none() {
self.secrets.register(self.fd)?;
self.raw = Some(r);
Ok(())
} else if self.raw.unwrap() == r {
if let Some(raw) = self.raw {
if raw == r {
Ok(())
} else {
Err(Error::MixedHandshakeMethod)
}
} else {
self.secrets.register(self.fd)?;
self.raw = Some(r);
Ok(())
}
}
/// Get information about the connection.
@@ -627,6 +637,11 @@ impl SecretAgent {
}
/// Return any fatal alert that the TLS stack might have sent.
#[allow(
clippy::allow_attributes,
clippy::missing_const_for_fn,
reason = "TODO: False positive on nightly."
)]
#[must_use]
pub fn alert(&self) -> Option<&Alert> {
(*self.alert).as_ref()
@@ -751,12 +766,15 @@ impl SecretAgent {
/// # Panics
///
/// If setup fails.
#[allow(clippy::branches_sharing_code)]
pub fn close(&mut self) {
// It should be safe to close multiple times.
if self.fd.is_null() {
return;
}
#[expect(
clippy::branches_sharing_code,
reason = "Moving the PR_Close call after the conditional crashes things?!"
)]
if self.raw == Some(true) {
// Need to hold the record list in scope until the close is done.
let _records = self.setup_raw().expect("Can only close");
@@ -769,7 +787,7 @@ impl SecretAgent {
unsafe {
prio::PR_Close(self.fd.cast());
}
};
}
let _output = self.io.take_output();
self.fd = null_mut();
}
@@ -793,6 +811,11 @@ impl SecretAgent {
}
/// Get the active ECH configuration, which is empty if ECH is disabled.
#[allow(
clippy::allow_attributes,
clippy::missing_const_for_fn,
reason = "TODO: False positive on nightly."
)]
#[must_use]
pub fn ech_config(&self) -> &[u8] {
&self.ech_config
@@ -840,13 +863,13 @@ impl ResumptionToken {
/// A TLS Client.
#[derive(Debug)]
#[allow(clippy::box_collection)] // We need the Box.
pub struct Client {
agent: SecretAgent,
/// The name of the server we're attempting a connection to.
server_name: String,
/// Records the resumption tokens we've received.
#[expect(clippy::box_collection, reason = "We need the Box.")]
resumption: Pin<Box<Vec<ResumptionToken>>>,
}
@@ -878,12 +901,10 @@ impl Client {
arg: *mut c_void,
) -> ssl::SECStatus {
let mut info: MaybeUninit<ssl::SSLResumptionTokenInfo> = MaybeUninit::uninit();
let info_res = &ssl::SSL_GetResumptionTokenInfo(
token,
len,
info.as_mut_ptr(),
c_uint::try_from(std::mem::size_of::<ssl::SSLResumptionTokenInfo>()).unwrap(),
);
let Ok(info_len) = c_uint::try_from(size_of::<ssl::SSLResumptionTokenInfo>()) else {
return ssl::SECFailure;
};
let info_res = &ssl::SSL_GetResumptionTokenInfo(token, len, info.as_mut_ptr(), info_len);
if info_res.is_err() {
// Ignore the token.
return ssl::SECSuccess;
@@ -893,8 +914,12 @@ impl Client {
// Ignore the token.
return ssl::SECSuccess;
}
let resumption = arg.cast::<Vec<ResumptionToken>>().as_mut().unwrap();
let len = usize::try_from(len).unwrap();
let Some(resumption) = arg.cast::<Vec<ResumptionToken>>().as_mut() else {
return ssl::SECFailure;
};
let Ok(len) = usize::try_from(len) else {
return ssl::SECFailure;
};
let mut v = Vec::with_capacity(len);
v.extend_from_slice(null_safe_slice(token, len));
qdebug!("[{fd:p}] Got resumption token {}", hex_snip_middle(&v));
@@ -908,6 +933,11 @@ impl Client {
ssl::SECSuccess
}
#[allow(
clippy::allow_attributes,
clippy::missing_const_for_fn,
reason = "TODO: False positive on nightly."
)]
#[must_use]
pub fn server_name(&self) -> &str {
&self.server_name
@@ -985,7 +1015,6 @@ impl Client {
impl Deref for Client {
type Target = SecretAgent;
#[must_use]
fn deref(&self) -> &SecretAgent {
&self.agent
}
@@ -1194,7 +1223,6 @@ impl Server {
impl Deref for Server {
type Target = SecretAgent;
#[must_use]
fn deref(&self) -> &SecretAgent {
&self.agent
}
@@ -1221,7 +1249,6 @@ pub enum Agent {
impl Deref for Agent {
type Target = SecretAgent;
#[must_use]
fn deref(&self) -> &SecretAgent {
match self {
Self::Client(c) => c,
@@ -1240,14 +1267,12 @@ impl DerefMut for Agent {
}
impl From<Client> for Agent {
#[must_use]
fn from(c: Client) -> Self {
Self::Client(c)
}
}
impl From<Server> for Agent {
#[must_use]
fn from(s: Server) -> Self {
Self::Server(s)
}

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(
clippy::unwrap_used,
reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed."
)]
use std::{
cmp::min,
fmt, mem,
@@ -95,10 +100,17 @@ impl RecordList {
len: c_uint,
arg: *mut c_void,
) -> ssl::SECStatus {
let records = arg.cast::<Self>().as_mut().unwrap();
let Ok(epoch) = Epoch::try_from(epoch) else {
return ssl::SECFailure;
};
let Ok(ct) = ContentType::try_from(ct) else {
return ssl::SECFailure;
};
let Some(records) = arg.cast::<Self>().as_mut() else {
return ssl::SECFailure;
};
let slice = null_safe_slice(data, len);
records.append(epoch, ContentType::try_from(ct).unwrap(), slice);
records.append(epoch, ct, slice);
ssl::SECSuccess
}
@@ -114,7 +126,6 @@ impl RecordList {
impl Deref for RecordList {
type Target = Vec<Record>;
#[must_use]
fn deref(&self) -> &Vec<Record> {
&self.records
}
@@ -132,7 +143,6 @@ impl Iterator for RecordListIter {
impl IntoIterator for RecordList {
type Item = Record;
type IntoIter = RecordListIter;
#[must_use]
fn into_iter(self) -> Self::IntoIter {
RecordListIter(self.records.into_iter())
}
@@ -175,7 +185,10 @@ impl AgentIoInput {
return Err(Error::NoDataAvailable);
}
#[allow(clippy::disallowed_methods)] // We just checked if this was empty.
#[expect(
clippy::disallowed_methods,
reason = "We just checked if this was empty."
)]
let src = unsafe { std::slice::from_raw_parts(self.input, amount) };
qtrace!("[{self}] read {}", hex(src));
let dst = unsafe { std::slice::from_raw_parts_mut(buf, amount) };
@@ -329,10 +342,14 @@ unsafe extern "C" fn agent_available64(mut fd: PrFd) -> prio::PRInt64 {
.unwrap_or_else(|_| PR_FAILURE.into())
}
#[allow(clippy::cast_possible_truncation)]
#[expect(
clippy::cast_possible_truncation,
reason = "Cast is safe because prio::PR_AF_INET is 2."
)]
unsafe extern "C" fn agent_getname(_fd: PrFd, addr: *mut prio::PRNetAddr) -> PrStatus {
let a = addr.as_mut().unwrap();
// Cast is safe because prio::PR_AF_INET is 2
let Some(a) = addr.as_mut() else {
return PR_FAILURE;
};
a.inet.family = prio::PR_AF_INET as prio::PRUint16;
a.inet.port = 0;
a.inet.ip = 0;
@@ -340,11 +357,12 @@ unsafe extern "C" fn agent_getname(_fd: PrFd, addr: *mut prio::PRNetAddr) -> PrS
}
unsafe extern "C" fn agent_getsockopt(_fd: PrFd, opt: *mut prio::PRSocketOptionData) -> PrStatus {
let o = opt.as_mut().unwrap();
if let Some(o) = opt.as_mut() {
if o.option == prio::PRSockOption::PR_SockOpt_Nonblocking {
o.value.non_blocking = 1;
return PR_SUCCESS;
}
}
PR_FAILURE
}

View File

@@ -4,105 +4,48 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use strum::FromRepr;
use crate::err::{mozpkix, sec, ssl, PRErrorCode};
/// The outcome of authentication.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, FromRepr)]
#[repr(i32)]
pub enum AuthenticationStatus {
Ok,
CaInvalid,
CaNotV3,
CertAlgorithmDisabled,
CertExpired,
CertInvalidTime,
CertIsCa,
CertKeyUsage,
CertMitm,
CertNotYetValid,
CertRevoked,
CertSelfSigned,
CertSubjectInvalid,
CertUntrusted,
CertWeakKey,
IssuerEmptyName,
IssuerExpired,
IssuerNotYetValid,
IssuerUnknown,
IssuerUntrusted,
PolicyRejection,
Unknown,
CaInvalid = sec::SEC_ERROR_CA_CERT_INVALID,
CaNotV3 = mozpkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA,
CertAlgorithmDisabled = sec::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
CertExpired = sec::SEC_ERROR_EXPIRED_CERTIFICATE,
CertInvalidTime = sec::SEC_ERROR_INVALID_TIME,
CertIsCa = mozpkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY,
CertKeyUsage = sec::SEC_ERROR_INADEQUATE_KEY_USAGE,
CertMitm = mozpkix::MOZILLA_PKIX_ERROR_MITM_DETECTED,
CertNotYetValid = mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE,
CertRevoked = sec::SEC_ERROR_REVOKED_CERTIFICATE,
CertSelfSigned = mozpkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,
CertSubjectInvalid = ssl::SSL_ERROR_BAD_CERT_DOMAIN,
CertUntrusted = sec::SEC_ERROR_UNTRUSTED_CERT,
CertWeakKey = mozpkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE,
IssuerEmptyName = mozpkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME,
IssuerExpired = sec::SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
IssuerNotYetValid = mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE,
IssuerUnknown = sec::SEC_ERROR_UNKNOWN_ISSUER,
IssuerUntrusted = sec::SEC_ERROR_UNTRUSTED_ISSUER,
PolicyRejection = mozpkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED,
Unknown = sec::SEC_ERROR_LIBRARY_FAILURE,
}
impl From<AuthenticationStatus> for PRErrorCode {
#[must_use]
fn from(v: AuthenticationStatus) -> Self {
match v {
AuthenticationStatus::Ok => 0,
AuthenticationStatus::CaInvalid => sec::SEC_ERROR_CA_CERT_INVALID,
AuthenticationStatus::CaNotV3 => mozpkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA,
AuthenticationStatus::CertAlgorithmDisabled => {
sec::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
}
AuthenticationStatus::CertExpired => sec::SEC_ERROR_EXPIRED_CERTIFICATE,
AuthenticationStatus::CertInvalidTime => sec::SEC_ERROR_INVALID_TIME,
AuthenticationStatus::CertIsCa => {
mozpkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY
}
AuthenticationStatus::CertKeyUsage => sec::SEC_ERROR_INADEQUATE_KEY_USAGE,
AuthenticationStatus::CertMitm => mozpkix::MOZILLA_PKIX_ERROR_MITM_DETECTED,
AuthenticationStatus::CertNotYetValid => {
mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE
}
AuthenticationStatus::CertRevoked => sec::SEC_ERROR_REVOKED_CERTIFICATE,
AuthenticationStatus::CertSelfSigned => mozpkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,
AuthenticationStatus::CertSubjectInvalid => ssl::SSL_ERROR_BAD_CERT_DOMAIN,
AuthenticationStatus::CertUntrusted => sec::SEC_ERROR_UNTRUSTED_CERT,
AuthenticationStatus::CertWeakKey => mozpkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE,
AuthenticationStatus::IssuerEmptyName => mozpkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME,
AuthenticationStatus::IssuerExpired => sec::SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
AuthenticationStatus::IssuerNotYetValid => {
mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE
}
AuthenticationStatus::IssuerUnknown => sec::SEC_ERROR_UNKNOWN_ISSUER,
AuthenticationStatus::IssuerUntrusted => sec::SEC_ERROR_UNTRUSTED_ISSUER,
AuthenticationStatus::PolicyRejection => {
mozpkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED
}
AuthenticationStatus::Unknown => sec::SEC_ERROR_LIBRARY_FAILURE,
}
v as Self
}
}
// Note that this mapping should be removed after gecko eventually learns how to
// map into the enumerated type.
impl From<PRErrorCode> for AuthenticationStatus {
#[must_use]
fn from(v: PRErrorCode) -> Self {
match v {
0 => Self::Ok,
sec::SEC_ERROR_CA_CERT_INVALID => Self::CaInvalid,
mozpkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA => Self::CaNotV3,
sec::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED => Self::CertAlgorithmDisabled,
sec::SEC_ERROR_EXPIRED_CERTIFICATE => Self::CertExpired,
sec::SEC_ERROR_INVALID_TIME => Self::CertInvalidTime,
mozpkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY => Self::CertIsCa,
sec::SEC_ERROR_INADEQUATE_KEY_USAGE => Self::CertKeyUsage,
mozpkix::MOZILLA_PKIX_ERROR_MITM_DETECTED => Self::CertMitm,
mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE => Self::CertNotYetValid,
sec::SEC_ERROR_REVOKED_CERTIFICATE => Self::CertRevoked,
mozpkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT => Self::CertSelfSigned,
ssl::SSL_ERROR_BAD_CERT_DOMAIN => Self::CertSubjectInvalid,
sec::SEC_ERROR_UNTRUSTED_CERT => Self::CertUntrusted,
mozpkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE => Self::CertWeakKey,
mozpkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME => Self::IssuerEmptyName,
sec::SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE => Self::IssuerExpired,
mozpkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE => Self::IssuerNotYetValid,
sec::SEC_ERROR_UNKNOWN_ISSUER => Self::IssuerUnknown,
sec::SEC_ERROR_UNTRUSTED_ISSUER => Self::IssuerUntrusted,
mozpkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED => {
Self::PolicyRejection
}
_ => Self::Unknown,
}
Self::from_repr(v).unwrap_or(Self::Unknown)
}
}

View File

@@ -4,30 +4,49 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
use enum_map::Enum;
use strum::FromRepr;
use crate::ssl;
use crate::{ssl, Error};
// Ideally all of these would be enums, but size matters and we need to allow
// for values outside of those that are defined here.
pub type Alert = u8;
pub type Epoch = u16;
// TLS doesn't really have an "initial" concept that maps to QUIC so directly,
// but this should be clear enough.
pub const TLS_EPOCH_INITIAL: Epoch = 0_u16;
pub const TLS_EPOCH_ZERO_RTT: Epoch = 1_u16;
pub const TLS_EPOCH_HANDSHAKE: Epoch = 2_u16;
// Also, we don't use TLS epochs > 3.
pub const TLS_EPOCH_APPLICATION_DATA: Epoch = 3_u16;
#[derive(Default, Debug, Enum, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[repr(u16)]
pub enum Epoch {
// TLS doesn't really have an "initial" concept that maps to QUIC so directly,
// but this should be clear enough.
#[default]
Initial = 0,
ZeroRtt,
Handshake,
ApplicationData,
// Also, we don't use TLS epochs > 3.
}
impl TryFrom<u16> for Epoch {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::from_repr(value).ok_or(Error::InvalidEpoch)
}
}
impl From<Epoch> for usize {
fn from(e: Epoch) -> Self {
e as Self
}
}
/// Rather than defining a type alias and a bunch of constants, which leads to a ton of repetition,
/// use this macro.
macro_rules! remap_enum {
{ $t:ident: $s:ty { $( $n:ident = $v:path ),+ $(,)? } } => {
pub type $t = $s;
$(#[allow(clippy::cast_possible_truncation)] pub const $n: $t = $v as $t; )+
$(#[expect(clippy::cast_possible_truncation, reason = "Inherent in macro use.")] pub const $n: $t = $v as $t; )+
};
{ $t:ident: $s:ty => $e:ident { $( $n:ident = $v:ident ),+ $(,)? } } => {
remap_enum!{ $t: $s { $( $n = $e::$v ),+ } }
@@ -44,6 +63,7 @@ remap_enum! {
}
}
#[expect(dead_code, reason = "Code is bindgen-generated.")]
mod ciphers {
include!(concat!(env!("OUT_DIR"), "/nss_ciphers.rs"));
}

View File

@@ -107,7 +107,7 @@ pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
params.extend_from_slice(oid_slc);
let mut public_ptr: *mut SECKEYPublicKey = null_mut();
let mut param_item = Item::wrap(&params);
let mut param_item = Item::wrap(&params)?;
// If we have tracing on, try to ensure that key data can be read.
let insensitive_secret_ptr = if log::log_enabled!(log::Level::Trace) {

View File

@@ -4,29 +4,29 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
use std::{os::raw::c_char, str::Utf8Error};
use crate::ssl::{SECStatus, SECSuccess};
include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
#[expect(non_snake_case, dead_code, reason = "Code is bindgen-generated.")]
mod codes {
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
}
pub use codes::{SECErrorCodes as sec, SSLErrorCodes as ssl};
#[expect(dead_code, reason = "Code is bindgen-generated.")]
pub mod nspr {
include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
}
#[expect(dead_code, reason = "Some constants are not used.")]
pub mod mozpkix {
// These are manually extracted from the many bindings generated
// by bindgen when provided with the simple header:
// #include "mozpkix/pkixnss.h"
#[allow(non_camel_case_types)]
#[expect(non_camel_case_types, reason = "Code is bindgen-generated.")]
pub type mozilla_pkix_ErrorCode = ::std::os::raw::c_int;
pub const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE: mozilla_pkix_ErrorCode = -16384;
pub const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY: mozilla_pkix_ErrorCode = -16383;
@@ -83,11 +83,9 @@ impl Error {
}
impl std::error::Error for Error {
#[must_use]
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
#[must_use]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
@@ -100,13 +98,11 @@ impl std::fmt::Display for Error {
}
impl From<std::num::TryFromIntError> for Error {
#[must_use]
fn from(_: std::num::TryFromIntError) -> Self {
Self::IntegerOverflow
}
}
impl From<std::ffi::NulError> for Error {
#[must_use]
fn from(_: std::ffi::NulError) -> Self {
Self::InternalError
}

View File

@@ -7,10 +7,10 @@
#[macro_export]
macro_rules! experimental_api {
( $n:ident ( $( $a:ident : $t:ty ),* $(,)? ) ) => {
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::missing_errors_doc)]
#[expect(non_snake_case, reason = "Inherent in macro use.")]
#[allow(clippy::allow_attributes, clippy::too_many_arguments, reason = "Inherent in macro use.")]
#[allow(clippy::allow_attributes, clippy::missing_safety_doc, reason = "Inherent in macro use.")]
#[allow(clippy::allow_attributes, clippy::missing_errors_doc, reason = "Inherent in macro use.")]
pub unsafe fn $n ( $( $a : $t ),* ) -> Result<(), $crate::err::Error> {
const EXP_FUNCTION: &str = stringify!($n);
let n = ::std::ffi::CString::new(EXP_FUNCTION)?;

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(
clippy::unwrap_used,
reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed."
)]
use std::{
cell::RefCell,
os::raw::{c_uint, c_void},
@@ -75,7 +80,6 @@ impl ExtensionTracker {
f(&mut *rc.borrow_mut())
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
unsafe extern "C" fn extension_writer(
_fd: *mut PRFileDesc,
message: SSLHandshakeType::Type,
@@ -86,7 +90,12 @@ impl ExtensionTracker {
) -> PRBool {
let d = std::slice::from_raw_parts_mut(data, max_len as usize);
Self::wrap_handler_call(arg, |handler| {
// Cast is safe here because the message type is always part of the enum
#[allow(
clippy::allow_attributes,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "Cast is safe here because the message type is always part of the enum."
)]
match handler.write(message as HandshakeMessage, d) {
ExtensionWriterResult::Write(sz) => {
*len = c_uint::try_from(sz).expect("integer overflow from extension writer");
@@ -106,9 +115,14 @@ impl ExtensionTracker {
arg: *mut c_void,
) -> SECStatus {
let d = null_safe_slice(data, len);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Self::wrap_handler_call(arg, |handler| {
// Cast is safe here because the message type is always part of the enum
#[allow(
clippy::allow_attributes,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "Cast is safe here because the message type is always part of the enum."
)]
match handler.handle(message as HandshakeMessage, d) {
ExtensionHandlerResult::Ok => SECSuccess,
ExtensionHandlerResult::Alert(a) => {

View File

@@ -84,7 +84,7 @@ pub fn import_key(version: Version, buf: &[u8]) -> Res<SymKey> {
CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
PK11Origin::PK11_OriginUnwrap,
CK_ATTRIBUTE_TYPE::from(CKA_DERIVE),
&mut Item::wrap(buf),
&mut Item::wrap(buf)?,
null_mut(),
)
};

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use std::{
cell::RefCell,
fmt::{self, Debug},
@@ -106,7 +111,7 @@ impl HpKey {
mech,
CK_ATTRIBUTE_TYPE::from(CKA_ENCRYPT),
*key,
&Item::wrap(&ZERO[..0]), // Borrow a zero-length slice of ZERO.
&Item::wrap(&ZERO[..0])?, // Borrow a zero-length slice of ZERO.
)
};
let context = Context::from_ptr(context_ptr).or(Err(Error::CipherInitFailure))?;
@@ -169,7 +174,7 @@ impl HpKey {
ulNonceBits: 96,
};
let mut output_len: c_uint = 0;
let mut param_item = Item::wrap_struct(&params);
let mut param_item = Item::wrap_struct(&params)?;
secstatus_to_res(unsafe {
PK11_Encrypt(
**key,

View File

@@ -4,9 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)] // This lint doesn't work here.
#![allow(clippy::unseparated_literal_suffix, clippy::used_underscore_binding)] // For bindgen code.
mod aead;
#[cfg(feature = "disable-encryption")]
pub mod aead_null;
@@ -31,7 +28,7 @@ pub mod selfencrypt;
mod ssl;
mod time;
use std::{ffi::CString, path::PathBuf, ptr::null, sync::OnceLock};
use std::{env, ffi::CString, path::PathBuf, ptr::null, sync::OnceLock};
#[cfg(not(feature = "disable-encryption"))]
pub use self::aead::RealAead as Aead;
@@ -63,7 +60,7 @@ mod min_version;
use min_version::MINIMUM_NSS_VERSION;
use neqo_common::qerror;
#[allow(non_upper_case_globals)]
#[expect(non_upper_case_globals, reason = "Code is bindgen-generated.")]
mod nss {
include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
}
@@ -170,6 +167,9 @@ pub fn init() -> Res<()> {
///
/// If NSS cannot be initialized.
pub fn init_db<P: Into<PathBuf>>(dir: P) -> Res<()> {
// Allow overriding the NSS database path with an environment variable.
let dir = env::var("NSS_DB_PATH")
.unwrap_or(dir.into().to_str().ok_or(Error::InternalError)?.to_string());
let res = INITIALIZED.get_or_init(|| init_once(Some(dir.into())));
res.as_ref().map(|_| ()).map_err(Clone::clone)
}
@@ -201,7 +201,7 @@ where
if data.is_null() || len == 0 {
&[]
} else {
#[allow(clippy::disallowed_methods)]
#[expect(clippy::disallowed_methods, reason = "This is non-null.")]
std::slice::from_raw_parts(data, len)
}
}

View File

@@ -4,11 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
@@ -24,7 +19,14 @@ use crate::{
null_safe_slice,
};
#[allow(clippy::unreadable_literal)]
#[expect(
dead_code,
non_snake_case,
non_upper_case_globals,
non_camel_case_types,
clippy::unreadable_literal,
reason = "For included bindgen code."
)]
mod nss_p11 {
include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
}
@@ -44,6 +46,7 @@ macro_rules! scoped_ptr {
/// # Errors
///
/// When passed a null pointer generates an error.
#[allow(clippy::allow_attributes, dead_code, reason = "False positive.")]
pub fn from_ptr(ptr: *mut $target) -> Result<Self, $crate::err::Error> {
if ptr.is_null() {
Err($crate::err::Error::last_nss_error())
@@ -55,7 +58,6 @@ macro_rules! scoped_ptr {
impl Deref for $scoped {
type Target = *mut $target;
#[must_use]
fn deref(&self) -> &*mut $target {
&self.ptr
}
@@ -105,7 +107,6 @@ impl PublicKey {
}
impl Clone for PublicKey {
#[must_use]
fn clone(&self) -> Self {
let ptr = unsafe { SECKEY_CopyPublicKey(self.ptr) };
assert!(!ptr.is_null());
@@ -160,7 +161,6 @@ impl PrivateKey {
unsafe impl Send for PrivateKey {}
impl Clone for PrivateKey {
#[must_use]
fn clone(&self) -> Self {
let ptr = unsafe { SECKEY_CopyPrivateKey(self.ptr) };
assert!(!ptr.is_null());
@@ -208,7 +208,6 @@ impl SymKey {
}
impl Clone for SymKey {
#[must_use]
fn clone(&self) -> Self {
let ptr = unsafe { PK11_ReferenceSymKey(self.ptr) };
assert!(!ptr.is_null());
@@ -226,6 +225,12 @@ impl std::fmt::Debug for SymKey {
}
}
impl Default for SymKey {
fn default() -> Self {
Self { ptr: null_mut() }
}
}
unsafe fn destroy_pk11_context(ctxt: *mut PK11Context) {
PK11_DestroyContext(ctxt, PRBool::from(true));
}
@@ -247,25 +252,25 @@ impl Item {
/// Creating this object is technically safe, but using it is extremely dangerous.
/// Minimally, it can only be passed as a `const SECItem*` argument to functions,
/// or those that treat their argument as `const`.
pub fn wrap(buf: &[u8]) -> SECItem {
SECItem {
pub fn wrap(buf: &[u8]) -> Res<SECItem> {
Ok(SECItem {
type_: SECItemType::siBuffer,
data: buf.as_ptr().cast_mut(),
len: c_uint::try_from(buf.len()).unwrap(),
}
len: c_uint::try_from(buf.len())?,
})
}
/// Create a wrapper for a struct.
/// Creating this object is technically safe, but using it is extremely dangerous.
/// Minimally, it can only be passed as a `const SECItem*` argument to functions,
/// or those that treat their argument as `const`.
pub fn wrap_struct<T>(v: &T) -> SECItem {
pub fn wrap_struct<T>(v: &T) -> Res<SECItem> {
let data: *const T = v;
SECItem {
Ok(SECItem {
type_: SECItemType::siBuffer,
data: data.cast_mut().cast(),
len: c_uint::try_from(std::mem::size_of::<T>()).unwrap(),
}
len: c_uint::try_from(size_of::<T>())?,
})
}
/// Make an empty `SECItem` for passing as a mutable `SECItem*` argument.
@@ -276,20 +281,6 @@ impl Item {
len: 0,
}
}
/// This dereferences the pointer held by the item and makes a copy of the
/// content that is referenced there.
///
/// # Safety
///
/// This dereferences two pointers. It doesn't get much less safe.
pub unsafe fn into_vec(self) -> Vec<u8> {
let b = self.ptr.as_ref().unwrap();
// Sanity check the type, as some types don't count bytes in `Item::len`.
assert_eq!(b.type_, SECItemType::siBuffer);
let slc = null_safe_slice(b.data, b.len);
Vec::from(slc)
}
}
unsafe fn destroy_secitem_array(array: *mut SECItemArray) {
@@ -348,8 +339,8 @@ pub fn randomize<B: AsMut<[u8]>>(mut buf: B) -> B {
#[cfg(not(feature = "disable-random"))]
pub fn randomize<B: AsMut<[u8]>>(mut buf: B) -> B {
let m_buf = buf.as_mut();
let len = std::os::raw::c_int::try_from(m_buf.len()).unwrap();
secstatus_to_res(unsafe { PK11_GenerateRandom(m_buf.as_mut_ptr(), len) }).unwrap();
let len = std::os::raw::c_int::try_from(m_buf.len()).expect("usize fits into c_int");
secstatus_to_res(unsafe { PK11_GenerateRandom(m_buf.as_mut_ptr(), len) }).expect("NSS failed");
buf
}

View File

@@ -9,7 +9,9 @@
non_upper_case_globals,
non_snake_case,
clippy::cognitive_complexity,
clippy::too_many_lines
clippy::too_many_lines,
clippy::used_underscore_binding,
reason = "For included bindgen code."
)]
include!(concat!(env!("OUT_DIR"), "/nspr_io.rs"));

View File

@@ -44,7 +44,6 @@ scoped_ptr!(
/// It limits the exposure of servers to replay attack by rejecting 0-RTT
/// if it appears to be a replay. There is a false-positive rate that can be
/// managed by tuning the parameters used to create the context.
#[allow(clippy::module_name_repetitions)]
pub struct AntiReplay {
ctx: AntiReplayContext,
}

View File

@@ -4,9 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{os::raw::c_void, pin::Pin};
use std::{mem, os::raw::c_void, pin::Pin};
use enum_map::EnumMap;
use neqo_common::qdebug;
use strum::FromRepr;
use crate::{
agentio::as_c_void,
@@ -22,44 +24,37 @@ experimental_api!(SSL_SecretCallback(
arg: *mut c_void,
));
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, FromRepr)]
#[cfg_attr(windows, repr(i32))] // Windows has to be different, of course.
#[cfg_attr(not(windows), repr(u32))]
pub enum SecretDirection {
Read,
Write,
Read = SSLSecretDirection::ssl_secret_read,
Write = SSLSecretDirection::ssl_secret_write,
}
impl From<SSLSecretDirection::Type> for SecretDirection {
#[must_use]
fn from(dir: SSLSecretDirection::Type) -> Self {
match dir {
SSLSecretDirection::ssl_secret_read => Self::Read,
SSLSecretDirection::ssl_secret_write => Self::Write,
_ => unreachable!(),
}
Self::from_repr(dir).expect("Invalid secret direction")
}
}
#[derive(Debug, Default)]
#[allow(clippy::module_name_repetitions)]
pub struct DirectionalSecrets {
// We only need to maintain 3 secrets for the epochs used during the handshake.
secrets: [Option<SymKey>; 3],
secrets: EnumMap<Epoch, SymKey>,
}
impl DirectionalSecrets {
fn put(&mut self, epoch: Epoch, key: SymKey) {
assert!(epoch > 0);
let i = (epoch - 1) as usize;
assert!(i < self.secrets.len());
// assert!(self.secrets[i].is_none());
self.secrets[i] = Some(key);
debug_assert!(epoch != Epoch::Initial);
self.secrets[epoch] = key;
}
pub fn take(&mut self, epoch: Epoch) -> Option<SymKey> {
assert!(epoch > 0);
let i = (epoch - 1) as usize;
assert!(i < self.secrets.len());
self.secrets[i].take()
if self.secrets[epoch].is_null() {
None
} else {
Some(mem::take(&mut self.secrets[epoch]))
}
}
}
@@ -77,7 +72,15 @@ impl Secrets {
secret: *mut PK11SymKey,
arg: *mut c_void,
) {
let secrets = arg.cast::<Self>().as_mut().unwrap();
let Ok(epoch) = Epoch::try_from(epoch) else {
debug_assert!(false, "Invalid epoch");
// Don't touch secrets.
return;
};
let Some(secrets) = arg.cast::<Self>().as_mut() else {
debug_assert!(false, "No secrets");
return;
};
secrets.put_raw(epoch, dir, secret);
}

View File

@@ -126,7 +126,7 @@ impl SelfEncrypt {
///
/// Returns an error when the self-encrypted object is invalid;
/// when the keys have been rotated; or when NSS fails.
#[allow(clippy::similar_names)] // aad is similar to aead
#[expect(clippy::similar_names, reason = "aad is similar to aead.")]
pub fn open(&self, aad: &[u8], ciphertext: &[u8]) -> Res<Vec<u8>> {
if ciphertext[0] != Self::VERSION {
return Err(Error::SelfEncryptFailure);

View File

@@ -5,11 +5,13 @@
// except according to those terms.
#![allow(
clippy::allow_attributes,
dead_code,
non_upper_case_globals,
non_snake_case,
clippy::cognitive_complexity,
clippy::too_many_lines,
clippy::cognitive_complexity
reason = "For included bindgen code."
)]
use std::os::raw::{c_uint, c_void};
@@ -20,56 +22,41 @@ use crate::{
};
include!(concat!(env!("OUT_DIR"), "/nss_ssl.rs"));
#[expect(non_snake_case, reason = "OK here.")]
mod SSLOption {
include!(concat!(env!("OUT_DIR"), "/nss_sslopt.rs"));
}
// I clearly don't understand how bindgen operates.
pub enum PLArenaPool {}
pub enum PRFileDesc {}
// Remap some constants.
#[expect(non_upper_case_globals, reason = "OK here.")]
pub const SECSuccess: SECStatus = _SECStatus_SECSuccess;
#[expect(non_upper_case_globals, reason = "OK here.")]
pub const SECFailure: SECStatus = _SECStatus_SECFailure;
#[derive(Debug, Copy, Clone)]
#[repr(u32)]
pub enum Opt {
Locking,
Tickets,
OcspStapling,
Alpn,
ExtendedMasterSecret,
SignedCertificateTimestamps,
EarlyData,
RecordSizeLimit,
Tls13CompatMode,
HelloDowngradeCheck,
SuppressEndOfEarlyData,
Grease,
EnableChExtensionPermutation,
Locking = SSLOption::SSL_NO_LOCKS,
Tickets = SSLOption::SSL_ENABLE_SESSION_TICKETS,
OcspStapling = SSLOption::SSL_ENABLE_OCSP_STAPLING,
Alpn = SSLOption::SSL_ENABLE_ALPN,
ExtendedMasterSecret = SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET,
SignedCertificateTimestamps = SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
EarlyData = SSLOption::SSL_ENABLE_0RTT_DATA,
RecordSizeLimit = SSLOption::SSL_RECORD_SIZE_LIMIT,
Tls13CompatMode = SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE,
HelloDowngradeCheck = SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
SuppressEndOfEarlyData = SSLOption::SSL_SUPPRESS_END_OF_EARLY_DATA,
Grease = SSLOption::SSL_ENABLE_GREASE,
EnableChExtensionPermutation = SSLOption::SSL_ENABLE_CH_EXTENSION_PERMUTATION,
}
impl Opt {
// Cast is safe here because SSLOptions are within the i32 range
#[allow(clippy::cast_possible_wrap)]
#[must_use]
pub const fn as_int(self) -> PRInt32 {
let i = match self {
Self::Locking => SSLOption::SSL_NO_LOCKS,
Self::Tickets => SSLOption::SSL_ENABLE_SESSION_TICKETS,
Self::OcspStapling => SSLOption::SSL_ENABLE_OCSP_STAPLING,
Self::Alpn => SSLOption::SSL_ENABLE_ALPN,
Self::ExtendedMasterSecret => SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET,
Self::SignedCertificateTimestamps => SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
Self::EarlyData => SSLOption::SSL_ENABLE_0RTT_DATA,
Self::RecordSizeLimit => SSLOption::SSL_RECORD_SIZE_LIMIT,
Self::Tls13CompatMode => SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE,
Self::HelloDowngradeCheck => SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
Self::SuppressEndOfEarlyData => SSLOption::SSL_SUPPRESS_END_OF_EARLY_DATA,
Self::Grease => SSLOption::SSL_ENABLE_GREASE,
Self::EnableChExtensionPermutation => SSLOption::SSL_ENABLE_CH_EXTENSION_PERMUTATION,
};
i as PRInt32
self as PRInt32
}
// Some options are backwards, like SSL_NO_LOCKS, so use this to manage that.
@@ -86,11 +73,6 @@ impl Opt {
}
}
experimental_api!(SSL_GetCurrentEpoch(
fd: *mut PRFileDesc,
read_epoch: *mut u16,
write_epoch: *mut u16,
));
experimental_api!(SSL_HelloRetryRequestCallback(
fd: *mut PRFileDesc,
cb: SSLHelloRetryRequestCallback,

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![expect(
clippy::unwrap_used,
reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed."
)]
use std::{
ops::Deref,
os::raw::c_void,
@@ -139,7 +144,6 @@ impl TryInto<PRTime> for Time {
}
impl From<Time> for Instant {
#[must_use]
fn from(t: Time) -> Self {
t.t
}

View File

@@ -78,7 +78,7 @@ fn check_client_preinfo(client_preinfo: &SecretAgentPreInfo) {
assert_eq!(client_preinfo.cipher_suite(), None);
assert!(!client_preinfo.early_data());
assert_eq!(client_preinfo.early_data_cipher(), None);
assert_eq!(client_preinfo.max_early_data(), 0);
assert_eq!(client_preinfo.max_early_data(), Ok(0));
assert_eq!(client_preinfo.alpn(), None);
}
@@ -87,7 +87,7 @@ fn check_server_preinfo(server_preinfo: &SecretAgentPreInfo) {
assert_eq!(server_preinfo.cipher_suite(), Some(TLS_AES_128_GCM_SHA256));
assert!(!server_preinfo.early_data());
assert_eq!(server_preinfo.early_data_cipher(), None);
assert_eq!(server_preinfo.max_early_data(), 0);
assert_eq!(server_preinfo.max_early_data(), Ok(0));
assert_eq!(server_preinfo.alpn(), None);
}

View File

@@ -4,7 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
#![expect(clippy::unwrap_used, reason = "OK for tests.")]
use std::{mem, time::Instant};
@@ -16,8 +16,16 @@ use neqo_crypto::{
use test_fixture::{anti_replay, fixture_init, now};
/// Consume records until the handshake state changes.
#[allow(clippy::missing_panics_doc)]
#[allow(clippy::missing_errors_doc)]
#[allow(
clippy::allow_attributes,
clippy::missing_panics_doc,
reason = "OK for tests."
)]
#[allow(
clippy::allow_attributes,
clippy::missing_errors_doc,
reason = "OK for tests."
)]
pub fn forward_records(
now: Instant,
agent: &mut SecretAgent,
@@ -65,7 +73,11 @@ fn handshake(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
}
}
#[allow(clippy::missing_panics_doc)]
#[allow(
clippy::allow_attributes,
clippy::missing_panics_doc,
reason = "OK for tests."
)]
pub fn connect_at(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
handshake(now, client, server);
qinfo!("client: {:?}", client.state());
@@ -78,13 +90,19 @@ pub fn connect(client: &mut SecretAgent, server: &mut SecretAgent) {
connect_at(now(), client, server);
}
#[allow(clippy::missing_panics_doc)]
#[allow(
clippy::allow_attributes,
clippy::missing_panics_doc,
dead_code,
reason = "OK for tests."
)]
pub fn connect_fail(client: &mut SecretAgent, server: &mut SecretAgent) {
handshake(now(), client, server);
assert!(!client.state().is_connected());
assert!(!server.state().is_connected());
}
#[allow(clippy::allow_attributes, dead_code, reason = "False positive.")]
#[derive(Clone, Copy, Debug)]
pub enum Resumption {
WithoutZeroRtt,
@@ -115,6 +133,7 @@ impl ZeroRttChecker for PermissiveZeroRttChecker {
}
}
#[allow(clippy::allow_attributes, dead_code, reason = "False positive.")]
fn zero_rtt_setup(mode: Resumption, client: &Client, server: &mut Server) -> Option<AntiReplay> {
matches!(mode, Resumption::WithZeroRtt).then(|| {
client.enable_0rtt().expect("should enable 0-RTT on client");
@@ -131,7 +150,12 @@ fn zero_rtt_setup(mode: Resumption, client: &Client, server: &mut Server) -> Opt
})
}
#[allow(clippy::missing_panics_doc)]
#[allow(
clippy::allow_attributes,
clippy::missing_panics_doc,
dead_code,
reason = "OK for tests."
)]
#[must_use]
pub fn resumption_setup(mode: Resumption) -> (Option<AntiReplay>, ResumptionToken) {
fixture_init();

View File

@@ -26,7 +26,7 @@ fn hp_test(cipher: Cipher, expected: &[u8]) {
let mask = hp.mask(&[0; 16]).expect("should produce a mask");
assert_eq!(mask, expected, "first invocation should be correct");
#[allow(clippy::redundant_clone)] // This is deliberate.
#[expect(clippy::redundant_clone, reason = "This is deliberate.")]
let hp2 = hp.clone();
let mask = hp2.mask(&[0; 16]).expect("clone produces mask");
assert_eq!(mask, expected, "clone should produce the same mask");

View File

@@ -15,7 +15,11 @@ use neqo_crypto::{assert_initialized, init_db};
// Pull in the NSS internals so that we can ask NSS if it thinks that
// it is properly initialized.
#[allow(dead_code, non_upper_case_globals)]
#[expect(
non_upper_case_globals,
dead_code,
reason = "Code is bindgen-generated."
)]
mod nss {
include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
}

View File

@@ -5,6 +5,7 @@
// except according to those terms.
#![cfg(not(feature = "disable-encryption"))]
#![cfg(test)]
use neqo_crypto::{
constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"c82172fc083fd474f3c412afba7882d6792e3ee868da8e84881bfa5dd4c1195c","src/buffered_send_stream.rs":"d74c520af97bcdaa48862a396e6510c9f95124358dcf46a7722e4daf821fff2c","src/client_events.rs":"96d38deba366edbbe5f6e73d56d74ae36e239e35393ae3ac094d73063b564f68","src/conn_params.rs":"7f0df52bceda1923aef2b7c5c64a532f49ea083ea45e3dcd5bd4b03031b89643","src/connection.rs":"d225b55f1229988568085f133dac1cfe5d4adfd91864a7e1fc344a784a4b5594","src/connection_client.rs":"9a48949fe885e1139ae2c056a5504a3e336c8d75aae4cdcc2ad92355ad5aa623","src/connection_server.rs":"d16f73685ae29492e2d32321158b6206349fe1b0016d89bc50c2727f47c64140","src/control_stream_local.rs":"9325e5c8857036c68f7e49b81c898d5de51d43cb262e523385e6b2b7366c2cea","src/control_stream_remote.rs":"e7d0b5a195e3ef482b36972eac979b7d761215fcb76699112f2d4919708fcf26","src/features/extended_connect/mod.rs":"f9a08a6ec1dde79133c18c21b85f6b9a01468d98bace838f559340383cc603e7","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"51d6f3828c44b438eb1776e8dcce531af520f28bc0d715807d3f53a0eaa071d1","src/features/extended_connect/tests/webtransport/mod.rs":"9c0de1da3cfe9fac75f2d2faf3e0e0500261519e7862c6b5fec5097ab47f72f1","src/features/extended_connect/tests/webtransport/negotiation.rs":"8b505384d5b8a9c8e17194360a33d9ecbdb350c3359245b9f2bed9dc8cec7423","src/features/extended_connect/tests/webtransport/sessions.rs":"64b29ec9ba34e636b1109d99ab588666d85c85442565faa9071b743f4f764d6e","src/features/extended_connect/tests/webtransport/streams.rs":"eb0e793dc43ca9296334d29ced70d5ab3d90fd0c4ca3a485abad06c14eec29a4","src/features/extended_connect/webtransport_session.rs":"09a06af84e101f628f1ff2e9be344463662212f2d8914fe84b9675d9f0488af5","src/features/extended_connect/webtransport_streams.rs":"9855d77705acb7d21566333c4b297816e363be2ade14b8685fd1df4a4861cf74","src/features/mod.rs":"6f192996e6e2231a97f3fcae2aa02d648e571a6ec5e14b4a23b24b87888d117c","src/frames/hframe.rs":"5fcd9145a88e8ebf9ec1832c8ab61357a9afd362690e26d5a32595b2ae135395","src/frames/mod.rs":"0e6d49888d723b2c2c73df11020ceb88d9f062e9d4dc436eb38173e0b772d905","src/frames/reader.rs":"fff71d4e5a75ac16ed455c5cb7a12f355d56fe8bf7b33d6fb160e4bc48ee94cd","src/frames/tests/hframe.rs":"43a7735fc859692633e7f3c031710a9fb635611756cb4b9f387bac0a38c0fa09","src/frames/tests/mod.rs":"ee2eb2d88132e500640b156d5ae6b6ef5051e86eae09464a9cec827142ef6b73","src/frames/tests/reader.rs":"eaff7faa62aec5caf2ed21c6bd9e869654433b030df7676cc4843e2b15d1a6c1","src/frames/tests/wtframe.rs":"c6598d24f5e12972f02de6e1394362671633982db637a07e1c0bb9b56d93ea2a","src/frames/wtframe.rs":"544bc8fa74e3b15cde6e45ba47bcc52f098658e78664abfda8249a89c88d51b9","src/headers_checks.rs":"42f5bb95c6af9d7e747b3b4e39a2b10c4a323abd7da30cd4db4ebbc47696e060","src/lib.rs":"1fd024d8ec7fed5c4766f8a817531b1a4363bd2eaa60f9a41e4c2fed84682308","src/priority.rs":"946307329f31819d969093406ae5448f7923343ccc112221ea6eedf86cf447dc","src/push_controller.rs":"a03aa8b1e2db588f4342337252e7b7d10f43447498ddbc95733b5304a8e717ab","src/push_id.rs":"cd4cea3102b59918668b66725bbd19b66dc389e35d986132c5ceb72bbc9f3222","src/qlog.rs":"85187b6eee73d0ce085bc42dfc30999f4a51f92daa02a1a1ac1228a490131082","src/qpack_decoder_receiver.rs":"eb06c4be59da567fef70c20daa2c0f165c768131165479a210e69659f168b88f","src/qpack_encoder_receiver.rs":"831f3da9ec17966286786ba3f2c723395a132e65d6a33b4ec341fe7640c1a53d","src/recv_message.rs":"279c1de34d13bc43cfd5c622956d9a1a7e24153918b029c45c0358f384fef8fd","src/request_target.rs":"9720b9f87d66a7c2301bba7de5a5a9300f547613a63153a4d35c7a7506a59b31","src/send_message.rs":"16e688bcffe7aefa43bb5876cdad1f79b9227acad8695afc64f47c1565933e69","src/server.rs":"86c0a012b5dcb4d7c9de69360bf004dd30d75c7827b4cd60c282ca89c7bb2ed3","src/server_connection_events.rs":"1396baab265a814045ccfe63d637a4fdc32a667b5eb2925fa4951f5c3078fb20","src/server_events.rs":"26f3d3585633296a62c3e1e8594e491f45e4c6d17c3cd75847c3cdec5778c2e1","src/settings.rs":"d0f8c546e70161422a029a40564b9e9b953fe671c60835196b16f3364779eaf9","src/stream_type_reader.rs":"5ba77600c5e8c4abdd9443aec20311cf8a812b97ab18a90408aa4e689f42bded","tests/httpconn.rs":"5e2318ac776ce8b4c0d3c44b42fc32e6609b2ef31853767312462eae9a7c659d","tests/priority.rs":"f3869e5fbf63483f04b589d3447e02a4f167b2f3c406028cb30a87ad9a22da53","tests/send_message.rs":"21de05a3c6d516d28df6d77d7e4fb1b05985fc2debde1e8c7fb0607164c74b75","tests/webtransport.rs":"4ae2531c8384120b541198757106954b1dac9f2dcb39ed2231a67a59c1fb94f8"},"package":null}
{"files":{"Cargo.toml":"2ea9b93fd8d1cd8fed7eb33f1f5ab004f6896812d5b53903d15ad09982709943","benches/streams.rs":"00140c3f08abd66b5c785772d110a19b3e75882e6c6a4ea912565a1c18add57e","src/buffered_send_stream.rs":"39c10972e27d2c5c79703a13cf74bd6e5b013c29176f2b352e16cf4d9481d4de","src/client_events.rs":"38e48177d4bc301dce85e4953670e54a39b6bf1ce657412f9fb3497589d099e9","src/conn_params.rs":"7f0df52bceda1923aef2b7c5c64a532f49ea083ea45e3dcd5bd4b03031b89643","src/connection.rs":"e1aecf717d032c5228f0d8bee86d2d2960f471596676ba3fd61445625aa23e77","src/connection_client.rs":"48cd86a73960affe2cf101c8db6d12f5e6c7e8dab3412051482a691537ad56b4","src/connection_server.rs":"d16f73685ae29492e2d32321158b6206349fe1b0016d89bc50c2727f47c64140","src/control_stream_local.rs":"6b7774bc392292d66da4b4e915a477942cdcc0b287b38d77c24f1638f4b85755","src/control_stream_remote.rs":"e7d0b5a195e3ef482b36972eac979b7d761215fcb76699112f2d4919708fcf26","src/features/extended_connect/mod.rs":"f24b419d28eaffe47444120443f2b5613db9b5b5154f4b5725b35d8640c222e1","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"51d6f3828c44b438eb1776e8dcce531af520f28bc0d715807d3f53a0eaa071d1","src/features/extended_connect/tests/webtransport/mod.rs":"4e81ae458ecaa1d0f29b7565c19c9751658fe0c7496ce40d7c90a102d9f04a16","src/features/extended_connect/tests/webtransport/negotiation.rs":"b0083f8737bdea9bc0de1940c627d497fee8b79ebc218bbcea0a562ae530527f","src/features/extended_connect/tests/webtransport/sessions.rs":"64b29ec9ba34e636b1109d99ab588666d85c85442565faa9071b743f4f764d6e","src/features/extended_connect/tests/webtransport/streams.rs":"eb0e793dc43ca9296334d29ced70d5ab3d90fd0c4ca3a485abad06c14eec29a4","src/features/extended_connect/webtransport_session.rs":"5888cd09d204738b1ef4e53b83f6c5ccee33d5d3549502d39b5332db3c1c53bd","src/features/extended_connect/webtransport_streams.rs":"9855d77705acb7d21566333c4b297816e363be2ade14b8685fd1df4a4861cf74","src/features/mod.rs":"6f192996e6e2231a97f3fcae2aa02d648e571a6ec5e14b4a23b24b87888d117c","src/frames/hframe.rs":"8cf686390f2bfd28c92a0badcb647c6ea439eeee2a8392e088f6d9af6662854d","src/frames/mod.rs":"ec86e32919bd4cf0baa8686444cb6062d6eabea9b74ef58680f5da476fdcc77c","src/frames/reader.rs":"08df3ec01b29e7b1001160cf63b27f393eafdb1891888e9751d20dd36e903781","src/frames/tests/hframe.rs":"43a7735fc859692633e7f3c031710a9fb635611756cb4b9f387bac0a38c0fa09","src/frames/tests/mod.rs":"4bbdf566a7370605d49373027b38c4bcbd91113f7693efee29b9d91e3914ad98","src/frames/tests/reader.rs":"35f94d1ce1d088fca35592d8a2898c49bd6df0f3facfa12ef6b918124a1356bc","src/frames/tests/wtframe.rs":"c6598d24f5e12972f02de6e1394362671633982db637a07e1c0bb9b56d93ea2a","src/frames/wtframe.rs":"544bc8fa74e3b15cde6e45ba47bcc52f098658e78664abfda8249a89c88d51b9","src/headers_checks.rs":"93b69056321eefa8b0260bbfd8ba82064de2fcb915c7fd63e0f2855ea6187eba","src/lib.rs":"f085007ac4fe1f75ca6f66f55836c395d6c36ccd849b3dfa77958e8bfad2daa4","src/priority.rs":"a32de5a2e333d387e6c73ebe7f1cbd2b14e42724145aed5d6d6c1bd787ca7704","src/push_controller.rs":"ebd055be1be92f9709d57b4347a4f2ceb56b8d1e48e5df1dc7c8cb475529dae6","src/push_id.rs":"cd4cea3102b59918668b66725bbd19b66dc389e35d986132c5ceb72bbc9f3222","src/qlog.rs":"6c0b622cd3ec7d64cb920d38fca0b0e87a43759c6fd4c2d63c352aa9c7ad11bf","src/qpack_decoder_receiver.rs":"eb06c4be59da567fef70c20daa2c0f165c768131165479a210e69659f168b88f","src/qpack_encoder_receiver.rs":"831f3da9ec17966286786ba3f2c723395a132e65d6a33b4ec341fe7640c1a53d","src/recv_message.rs":"cfdc209630332c013b075ff13c28227ecde6d93358116eff82cba88ca37d1c61","src/request_target.rs":"8b8c2d1221e1956f13b66d18f8bc880062549ce5f204f15d39c7d69f727875c9","src/send_message.rs":"4eadd7d5bd8f33e0d2212472200eaacf9f848c795d940ec543247cf4e0f89dbf","src/server.rs":"58da712bfa9fd2901df72d17b1cc7afb2cb8e30af508e3b8239de8841284d36b","src/server_connection_events.rs":"9b429916357f967d697cd9806c720e3ca7cf152603b0ddb16a7f9992a8188ee2","src/server_events.rs":"5599f3c87dfe4ac168c1279967617f702722fac0cb6c9dacf2dfdd31388fb815","src/settings.rs":"ae0b57b462803b963a17616863054e5d6858a89ff1021b52b43de8f72365bdc8","src/stream_type_reader.rs":"5ba77600c5e8c4abdd9443aec20311cf8a812b97ab18a90408aa4e689f42bded","tests/httpconn.rs":"a4dbbfa8bed000ca983bf4f2a015ee49283603924ae5fca8293b0a38f8ee5c2b","tests/priority.rs":"31aede7d5b6c66c415cbf9f0298c8de6fd9591e3c2d455ce24c5a30d56922d09","tests/send_message.rs":"f07c152d97ad074caf6dd52ea26b5eeb57f60a850d109179bae376cd1dcd6dee","tests/webtransport.rs":"1634de76a3081877670d015cfd7b7f7225f8f1c7ddfba729bd5070a25ba10da0"},"package":null}

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.76.0"
rust-version = "1.82.0"
name = "neqo-http3"
version = "0.12.2"
version = "0.13.1"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = false
autolib = false
@@ -76,6 +76,12 @@ path = "tests/send_message.rs"
name = "webtransport"
path = "tests/webtransport.rs"
[[bench]]
name = "streams"
path = "benches/streams.rs"
harness = false
required-features = ["bench"]
[dependencies.enumset]
version = "1.1"
default-features = false
@@ -97,18 +103,27 @@ path = "./../neqo-qpack"
path = "./../neqo-transport"
[dependencies.qlog]
version = "0.13"
version = "0.15.1"
default-features = false
[dependencies.sfv]
version = "0.9"
default-features = false
[dependencies.strum]
version = "0.26"
features = ["derive"]
default-features = false
[dependencies.url]
version = "2.5.3"
version = "2.5"
features = ["std"]
default-features = false
[dev-dependencies.criterion]
version = "0.5"
default-features = false
[dev-dependencies.neqo-http3]
path = "."
features = ["draft-29"]
@@ -121,22 +136,56 @@ features = ["draft-29"]
path = "../test-fixture"
[lints.clippy]
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
cfg_not_test = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
infinite_loop = "warn"
large_include_file = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
literal_string_with_formatting_args = "allow"
lossy_float_literal = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
module_name_repetitions = "warn"
multiple_crate_versions = "allow"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
pathbuf_init_then_push = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
self_named_module_files = "warn"
semicolon_inside_block = "warn"
string_lit_chars_any = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
try_err = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[lints.clippy.cargo]
level = "warn"
@@ -153,7 +202,6 @@ priority = -1
[lints.rust]
absolute_paths_not_starting_with_crate = "warn"
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
@@ -165,3 +213,4 @@ unit_bindings = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

View File

@@ -0,0 +1,101 @@
// 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.
#![expect(clippy::unwrap_used, reason = "OK in a bench.")]
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use neqo_crypto::AuthenticationStatus;
use neqo_http3::{Http3Client, Http3Parameters, Http3Server, Priority};
use neqo_transport::{ConnectionParameters, StreamType};
use test_fixture::{
fixture_init, http3_client_with_params, http3_server_with_params, now, DEFAULT_SERVER_NAME,
};
const STREAM_TYPE: StreamType = StreamType::BiDi;
const STREAMS_MAX: u64 = 1 << 60;
fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server, is_handshake: bool) {
let mut out = None;
let mut auth_needed = is_handshake;
loop {
out = client.process(out, now()).dgram();
let client_out_is_none = out.is_none();
if auth_needed && client.peer_certificate().is_some() {
client.authenticated(AuthenticationStatus::Ok, now());
auth_needed = false;
}
out = server.process(out, now()).dgram();
if client_out_is_none && out.is_none() {
break;
}
}
}
fn use_streams(client: &mut Http3Client, server: &mut Http3Server, streams: usize, data: &[u8]) {
let stream_ids = (0..streams)
.map(|_| {
client
.fetch(
now(),
"GET",
&("https", DEFAULT_SERVER_NAME, "/"),
&[],
Priority::default(),
)
.unwrap()
})
.collect::<Vec<_>>();
exchange_packets(client, server, false);
for stream_id in &stream_ids {
client.send_data(*stream_id, data).unwrap();
}
exchange_packets(client, server, false);
for stream_id in &stream_ids {
client.stream_close_send(*stream_id).unwrap();
}
exchange_packets(client, server, false);
}
fn connect() -> (Http3Client, Http3Server) {
let cp = ConnectionParameters::default()
.max_streams(STREAM_TYPE, STREAMS_MAX)
.pmtud(false)
.pacing(false)
.mlkem(false)
.sni_slicing(false);
let h3p = Http3Parameters::default().connection_parameters(cp);
let mut client = http3_client_with_params(h3p.clone());
let mut server = http3_server_with_params(h3p);
exchange_packets(&mut client, &mut server, true);
(client, server)
}
fn criterion_benchmark(c: &mut Criterion) {
fixture_init();
for (streams, data_size) in [
(1, 1),
(1000, 1),
(10000, 1),
(1, 1000),
(100, 1000),
(1000, 1000),
] {
let mut group = c.benchmark_group(format!("{streams} streams of {data_size} bytes"));
group.bench_function("multistream", |b| {
let data = vec![0; data_size];
b.iter_batched_ref(
connect,
|(client, server)| use_streams(client, server, streams, &data),
BatchSize::PerIteration,
);
});
group.finish();
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -9,16 +9,14 @@ use neqo_transport::{Connection, StreamId};
use crate::{qlog, Res};
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Default)]
pub enum BufferedStream {
#[default]
Uninitialized,
Initialized { stream_id: StreamId, buf: Vec<u8> },
}
impl Default for BufferedStream {
fn default() -> Self {
Self::Uninitialized
}
Initialized {
stream_id: StreamId,
buf: Vec<u8>,
},
}
impl ::std::fmt::Display for BufferedStream {

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use neqo_common::{event::Provider as EventProvider, Header};
@@ -16,7 +14,8 @@ use crate::{
connection::Http3State,
features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason},
settings::HSettingType,
CloseType, Http3StreamInfo, HttpRecvStreamEvents, PushId, RecvStreamEvents, SendStreamEvents,
CloseType, Error, Http3StreamInfo, HttpRecvStreamEvents, PushId, RecvStreamEvents, Res,
SendStreamEvents,
};
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -220,13 +219,14 @@ impl ExtendedConnectEvents for Http3ClientEvents {
}
}
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) {
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) -> Res<()> {
self.insert(Http3ClientEvent::WebTransport(
WebTransportEvent::NewStream {
stream_id: stream_info.stream_id(),
session_id: stream_info.session_id().unwrap(),
session_id: stream_info.session_id().ok_or(Error::Internal)?,
},
));
Ok(())
}
fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>) {

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::{
cell::RefCell,
collections::{BTreeSet, HashMap},
@@ -20,6 +18,7 @@ use neqo_transport::{
streams::SendOrder, AppError, CloseReason, Connection, DatagramTracking, State, StreamId,
StreamType, ZeroRttState,
};
use strum::Display;
use crate::{
client_events::Http3ClientEvents,
@@ -55,20 +54,12 @@ where
pub priority: Priority,
}
#[derive(Display)]
pub enum WebTransportSessionAcceptAction {
Accept,
Reject(Vec<Header>),
}
impl ::std::fmt::Display for WebTransportSessionAcceptAction {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match self {
Self::Accept => f.write_str("Accept"),
Self::Reject(_) => f.write_str("Reject"),
}
}
}
#[derive(Debug)]
enum Http3RemoteSettingsState {
NotReceived,
@@ -517,7 +508,6 @@ impl Http3Connection {
output = self.handle_new_stream(conn, stream_type, stream_id)?;
}
#[allow(clippy::match_same_arms)] // clippy is being stupid here
match output {
ReceiveOutput::UnblockedStreams(unblocked_streams) => {
self.handle_unblocked_streams(unblocked_streams, conn)?;
@@ -536,11 +526,11 @@ impl Http3Connection {
NewStreamType::Push(_)
| NewStreamType::Http(_)
| NewStreamType::WebTransportStream(_),
) => Ok(output),
)
| ReceiveOutput::NoOutput => Ok(output),
ReceiveOutput::NewStream(_) => {
unreachable!("NewStream should have been handled already")
}
ReceiveOutput::NoOutput => Ok(output),
}
}
@@ -727,13 +717,13 @@ impl Http3Connection {
match conn.stream_fairness(stream_id, true) {
Ok(()) | Err(neqo_transport::Error::InvalidStreamId) => (),
Err(e) => return Err(Error::from(e)),
};
}
qinfo!("[{self}] A new WebTransport stream {stream_id} for session {session_id}");
}
NewStreamType::Unknown => {
conn.stream_stop_sending(stream_id, Error::HttpStreamCreation.code())?;
}
};
}
match stream_type {
NewStreamType::Control | NewStreamType::Decoder | NewStreamType::Encoder => {
@@ -891,7 +881,7 @@ impl Http3Connection {
send_message
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.send_headers(&final_headers, conn)?;
self.add_streams(
@@ -1182,9 +1172,13 @@ impl Http3Connection {
stream_id,
events,
self.role,
self.recv_streams.remove(&stream_id).unwrap(),
self.send_streams.remove(&stream_id).unwrap(),
)));
self.recv_streams
.remove(&stream_id)
.ok_or(Error::Internal)?,
self.send_streams
.remove(&stream_id)
.ok_or(Error::Internal)?,
)?));
self.add_streams(
stream_id,
Box::new(Rc::clone(&extended_conn)),
@@ -1258,7 +1252,7 @@ impl Http3Connection {
send_events,
recv_events,
true,
);
)?;
Ok(stream_id)
}
@@ -1285,7 +1279,7 @@ impl Http3Connection {
send_events,
recv_events,
false,
);
)?;
Ok(())
}
@@ -1297,8 +1291,8 @@ impl Http3Connection {
send_events: Box<dyn SendStreamEvents>,
recv_events: Box<dyn RecvStreamEvents>,
local: bool,
) {
webtransport_session.borrow_mut().add_stream(stream_id);
) -> Res<()> {
webtransport_session.borrow_mut().add_stream(stream_id)?;
if stream_id.stream_type() == StreamType::UniDi {
if local {
self.send_streams.insert(
@@ -1340,6 +1334,7 @@ impl Http3Connection {
)),
);
}
Ok(())
}
pub fn webtransport_send_datagram(
@@ -1481,7 +1476,7 @@ impl Http3Connection {
.http_stream()
.ok_or(Error::InvalidStreamId)?;
if stream.maybe_update_priority(priority) {
if stream.maybe_update_priority(priority)? {
self.control_stream_local.queue_update_priority(stream_id);
Ok(true)
} else {
@@ -1559,7 +1554,7 @@ impl Http3Connection {
let stream = self.recv_streams.remove(&stream_id);
if let Some(s) = &stream {
if s.stream_type() == Http3StreamType::ExtendedConnect {
self.send_streams.remove(&stream_id).unwrap();
self.send_streams.remove(&stream_id)?;
if let Some(wt) = s.webtransport() {
self.remove_extended_connect(&wt, conn);
}
@@ -1576,7 +1571,7 @@ impl Http3Connection {
let stream = self.send_streams.remove(&stream_id);
if let Some(s) = &stream {
if s.stream_type() == Http3StreamType::ExtendedConnect {
if let Some(wt) = self.recv_streams.remove(&stream_id).unwrap().webtransport() {
if let Some(wt) = self.recv_streams.remove(&stream_id)?.webtransport() {
self.remove_extended_connect(&wt, conn);
}
}

View File

@@ -785,7 +785,8 @@ impl Http3Client {
/// This cannot panic. The max varint length is 8.
pub fn webtransport_max_datagram_size(&self, session_id: StreamId) -> Res<u64> {
Ok(self.conn.max_datagram_size()?
- u64::try_from(Encoder::varint_len(session_id.as_u64())).unwrap())
- u64::try_from(Encoder::varint_len(session_id.as_u64()))
.map_err(|_| Error::Internal)?)
}
/// Sets the `SendOrder` for a given stream
@@ -841,7 +842,11 @@ impl Http3Client {
}
/// This function combines `process_input` and `process_output` function.
pub fn process(&mut self, dgram: Option<Datagram<impl AsRef<[u8]>>>, now: Instant) -> Output {
pub fn process(
&mut self,
dgram: Option<Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) -> Output {
qtrace!("[{self}] Process");
if let Some(d) = dgram {
self.process_input(d, now);
@@ -859,13 +864,13 @@ impl Http3Client {
/// packets need to be sent or if a timer needs to be updated.
///
/// [1]: ../neqo_transport/enum.ConnectionEvent.html
pub fn process_input(&mut self, dgram: Datagram<impl AsRef<[u8]>>, now: Instant) {
pub fn process_input(&mut self, dgram: Datagram<impl AsRef<[u8]> + AsMut<[u8]>>, now: Instant) {
self.process_multiple_input(iter::once(dgram), now);
}
pub fn process_multiple_input(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<impl AsRef<[u8]>>>,
dgrams: impl IntoIterator<Item = Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) {
let mut dgrams = dgrams.into_iter().peekable();
@@ -1279,7 +1284,7 @@ mod tests {
use neqo_qpack::{encoder::QPackEncoder, QpackSettings};
use neqo_transport::{
CloseReason, ConnectionEvent, ConnectionParameters, Output, State, StreamId, StreamType,
Version, MIN_INITIAL_PACKET_SIZE, RECV_BUFFER_SIZE, SEND_BUFFER_SIZE,
Version, INITIAL_RECV_WINDOW_SIZE, MIN_INITIAL_PACKET_SIZE,
};
use test_fixture::{
anti_replay, default_server_h3, fixture_init, new_server, now,
@@ -1304,7 +1309,7 @@ mod tests {
assert_eq!(err, CloseReason::Application(expected.code()));
}
_ => panic!("Wrong state {:?}", client.state()),
};
}
}
/// Create a http3 client with default configuration.
@@ -1625,12 +1630,16 @@ mod tests {
fn handshake_only(client: &mut Http3Client, server: &mut TestServer) -> Output {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
server.conn.process_input(out.dgram().unwrap(), now());
let out = server.conn.process(out2.dgram(), now());
assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
assert!(out.as_dgram_ref().is_none());
@@ -2730,7 +2739,7 @@ mod tests {
if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
if stream_id == request_stream_id {
// Read the DATA frame.
let mut buf = vec![1_u8; RECV_BUFFER_SIZE];
let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE];
let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap();
assert!(fin);
assert_eq!(
@@ -2803,7 +2812,7 @@ mod tests {
assert_eq!(sent, Ok(first_frame.len()));
// The second frame cannot fit.
let sent = client.send_data(request_stream_id, &vec![0_u8; SEND_BUFFER_SIZE]);
let sent = client.send_data(request_stream_id, &vec![0_u8; INITIAL_RECV_WINDOW_SIZE]);
assert_eq!(sent, Ok(expected_second_data_frame.len()));
// Close stream.
@@ -2812,7 +2821,7 @@ mod tests {
let mut out = client.process_output(now());
// We need to loop a bit until all data has been sent. Once for every 1K
// of data.
for _i in 0..SEND_BUFFER_SIZE / 1000 {
for _i in 0..INITIAL_RECV_WINDOW_SIZE / 1000 {
out = server.conn.process(out.dgram(), now());
out = client.process(out.dgram(), now());
}
@@ -2822,7 +2831,7 @@ mod tests {
if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
if stream_id == request_stream_id {
// Read DATA frames.
let mut buf = vec![1_u8; RECV_BUFFER_SIZE];
let mut buf = vec![1_u8; INITIAL_RECV_WINDOW_SIZE];
let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap();
assert!(fin);
assert_eq!(
@@ -2875,7 +2884,7 @@ mod tests {
// After the first frame there is exactly 63+2 bytes left in the send buffer.
#[test]
fn fetch_two_data_frame_second_63bytes() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 88);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 88);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]);
}
@@ -2884,7 +2893,7 @@ mod tests {
// but we can only send 63 bytes.
#[test]
fn fetch_two_data_frame_second_63bytes_place_for_66() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 89);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 89);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]);
}
@@ -2893,7 +2902,7 @@ mod tests {
// but we can only send 64 bytes.
#[test]
fn fetch_two_data_frame_second_64bytes_place_for_67() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 90);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 90);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x40, 0x40], &[0_u8; 64]);
}
@@ -2901,7 +2910,7 @@ mod tests {
// After the first frame there is exactly 16383+3 bytes left in the send buffer.
#[test]
fn fetch_two_data_frame_second_16383bytes() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16409);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16409);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}
@@ -2910,7 +2919,7 @@ mod tests {
// send 16383 bytes.
#[test]
fn fetch_two_data_frame_second_16383bytes_place_for_16387() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16410);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16410);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}
@@ -2919,7 +2928,7 @@ mod tests {
// send 16383 bytes.
#[test]
fn fetch_two_data_frame_second_16383bytes_place_for_16388() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16411);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16411);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]);
}
@@ -2928,7 +2937,7 @@ mod tests {
// 16384 bytes.
#[test]
fn fetch_two_data_frame_second_16384bytes_place_for_16389() {
let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16412);
let (buf, hdr) = alloc_buffer(INITIAL_RECV_WINDOW_SIZE - 16412);
fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x80, 0x0, 0x40, 0x0], &[0_u8; 16384]);
}
@@ -3675,7 +3684,7 @@ mod tests {
panic!("We should not receive a DataGeadable event!");
}
_ => {}
};
}
}
// ok NOW send fin
@@ -3699,7 +3708,7 @@ mod tests {
assert!(fin);
}
_ => {}
};
}
}
// Stream should now be closed and gone
@@ -3751,7 +3760,7 @@ mod tests {
assert_eq!(Ok((0, true)), client.read_data(now(), stream_id, &mut buf));
}
_ => {}
};
}
}
// Stream should now be closed and gone
@@ -3800,7 +3809,7 @@ mod tests {
panic!("We should not receive a DataGeadable event!");
}
_ => {}
};
}
}
// ok NOW send fin
@@ -3824,7 +3833,7 @@ mod tests {
assert!(fin);
}
_ => {}
};
}
}
// Stream should now be closed and gone
@@ -3871,7 +3880,7 @@ mod tests {
assert!(!fin);
}
_ => {}
};
}
}
// ok NOW send fin
@@ -4158,10 +4167,12 @@ mod tests {
let (mut client, mut server) = start_with_0rtt();
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
server.conn.process_input(out.dgram().unwrap(), now());
let out = server.conn.process(out2.dgram(), now());
// Check that control and qpack streams are received and a
// SETTINGS frame has been received.
@@ -4175,6 +4186,8 @@ mod tests {
assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
assert_eq!(client.state(), Http3State::Connected);
drop(server.conn.process(out.dgram(), now()));
@@ -4193,10 +4206,12 @@ mod tests {
assert_eq!(request_stream_id, 0);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
server.conn.process_input(out.dgram().unwrap(), now());
let out = server.conn.process(out2.dgram(), now());
// Check that control and qpack streams are received and a
// SETTINGS frame has been received.
@@ -4210,6 +4225,8 @@ mod tests {
assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
assert_eq!(client.state(), Http3State::Connected);
let out = server.conn.process(out.dgram(), now());
assert!(server.conn.state().connected());
@@ -4280,17 +4297,19 @@ mod tests {
let client_0rtt = client.process_output(now());
assert!(client_0rtt.as_dgram_ref().is_some());
let server_hs = server.process(client_hs.dgram(), now());
server.process_input(client_hs.dgram().unwrap(), now());
let server_hs = server.process(client_0rtt.dgram(), now());
assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc...
let server_ignored = server.process(client_0rtt.dgram(), now());
assert!(server_ignored.as_dgram_ref().is_none());
let dgram = client.process(server_hs.dgram(), now()).dgram();
let dgram = server.process(dgram, now());
// The server shouldn't receive that 0-RTT data.
let recvd_stream_evt = |e| matches!(e, ConnectionEvent::NewStream { .. });
assert!(!server.events().any(recvd_stream_evt));
// Client should get a rejection.
let client_out = client.process(server_hs.dgram(), now());
let client_out = client.process(dgram.dgram(), now());
assert!(client_out.as_dgram_ref().is_some());
let recvd_0rtt_reject = |e| e == Http3ClientEvent::ZeroRttRejected;
assert!(client.events().any(recvd_0rtt_reject));
@@ -4329,10 +4348,12 @@ mod tests {
.expect("Set resumption token");
assert_eq!(client.state(), Http3State::ZeroRtt);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
server.conn.process_input(out.dgram().unwrap(), now());
let out = server.conn.process(out2.dgram(), now());
// Check that control and qpack streams and a SETTINGS frame are received.
// Also qpack encoder stream will send "change capacity" instruction because it has
@@ -4345,6 +4366,8 @@ mod tests {
assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
assert_eq!(client.state(), Http3State::Connected);
drop(server.conn.process(out.dgram(), now()));

View File

@@ -9,7 +9,7 @@ use std::collections::{HashMap, VecDeque};
use neqo_common::{qtrace, Encoder};
use neqo_transport::{Connection, StreamId, StreamType};
use crate::{frames::HFrame, BufferedStream, Http3StreamType, RecvStream, Res};
use crate::{frames::HFrame, BufferedStream, Error, Http3StreamType, RecvStream, Res};
pub const HTTP3_UNI_STREAM_TYPE_CONTROL: u64 = 0x0;
@@ -73,14 +73,14 @@ impl ControlStreamLocal {
update_stream.stream_type(),
Http3StreamType::Http | Http3StreamType::Push
));
let stream = update_stream.http_stream().unwrap();
let stream = update_stream.http_stream().ok_or(Error::Internal)?;
// in case multiple priority_updates were issued, ignore now irrelevant
if let Some(hframe) = stream.priority_update_frame() {
let mut enc = Encoder::new();
hframe.encode(&mut enc);
if self.stream.send_atomic(conn, enc.as_ref())? {
stream.priority_update_sent();
stream.priority_update_sent()?;
} else {
self.outstanding_priority_update.push_front(update_id);
break;
@@ -95,7 +95,7 @@ impl ControlStreamLocal {
qtrace!("[{self}] Create a control stream");
self.stream.init(conn.stream_create(StreamType::UniDi)?);
self.stream
.buffer(&[u8::try_from(HTTP3_UNI_STREAM_TYPE_CONTROL).unwrap()]);
.buffer(&[u8::try_from(HTTP3_UNI_STREAM_TYPE_CONTROL).map_err(|_| Error::Internal)?]);
Ok(())
}

View File

@@ -17,7 +17,7 @@ use crate::{
client_events::Http3ClientEvents,
features::NegotiationState,
settings::{HSettingType, HSettings},
CloseType, Http3StreamInfo, Http3StreamType,
CloseType, Http3StreamInfo, Http3StreamType, Res,
};
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -56,7 +56,7 @@ pub(crate) trait ExtendedConnectEvents: Debug {
reason: SessionCloseReason,
headers: Option<Vec<Header>>,
);
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo);
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) -> Res<()>;
fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>);
}
@@ -67,12 +67,18 @@ pub(crate) enum ExtendedConnectType {
impl ExtendedConnectType {
#[must_use]
#[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType.
#[expect(
clippy::unused_self,
reason = "This will change when we have more features using ExtendedConnectType."
)]
pub const fn string(self) -> &'static str {
"webtransport"
}
#[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType.
#[expect(
clippy::unused_self,
reason = "This will change when we have more features using ExtendedConnectType."
)]
#[must_use]
pub const fn get_stream_type(self, session_id: StreamId) -> Http3StreamType {
Http3StreamType::WebTransport(session_id)

View File

@@ -82,8 +82,12 @@ fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
fn connect_with(client: &mut Http3Client, server: &mut Http3Server) {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);
_ = server.process(out.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());

View File

@@ -270,5 +270,5 @@ fn wrong_setting_value() {
assert_eq!(err, CloseReason::Application(Error::HttpSettings.code()));
}
_ => panic!("Wrong state {:?}", client.state()),
};
}
}

View File

@@ -99,24 +99,23 @@ impl WebTransportSession {
///
/// This function is only called with `RecvStream` and `SendStream` that also implement
/// the http specific functions and `http_stream()` will never return `None`.
#[must_use]
pub fn new_with_http_streams(
session_id: StreamId,
events: Box<dyn ExtendedConnectEvents>,
role: Role,
mut control_stream_recv: Box<dyn RecvStream>,
mut control_stream_send: Box<dyn SendStream>,
) -> Self {
) -> Res<Self> {
let stream_event_listener = Rc::new(RefCell::new(WebTransportSessionListener::default()));
control_stream_recv
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.set_new_listener(Box::new(Rc::clone(&stream_event_listener)));
control_stream_send
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.set_new_listener(Box::new(Rc::clone(&stream_event_listener)));
Self {
Ok(Self {
control_stream_recv,
control_stream_send,
stream_event_listener,
@@ -127,7 +126,7 @@ impl WebTransportSession {
send_streams: BTreeSet::new(),
recv_streams: BTreeSet::new(),
role,
}
})
}
/// # Errors
@@ -141,7 +140,7 @@ impl WebTransportSession {
pub fn send_request(&mut self, headers: &[Header], conn: &mut Connection) -> Res<()> {
self.control_stream_send
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.send_headers(headers, conn)
}
@@ -149,7 +148,7 @@ impl WebTransportSession {
qtrace!("[{self}] receive control data");
let (out, _) = self.control_stream_recv.receive(conn)?;
debug_assert!(out == ReceiveOutput::NoOutput);
self.maybe_check_headers();
self.maybe_check_headers()?;
self.read_control_stream(conn)?;
Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done))
}
@@ -158,33 +157,32 @@ impl WebTransportSession {
let (out, _) = self
.control_stream_recv
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.header_unblocked(conn)?;
debug_assert!(out == ReceiveOutput::NoOutput);
self.maybe_check_headers();
self.maybe_check_headers()?;
self.read_control_stream(conn)?;
Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done))
}
fn maybe_update_priority(&mut self, priority: Priority) -> bool {
fn maybe_update_priority(&mut self, priority: Priority) -> Res<bool> {
self.control_stream_recv
.http_stream()
.unwrap()
.ok_or(Error::Internal)?
.maybe_update_priority(priority)
}
fn priority_update_frame(&mut self) -> Option<HFrame> {
self.control_stream_recv
.http_stream()
.unwrap()
.http_stream()?
.priority_update_frame()
}
fn priority_update_sent(&mut self) {
fn priority_update_sent(&mut self) -> Res<()> {
self.control_stream_recv
.http_stream()
.unwrap()
.priority_update_sent();
.ok_or(Error::Internal)?
.priority_update_sent()
}
fn send(&mut self, conn: &mut Connection) -> Res<()> {
@@ -222,9 +220,9 @@ impl WebTransportSession {
/// # Panics
///
/// This cannot panic because headers are checked before this function called.
pub fn maybe_check_headers(&mut self) {
pub fn maybe_check_headers(&mut self) -> Res<()> {
if SessionState::Negotiating != self.state {
return;
return Ok(());
}
if let Some((headers, interim, fin)) = self.stream_event_listener.borrow_mut().get_headers()
@@ -254,7 +252,7 @@ impl WebTransportSession {
None
}
})
.unwrap();
.ok_or(Error::Internal)?;
self.state = if (200..300).contains(&status) {
if fin {
@@ -288,9 +286,10 @@ impl WebTransportSession {
};
}
}
Ok(())
}
pub fn add_stream(&mut self, stream_id: StreamId) {
pub fn add_stream(&mut self, stream_id: StreamId) -> Res<()> {
if self.state == SessionState::Active {
if stream_id.is_bidi() {
self.send_streams.insert(stream_id);
@@ -306,9 +305,10 @@ impl WebTransportSession {
.extended_connect_new_stream(Http3StreamInfo::new(
stream_id,
ExtendedConnectType::WebTransport.get_stream_type(self.session_id),
));
))?;
}
}
Ok(())
}
pub fn remove_recv_stream(&mut self, stream_id: StreamId) {
@@ -456,7 +456,7 @@ impl HttpRecvStream for Rc<RefCell<WebTransportSession>> {
self.borrow_mut().header_unblocked(conn)
}
fn maybe_update_priority(&mut self, priority: Priority) -> bool {
fn maybe_update_priority(&mut self, priority: Priority) -> Res<bool> {
self.borrow_mut().maybe_update_priority(priority)
}
@@ -464,8 +464,8 @@ impl HttpRecvStream for Rc<RefCell<WebTransportSession>> {
self.borrow_mut().priority_update_frame()
}
fn priority_update_sent(&mut self) {
self.borrow_mut().priority_update_sent();
fn priority_update_sent(&mut self) -> Res<()> {
self.borrow_mut().priority_update_sent()
}
}

View File

@@ -87,7 +87,7 @@ impl HFrame {
Self::PriorityUpdateRequest { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST,
Self::PriorityUpdatePush { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH,
Self::Grease => {
let r = Decoder::from(&random::<8>()).decode_uint::<u64>().unwrap();
let r = u64::from_ne_bytes(random::<8>());
// Zero out the top 7 bits: 2 for being a varint; 5 to account for the *0x1f.
HFrameType((r >> 7) * 0x1f + 0x21)
}

View File

@@ -8,7 +8,11 @@ pub mod hframe;
pub mod reader;
pub mod wtframe;
#[allow(unused_imports)]
#[allow(
clippy::allow_attributes,
unused_imports,
reason = "These are exported."
)]
pub use hframe::{HFrame, H3_FRAME_TYPE_HEADERS, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES};
pub use reader::{FrameReader, StreamReaderConnectionWrapper, StreamReaderRecvStreamWrapper};
pub use wtframe::WebTransportFrame;

View File

@@ -4,8 +4,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use std::fmt::Debug;
use neqo_common::{
@@ -93,7 +95,6 @@ enum FrameReaderState {
UnknownFrameDischargeData { decoder: IncrementalDecoderIgnore },
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct FrameReader {
state: FrameReaderState,

View File

@@ -21,6 +21,10 @@ pub fn enc_dec<T: FrameDecoder<T>>(d: &Encoder, st: &str, remaining: usize) -> T
let mut conn_c = default_client();
let mut conn_s = default_server();
let out = conn_c.process_output(now());
let out2 = conn_c.process_output(now());
_ = conn_s.process(out.dgram(), now());
let out = conn_s.process(out2.dgram(), now());
let out = conn_c.process(out.dgram(), now());
let out = conn_s.process(out.dgram(), now());
let out = conn_c.process(out.dgram(), now());
drop(conn_s.process(out.dgram(), now()));

View File

@@ -38,7 +38,7 @@ impl FrameReaderTest {
}
fn process<T: FrameDecoder<T>>(&mut self, v: &[u8]) -> Option<T> {
self.conn_s.stream_send(self.stream_id, v).unwrap();
self.conn_s.stream_send(self.stream_id, v).ok()?;
let out = self.conn_s.process_output(now());
drop(self.conn_c.process(out.dgram(), now()));
let (frame, fin) = self
@@ -47,7 +47,7 @@ impl FrameReaderTest {
&mut self.conn_c,
self.stream_id,
))
.unwrap();
.ok()?;
assert!(!fin);
frame
}
@@ -264,7 +264,7 @@ fn test_reading_frame<T: FrameDecoder<T> + PartialEq + Debug>(
assert!(fin);
assert!(f.is_none());
}
};
}
}
#[test]

View File

@@ -50,7 +50,6 @@ impl TryFrom<(MessageType, &str)> for PseudoHeaderState {
/// a status header or if the value of the header is 101 or cannot be parsed.
pub fn is_interim(headers: &[Header]) -> Res<bool> {
if let Some(h) = headers.iter().take(1).find_header(":status") {
#[allow(clippy::map_err_ignore)]
let status_code = h.value().parse::<u16>().map_err(|_| Error::InvalidHeader)?;
if status_code == 101 {
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-http#section-4.3

View File

@@ -214,7 +214,6 @@ pub enum Error {
// Internal errors from here.
AlreadyClosed,
AlreadyInitialized,
DecodingFrame,
FatalError,
HttpGoaway,
Internal,
@@ -333,7 +332,7 @@ impl Error {
_ => {
debug_assert!(false, "Unexpected error");
}
};
}
Self::TransportStreamDoesNotExist
}
@@ -495,9 +494,9 @@ trait HttpRecvStream: RecvStream {
/// An error may happen while reading a stream, e.g. early close, protocol error, etc.
fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)>;
fn maybe_update_priority(&mut self, priority: Priority) -> bool;
fn maybe_update_priority(&mut self, priority: Priority) -> Res<bool>;
fn priority_update_frame(&mut self) -> Option<HFrame>;
fn priority_update_sent(&mut self);
fn priority_update_sent(&mut self) -> Res<()>;
fn set_new_listener(&mut self, _conn_events: Box<dyn HttpRecvStreamEvents>) {}
fn extended_connect_wait_for_response(&self) -> bool {

View File

@@ -66,7 +66,7 @@ impl Priority {
Some(ListEntry::Item(Item {
bare_item: BareItem::Integer(u),
..
})) if (0..=7).contains(u) => u8::try_from(*u).unwrap(),
})) if (0..=7).contains(u) => u8::try_from(*u).map_err(|_| Error::Internal)?,
_ => 3,
};
let incremental = match dict.get("i") {
@@ -107,7 +107,6 @@ impl fmt::Display for Priority {
}
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct PriorityHandler {
push_stream: bool,
priority: Priority,

View File

@@ -77,7 +77,7 @@ impl ActivePushStreams {
return None;
}
let inx = usize::try_from(u64::from(push_id - self.first_push_id)).unwrap();
let inx = usize::try_from(u64::from(push_id - self.first_push_id)).ok()?;
if inx >= self.push_streams.len() {
self.push_streams.resize(inx + 1, PushState::Init);
}
@@ -116,7 +116,7 @@ impl ActivePushStreams {
.filter(|&e| e == &PushState::Closed)
.count(),
)
.unwrap()
.expect("usize fits in u64")
}
pub fn clear(&mut self) {

View File

@@ -18,7 +18,7 @@ pub fn h3_data_moved_up(qlog: &NeqoQlog, stream_id: StreamId, amount: usize) {
let ev_data = EventData::DataMoved(qlog::events::quic::DataMoved {
stream_id: Some(stream_id.as_u64()),
offset: None,
length: Some(u64::try_from(amount).unwrap()),
length: Some(u64::try_from(amount).expect("usize fits in u64")),
from: Some(DataRecipient::Transport),
to: Some(DataRecipient::Application),
raw: None,
@@ -36,7 +36,7 @@ pub fn h3_data_moved_down(qlog: &NeqoQlog, stream_id: StreamId, amount: usize) {
let ev_data = EventData::DataMoved(qlog::events::quic::DataMoved {
stream_id: Some(stream_id.as_u64()),
offset: None,
length: Some(u64::try_from(amount).unwrap()),
length: Some(u64::try_from(amount).expect("usize fits in u64")),
from: Some(DataRecipient::Application),
to: Some(DataRecipient::Transport),
raw: None,

View File

@@ -19,7 +19,6 @@ use crate::{
MessageType, Priority, PushId, ReceiveOutput, RecvStream, Res, Stream,
};
#[allow(clippy::module_name_repetitions)]
pub struct RecvMessageInfo {
pub message_type: MessageType,
pub stream_type: Http3StreamType,
@@ -292,7 +291,7 @@ impl RecvMessage {
break self.set_state_to_close_pending(post_readable_event);
}
}
};
}
}
RecvMessageState::DecodingHeaders { header_block, fin } => {
if self
@@ -338,7 +337,7 @@ impl RecvMessage {
// WebTransportSession
break Ok(());
}
};
}
}
}
@@ -460,16 +459,17 @@ impl HttpRecvStream for RecvMessage {
self.receive(conn)
}
fn maybe_update_priority(&mut self, priority: Priority) -> bool {
self.priority_handler.maybe_update_priority(priority)
fn maybe_update_priority(&mut self, priority: Priority) -> Res<bool> {
Ok(self.priority_handler.maybe_update_priority(priority))
}
fn priority_update_frame(&mut self) -> Option<HFrame> {
self.priority_handler.maybe_encode_frame(self.stream_id)
}
fn priority_update_sent(&mut self) {
fn priority_update_sent(&mut self) -> Res<()> {
self.priority_handler.priority_update_sent();
Ok(())
}
fn set_new_listener(&mut self, conn_events: Box<dyn HttpRecvStreamEvents>) {

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::fmt::{Debug, Formatter};
use url::{ParseError, Url};

View File

@@ -151,7 +151,7 @@ impl SendMessage {
}
fn stream_id(&self) -> StreamId {
Option::<StreamId>::from(&self.stream).unwrap()
Option::<StreamId>::from(&self.stream).expect("stream has ID")
}
fn get_stream_info(&self) -> Http3StreamInfo {
@@ -183,7 +183,7 @@ impl SendStream for SendMessage {
// cheap, thus not worth optimizing.
conn.stream_set_writable_event_low_watermark(
self.stream_id(),
NonZeroUsize::new(MIN_DATA_FRAME_SIZE).unwrap(),
NonZeroUsize::new(MIN_DATA_FRAME_SIZE).ok_or(Error::Internal)?,
)?;
return Ok(0);
}

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::{
cell::{RefCell, RefMut},
collections::HashMap,
@@ -118,7 +116,11 @@ impl Http3Server {
self.process(None::<Datagram>, now)
}
pub fn process(&mut self, dgram: Option<Datagram<impl AsRef<[u8]>>>, now: Instant) -> Output {
pub fn process(
&mut self,
dgram: Option<Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) -> Output {
qtrace!("[{self}] Process");
let out = self.server.process(dgram, now);
self.process_http3(now);
@@ -135,9 +137,10 @@ impl Http3Server {
/// Process HTTP3 layer.
fn process_http3(&mut self, now: Instant) {
qtrace!("[{self}] Process http3 internal");
// `ActiveConnectionRef` `Hash` implementation doesnt access any of the interior mutable
// types.
#[allow(clippy::mutable_key_type)]
#[expect(
clippy::mutable_key_type,
reason = "ActiveConnectionRef::Hash doesn't access any of the interior mutable types."
)]
let mut active_conns = self.server.active_connections();
active_conns.extend(
self.http3_handlers
@@ -152,7 +155,10 @@ impl Http3Server {
}
}
#[allow(clippy::too_many_lines)]
#[expect(
clippy::too_many_lines,
reason = "Function is mostly a match statement."
)]
fn process_events(&mut self, conn: &ConnectionRef, now: Instant) {
let mut remove = false;
let http3_parameters = &self.http3_parameters;
@@ -410,8 +416,12 @@ mod tests {
fn connect_transport(server: &mut Http3Server, client: &mut Connection, resume: bool) {
let c1 = client.process_output(now());
let s1 = server.process(c1.dgram(), now());
let c11 = client.process_output(now());
_ = server.process(c1.dgram(), now());
let s1 = server.process(c11.dgram(), now());
let c2 = client.process(s1.dgram(), now());
let s2 = server.process(c2.dgram(), now());
let c2 = client.process(s2.dgram(), now());
let needs_auth = client
.events()
.any(|e| e == ConnectionEvent::AuthenticationNeeded);
@@ -430,8 +440,7 @@ mod tests {
assert!(client.state().connected());
let s2 = server.process(c2.dgram(), now());
assert_connected(server);
let c3 = client.process(s2.dgram(), now());
assert!(c3.dgram().is_none());
_ = client.process(s2.dgram(), now());
}
// Start a client/server and check setting frame.

View File

@@ -12,7 +12,8 @@ use neqo_transport::{AppError, StreamId};
use crate::{
connection::Http3State,
features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason},
CloseType, Http3StreamInfo, HttpRecvStreamEvents, Priority, RecvStreamEvents, SendStreamEvents,
CloseType, Http3StreamInfo, HttpRecvStreamEvents, Priority, RecvStreamEvents, Res,
SendStreamEvents,
};
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -69,10 +70,9 @@ pub struct Http3ServerConnEvents {
impl SendStreamEvents for Http3ServerConnEvents {
fn send_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) {
if close_type != CloseType::Done {
self.insert(Http3ServerConnEvent::StreamStopSending {
stream_info,
error: close_type.error().unwrap(),
});
if let Some(error) = close_type.error() {
self.insert(Http3ServerConnEvent::StreamStopSending { stream_info, error });
}
}
}
@@ -90,10 +90,9 @@ impl RecvStreamEvents for Http3ServerConnEvents {
fn recv_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) {
if close_type != CloseType::Done {
self.remove_events_for_stream_id(stream_info);
self.insert(Http3ServerConnEvent::StreamReset {
stream_info,
error: close_type.error().unwrap(),
});
if let Some(error) = close_type.error() {
self.insert(Http3ServerConnEvent::StreamReset { stream_info, error });
}
}
}
}
@@ -144,8 +143,9 @@ impl ExtendedConnectEvents for Http3ServerConnEvents {
});
}
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) {
fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) -> Res<()> {
self.insert(Http3ServerConnEvent::ExtendedConnectNewStream(stream_info));
Ok(())
}
fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>) {

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::{
cell::RefCell,
collections::VecDeque,
@@ -22,7 +20,7 @@ use crate::{
connection::{Http3State, WebTransportSessionAcceptAction},
connection_server::Http3ServerHandler,
features::extended_connect::SessionCloseReason,
Http3StreamInfo, Http3StreamType, Priority, Res,
Error, Http3StreamInfo, Http3StreamType, Priority, Res,
};
#[derive(Debug, Clone)]
@@ -53,8 +51,6 @@ impl PartialEq for StreamHandler {
}
}
impl Eq for StreamHandler {}
impl StreamHandler {
pub const fn stream_id(&self) -> StreamId {
self.stream_info.stream_id()
@@ -215,7 +211,6 @@ impl Http3OrWebTransportStream {
impl Deref for Http3OrWebTransportStream {
type Target = StreamHandler;
#[must_use]
fn deref(&self) -> &Self::Target {
&self.stream_handler
}
@@ -378,13 +373,12 @@ impl WebTransportRequest {
- u64::try_from(Encoder::varint_len(
self.stream_handler.stream_id().as_u64(),
))
.unwrap())
.map_err(|_| Error::Internal)?)
}
}
impl Deref for WebTransportRequest {
type Target = StreamHandler;
#[must_use]
fn deref(&self) -> &Self::Target {
&self.stream_handler
}
@@ -409,8 +403,6 @@ impl PartialEq for WebTransportRequest {
}
}
impl Eq for WebTransportRequest {}
#[derive(Debug, Clone)]
pub enum WebTransportServerEvent {
NewSession {

View File

@@ -4,8 +4,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::module_name_repetitions)]
use std::ops::Deref;
use neqo_common::{Decoder, Encoder};
@@ -180,7 +178,7 @@ impl HSettings {
// other supported settings here
(Some(_), Some(_)) => {} // ignore unknown setting, it is fine.
_ => return Err(Error::NotEnoughData),
};
}
}
Ok(())
}

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![cfg(test)]
use std::time::{Duration, Instant};
use neqo_common::{event::Provider as _, qtrace, Datagram};
@@ -92,8 +94,12 @@ fn process_client_events(conn: &mut Http3Client) {
fn connect_peers(hconn_c: &mut Http3Client, hconn_s: &mut Http3Server) -> Option<Datagram> {
assert_eq!(hconn_c.state(), Http3State::Initializing);
let out = hconn_c.process_output(now()); // Initial
let out = hconn_s.process(out.dgram(), now()); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now()); // ACK
let out2 = hconn_c.process_output(now()); // Initial
_ = hconn_s.process(out.dgram(), now()); // ACK
let out = hconn_s.process(out2.dgram(), now()); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now());
let out = hconn_s.process(out.dgram(), now());
let out = hconn_c.process(out.dgram(), now());
drop(hconn_s.process(out.dgram(), now())); // consume ACK
let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
assert!(hconn_c.events().any(authentication_needed));
@@ -119,8 +125,14 @@ fn connect_peers_with_network_propagation_delay(
assert_eq!(hconn_c.state(), Http3State::Initializing);
let mut now = now();
let out = hconn_c.process_output(now); // Initial
let out2 = hconn_c.process_output(now); // Initial
now += net_delay;
let out = hconn_s.process(out.dgram(), now); // Initial + Handshake
_ = hconn_s.process(out.dgram(), now); // ACK
let out = hconn_s.process(out2.dgram(), now);
now += net_delay;
let out = hconn_c.process(out.dgram(), now);
now += net_delay;
let out = hconn_s.process(out.dgram(), now);
now += net_delay;
let out = hconn_c.process(out.dgram(), now); // ACK
now += net_delay;
@@ -143,7 +155,8 @@ fn connect_peers_with_network_propagation_delay(
(out.dgram(), now)
}
fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
#[must_use]
pub fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
let mut hconn_c = default_http3_client();
let mut hconn_s = default_http3_server();
@@ -242,7 +255,7 @@ fn response_103() {
/// Test [`neqo_http3::SendMessage::send_data`] to set
/// [`neqo_transport::SendStream::set_writable_event_low_watermark`].
#[allow(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_truncation, reason = "OK in a test.")]
#[test]
fn data_writable_events_low_watermark() -> Result<(), Box<dyn std::error::Error>> {
const STREAM_LIMIT: u64 = 5000;
@@ -446,6 +459,11 @@ fn zerortt() {
hconn_c.stream_close_send(req).unwrap();
let out = hconn_c.process(dgram, now());
let out2 = hconn_c.process_output(now());
_ = hconn_s.process(out.dgram(), now());
let out = hconn_s.process(out2.dgram(), now());
let out = hconn_c.process(out.dgram(), now());
let out = hconn_s.process(out.dgram(), now());
let mut request_stream = None;

View File

@@ -29,8 +29,12 @@ fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
fn connect_with(client: &mut Http3Client, server: &mut Http3Server) {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);
_ = server.process(out.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![cfg(test)]
use std::sync::OnceLock;
use neqo_common::event::Provider as _;

View File

@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![cfg(test)]
use std::{cell::RefCell, rc::Rc};
use neqo_common::{event::Provider as _, header::HeadersExt as _};
@@ -42,8 +44,12 @@ fn connect() -> (Http3Client, Http3Server) {
.expect("create a server");
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);
_ = server.process(out.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"2ccc60ca10aabbb26b08bff596ac851e1e5e58740a53d07bea8a3dfb9835e4db","src/decoder.rs":"4d83a4f375cd6fdd01af5e9f3ad03deb7a6362c4ad1cf56d1c693e76946ae84d","src/decoder_instructions.rs":"0e6c2036372061b16f71b172e3b6f7796683c0992f5f6a39e6c4b37d9fff349a","src/encoder.rs":"77f4229ce9cb0b466ea0cd5108bb0128824f34962d87e94f76b757ce821a281f","src/encoder_instructions.rs":"033b7aeda62f8df2cf9b09a93d449cbae74376e9f49791683543874d46f64e08","src/header_block.rs":"4285d03556b6eba3a00715c9450b00af676a3ee5f3aa07a9f4e5e33944695ee3","src/huffman.rs":"6976f1b4d3e5ef849a6b080cfb2e8804bf01cfe3b9bd9e3994a319d5405cd8f3","src/huffman_decode_helper.rs":"9ce470e318b3664f58aa109bed483ab15bfd9e0b17d261ea2b609668a42a9d80","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"f9bad0fe7643c618d034c4941ebd30ad5f6015b8b87b484b0ea79681d13d8b49","src/prefix.rs":"1655af52fcc58c27984d78af6639d66a26c2307995e7ff22eb309fd7e4d50a81","src/qlog.rs":"d8e8482624f0cc4388546169772846d37a0422ce10287ee2f716dfc5f7a56c3d","src/qpack_send_buf.rs":"c0c2bc22ffd72b0dc2fb2f6d5590fc6a2fa4a19cc32b1407e9373140cbf44414","src/reader.rs":"ae15506441a52ad522f15048796c37ad791680e3ca020a50f7c84f54ab95435e","src/static_table.rs":"6e5ec26e2b6bd63375d2d77e72748151d430d1629a8e497ec0d0ea21c078524a","src/stats.rs":"cb01723249f60e15a5cd7efd9cbab409fddc588d1df655ed06ba8c80e3d5d28e","src/table.rs":"f7aac571794f192ef4bed1dc4c1ddd96dac5553b0341a5a99ff2fadcc46d8f7d"},"package":null}
{"files":{"Cargo.toml":"62c9acbd08bb43060ac7243b0a6b88e1cf2c679a6fd36ed7aeb32a6d90b3b139","src/decoder.rs":"06dc4b0362ddf0a502ab7f3a8622aa213aaa6ca599990471f46c918778df69e6","src/decoder_instructions.rs":"0e6c2036372061b16f71b172e3b6f7796683c0992f5f6a39e6c4b37d9fff349a","src/encoder.rs":"22ac6cf0e6cbd9be5c7d09d4b73ac396bf7a9ac0e8a8f5137dcdafb0e004f85f","src/encoder_instructions.rs":"210442f053b317c14b8630c57e9c2f75f66e9ce3de79f5ff6cd5c6fcf1fea17a","src/header_block.rs":"4285d03556b6eba3a00715c9450b00af676a3ee5f3aa07a9f4e5e33944695ee3","src/huffman.rs":"4ffaf7766b7a9e1c01f094d48c447a75516ee7be91f2302327f7041d328a8c3f","src/huffman_decode_helper.rs":"c799b85c7738cdf6a1f6ea039062d2ea5ce0b4f08789d64e90a8712d57040d2b","src/huffman_table.rs":"5a5819dd2708f88240a2f825d86aa8b5f45c28d1427c0b8463831653e8eb3527","src/lib.rs":"1cde51b6c8c3c5a187f2393ad636e7e3919e10902c53bc9cc568bfe6c74a50c4","src/prefix.rs":"31bfb11d334a6df619bcc2720621e44a656be2514fad9033531a712d47dbe672","src/qlog.rs":"d8e8482624f0cc4388546169772846d37a0422ce10287ee2f716dfc5f7a56c3d","src/qpack_send_buf.rs":"cf10375c0bc5fb27d6faaf82df8607743bd62c5772d2cfb3779d801f4650e4f2","src/reader.rs":"5f0c2ab16bc804a892a7bd2310dfd6e614fd8ab2b4f552914fca6ad32b4fb006","src/static_table.rs":"6e5ec26e2b6bd63375d2d77e72748151d430d1629a8e497ec0d0ea21c078524a","src/stats.rs":"cb01723249f60e15a5cd7efd9cbab409fddc588d1df655ed06ba8c80e3d5d28e","src/table.rs":"f7c773dd3e086ead69ffffe9850e5c9b9b007c9fdd188022cdde7d08def248eb"},"package":null}

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.76.0"
rust-version = "1.82.0"
name = "neqo-qpack"
version = "0.12.2"
version = "0.13.1"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = false
autolib = false
@@ -64,7 +64,7 @@ path = "./../neqo-common"
path = "./../neqo-transport"
[dependencies.qlog]
version = "0.13"
version = "0.15.1"
default-features = false
[dependencies.static_assertions]
@@ -75,22 +75,56 @@ default-features = false
path = "../test-fixture"
[lints.clippy]
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
cfg_not_test = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
infinite_loop = "warn"
large_include_file = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
literal_string_with_formatting_args = "allow"
lossy_float_literal = "warn"
mem_forget = "warn"
mixed_read_write_in_expression = "warn"
module_name_repetitions = "warn"
multiple_crate_versions = "allow"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
pathbuf_init_then_push = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
self_named_module_files = "warn"
semicolon_inside_block = "warn"
string_lit_chars_any = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
try_err = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[lints.clippy.cargo]
level = "warn"
@@ -107,7 +141,6 @@ priority = -1
[lints.rust]
absolute_paths_not_starting_with_crate = "warn"
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
@@ -119,3 +152,4 @@ unit_bindings = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use neqo_common::{qdebug, Header};
use neqo_transport::{Connection, StreamId};
@@ -67,12 +72,9 @@ impl QPackDecoder {
self.max_table_size
}
/// # Panics
///
/// If the number of blocked streams is too large.
#[must_use]
pub fn get_blocked_streams(&self) -> u16 {
u16::try_from(self.max_blocked_streams).unwrap()
pub const fn get_blocked_streams(&self) -> usize {
self.max_blocked_streams
}
/// returns a list of unblocked streams
@@ -174,7 +176,6 @@ impl QPackDecoder {
/// # Panics
///
/// Never, but rust doesn't know that.
#[allow(clippy::map_err_ignore)]
pub fn send(&mut self, conn: &mut Connection) -> Res<()> {
// Encode increment instruction if needed.
let increment = self.table.base() - self.acked_inserts;
@@ -184,7 +185,10 @@ impl QPackDecoder {
}
if !self.send_buf.is_empty() && self.local_stream_id.is_some() {
let r = conn
.stream_send(self.local_stream_id.unwrap(), &self.send_buf[..])
.stream_send(
self.local_stream_id.ok_or(Error::Internal)?,
&self.send_buf[..],
)
.map_err(|_| Error::DecoderStream)?;
qdebug!("[{self}] {r} bytes sent");
self.send_buf.read(r);

View File

@@ -4,6 +4,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
clippy::module_name_repetitions,
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
)]
use std::collections::{HashMap, HashSet, VecDeque};
use neqo_common::{qdebug, qerror, qlog::NeqoQlog, qtrace, Header};
@@ -149,7 +154,6 @@ impl QPackEncoder {
}
}
#[allow(clippy::map_err_ignore)]
fn insert_count_instruction(&mut self, increment: u64) -> Res<()> {
self.table
.increment_acked(increment)
@@ -540,7 +544,7 @@ mod tests {
impl TestEncoder {
pub fn change_capacity(&mut self, capacity: u64) -> Res<()> {
self.encoder.set_max_capacity(capacity).unwrap();
self.encoder.set_max_capacity(capacity)?;
// We will try to really change the table only when we send the change capacity
// instruction.
self.encoder.send_encoder_updates(&mut self.conn)

View File

@@ -44,7 +44,7 @@ pub enum EncoderInstruction<'a> {
index: u64,
},
#[cfg(test)]
#[allow(dead_code)]
#[expect(dead_code, reason = "Only used in tests.")]
NoInstruction,
}

Some files were not shown because too many files have changed in this diff Show More