diff --git a/neqo-common/Cargo.toml b/neqo-common/Cargo.toml index 2fe80edb43..f27d97b42a 100644 --- a/neqo-common/Cargo.toml +++ b/neqo-common/Cargo.toml @@ -13,7 +13,6 @@ license.workspace = true # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 enum-map = "2.7" env_logger = { version = "0.10", default-features = false } -lazy_static = "1.4" log = { version = "0.4", default-features = false } qlog = "0.12" time = { version = "0.3", features = ["formatting"] } diff --git a/neqo-common/src/log.rs b/neqo-common/src/log.rs index d9c30b98b1..1b181de56b 100644 --- a/neqo-common/src/log.rs +++ b/neqo-common/src/log.rs @@ -6,10 +6,13 @@ #![allow(clippy::module_name_repetitions)] -use std::{io::Write, sync::Once, time::Instant}; +use std::{ + io::Write, + sync::{Once, OnceLock}, + time::{Duration, Instant}, +}; use env_logger::Builder; -use lazy_static::lazy_static; #[macro_export] macro_rules! do_log { @@ -42,17 +45,17 @@ macro_rules! log_subject { }}; } -static INIT_ONCE: Once = Once::new(); - -lazy_static! { - static ref START_TIME: Instant = Instant::now(); +fn since_start() -> Duration { + static START_TIME: OnceLock = OnceLock::new(); + START_TIME.get_or_init(Instant::now).elapsed() } pub fn init() { + static INIT_ONCE: Once = Once::new(); INIT_ONCE.call_once(|| { let mut builder = Builder::from_env("RUST_LOG"); builder.format(|buf, record| { - let elapsed = START_TIME.elapsed(); + let elapsed = since_start(); writeln!( buf, "{}s{:3}ms {} {}", diff --git a/neqo-common/src/timer.rs b/neqo-common/src/timer.rs index e8532af442..6708c70963 100644 --- a/neqo-common/src/timer.rs +++ b/neqo-common/src/timer.rs @@ -247,49 +247,50 @@ impl Timer { #[cfg(test)] mod test { - use lazy_static::lazy_static; + use std::sync::OnceLock; use super::{Duration, Instant, Timer}; - lazy_static! { - static ref NOW: Instant = Instant::now(); + fn now() -> Instant { + static NOW: OnceLock = OnceLock::new(); + *NOW.get_or_init(Instant::now) } const GRANULARITY: Duration = Duration::from_millis(10); const CAPACITY: usize = 10; #[test] fn create() { - let t: Timer<()> = Timer::new(*NOW, GRANULARITY, CAPACITY); + let t: Timer<()> = Timer::new(now(), GRANULARITY, CAPACITY); assert_eq!(t.span(), Duration::from_millis(100)); assert_eq!(None, t.next_time()); } #[test] fn immediate_entry() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - t.add(*NOW, 12); - assert_eq!(*NOW, t.next_time().expect("should have an entry")); - let values: Vec<_> = t.take_until(*NOW).collect(); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + t.add(now(), 12); + assert_eq!(now(), t.next_time().expect("should have an entry")); + let values: Vec<_> = t.take_until(now()).collect(); assert_eq!(vec![12], values); } #[test] fn same_time() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); let v1 = 12; let v2 = 13; - t.add(*NOW, v1); - t.add(*NOW, v2); - assert_eq!(*NOW, t.next_time().expect("should have an entry")); - let values: Vec<_> = t.take_until(*NOW).collect(); + t.add(now(), v1); + t.add(now(), v2); + assert_eq!(now(), t.next_time().expect("should have an entry")); + let values: Vec<_> = t.take_until(now()).collect(); assert!(values.contains(&v1)); assert!(values.contains(&v2)); } #[test] fn add() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - let near_future = *NOW + Duration::from_millis(17); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + let near_future = now() + Duration::from_millis(17); let v = 9; t.add(near_future, v); assert_eq!(near_future, t.next_time().expect("should return a value")); @@ -305,8 +306,8 @@ mod test { #[test] fn add_future() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - let future = *NOW + Duration::from_millis(117); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + let future = now() + Duration::from_millis(117); let v = 9; t.add(future, v); assert_eq!(future, t.next_time().expect("should return a value")); @@ -315,8 +316,8 @@ mod test { #[test] fn add_far_future() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - let far_future = *NOW + Duration::from_millis(892); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + let far_future = now() + Duration::from_millis(892); let v = 9; t.add(far_future, v); assert_eq!(far_future, t.next_time().expect("should return a value")); @@ -333,12 +334,12 @@ mod test { ]; fn with_times() -> Timer { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); for (i, time) in TIMES.iter().enumerate() { - t.add(*NOW + *time, i); + t.add(now() + *time, i); } assert_eq!( - *NOW + *TIMES.iter().min().unwrap(), + now() + *TIMES.iter().min().unwrap(), t.next_time().expect("should have a time") ); t @@ -348,7 +349,7 @@ mod test { #[allow(clippy::needless_collect)] // false positive fn multiple_values() { let mut t = with_times(); - let values: Vec<_> = t.take_until(*NOW + *TIMES.iter().max().unwrap()).collect(); + let values: Vec<_> = t.take_until(now() + *TIMES.iter().max().unwrap()).collect(); for i in 0..TIMES.len() { assert!(values.contains(&i)); } @@ -358,7 +359,7 @@ mod test { #[allow(clippy::needless_collect)] // false positive fn take_far_future() { let mut t = with_times(); - let values: Vec<_> = t.take_until(*NOW + Duration::from_secs(100)).collect(); + let values: Vec<_> = t.take_until(now() + Duration::from_secs(100)).collect(); for i in 0..TIMES.len() { assert!(values.contains(&i)); } @@ -368,15 +369,15 @@ mod test { fn remove_each() { let mut t = with_times(); for (i, time) in TIMES.iter().enumerate() { - assert_eq!(Some(i), t.remove(*NOW + *time, |&x| x == i)); + assert_eq!(Some(i), t.remove(now() + *time, |&x| x == i)); } assert_eq!(None, t.next_time()); } #[test] fn remove_future() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - let future = *NOW + Duration::from_millis(117); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + let future = now() + Duration::from_millis(117); let v = 9; t.add(future, v); @@ -385,9 +386,9 @@ mod test { #[test] fn remove_too_far_future() { - let mut t = Timer::new(*NOW, GRANULARITY, CAPACITY); - let future = *NOW + Duration::from_millis(117); - let too_far_future = *NOW + t.span() + Duration::from_millis(117); + let mut t = Timer::new(now(), GRANULARITY, CAPACITY); + let future = now() + Duration::from_millis(117); + let too_far_future = now() + t.span() + Duration::from_millis(117); let v = 9; t.add(future, v); diff --git a/neqo-crypto/src/hkdf.rs b/neqo-crypto/src/hkdf.rs index 058a63b2bd..5d918ec13a 100644 --- a/neqo-crypto/src/hkdf.rs +++ b/neqo-crypto/src/hkdf.rs @@ -17,9 +17,10 @@ use crate::{ }, err::{Error, Res}, p11::{ - random, Item, PK11Origin, PK11SymKey, PK11_ImportDataKey, Slot, SymKey, CKA_DERIVE, + Item, PK11Origin, PK11SymKey, PK11_ImportDataKey, Slot, SymKey, CKA_DERIVE, CKM_HKDF_DERIVE, CK_ATTRIBUTE_TYPE, CK_MECHANISM_TYPE, }, + random, }; experimental_api!(SSL_HkdfExtract( @@ -40,24 +41,32 @@ experimental_api!(SSL_HkdfExpandLabel( secret: *mut *mut PK11SymKey, )); -fn key_size(version: Version, cipher: Cipher) -> Res { +const MAX_KEY_SIZE: usize = 48; +const fn key_size(version: Version, cipher: Cipher) -> Res { if version != TLS_VERSION_1_3 { return Err(Error::UnsupportedVersion); } - Ok(match cipher { + let size = match cipher { TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => 32, TLS_AES_256_GCM_SHA384 => 48, _ => return Err(Error::UnsupportedCipher), - }) + }; + debug_assert!(size <= MAX_KEY_SIZE); + Ok(size) } /// Generate a random key of the right size for the given suite. /// /// # Errors /// -/// Only if NSS fails. +/// If the ciphersuite or protocol version is not supported. pub fn generate_key(version: Version, cipher: Cipher) -> Res { - import_key(version, &random(key_size(version, cipher)?)) + // With generic_const_expr, this becomes: + // import_key(version, &random::<{ key_size(version, cipher) }>()) + import_key( + version, + &random::()[0..key_size(version, cipher)?], + ) } /// Import a symmetric key for use with HKDF. diff --git a/neqo-crypto/src/lib.rs b/neqo-crypto/src/lib.rs index 05424ee1f3..4a23b5a7b1 100644 --- a/neqo-crypto/src/lib.rs +++ b/neqo-crypto/src/lib.rs @@ -27,7 +27,6 @@ mod exp; pub mod ext; pub mod hkdf; pub mod hp; -mod once; #[macro_use] mod p11; mod prio; @@ -41,6 +40,7 @@ use std::{ ffi::CString, path::{Path, PathBuf}, ptr::null, + sync::OnceLock, }; #[cfg(not(feature = "fuzzing"))] @@ -49,7 +49,6 @@ pub use self::aead::RealAead as Aead; pub use self::aead::RealAead; #[cfg(feature = "fuzzing")] pub use self::aead_fuzzing::FuzzingAead as Aead; -use self::once::OnceResult; pub use self::{ agent::{ Agent, AllowZeroRtt, Client, HandshakeState, Record, RecordList, ResumptionToken, @@ -64,7 +63,7 @@ pub use self::{ }, err::{Error, PRErrorCode, Res}, ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult}, - p11::{random, PrivateKey, PublicKey, SymKey}, + p11::{random, randomize, PrivateKey, PublicKey, SymKey}, replay::AntiReplay, secrets::SecretDirection, ssl::Opt, @@ -100,7 +99,7 @@ impl Drop for NssLoaded { } } -static mut INITIALIZED: OnceResult = OnceResult::new(); +static INITIALIZED: OnceLock = OnceLock::new(); fn already_initialized() -> bool { unsafe { nss::NSS_IsInitialized() != 0 } @@ -124,19 +123,18 @@ fn version_check() { pub fn init() { // Set time zero. time::init(); - unsafe { - INITIALIZED.call_once(|| { - version_check(); - if already_initialized() { - return NssLoaded::External; - } + _ = INITIALIZED.get_or_init(|| { + version_check(); + if already_initialized() { + return NssLoaded::External; + } - secstatus_to_res(nss::NSS_NoDB_Init(null())).expect("NSS_NoDB_Init failed"); - secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); + secstatus_to_res(unsafe { nss::NSS_NoDB_Init(null()) }).expect("NSS_NoDB_Init failed"); + secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() }) + .expect("NSS_SetDomesticPolicy failed"); - NssLoaded::NoDb - }); - } + NssLoaded::NoDb + }); } /// This enables SSLTRACE by calling a simple, harmless function to trigger its @@ -158,51 +156,47 @@ fn enable_ssl_trace() { /// If NSS cannot be initialized. pub fn init_db>(dir: P) { time::init(); - unsafe { - INITIALIZED.call_once(|| { - version_check(); - if already_initialized() { - return NssLoaded::External; - } + _ = INITIALIZED.get_or_init(|| { + version_check(); + if already_initialized() { + return NssLoaded::External; + } - let path = dir.into(); - assert!(path.is_dir()); - let pathstr = path.to_str().expect("path converts to string").to_string(); - let dircstr = CString::new(pathstr).unwrap(); - let empty = CString::new("").unwrap(); - secstatus_to_res(nss::NSS_Initialize( + let path = dir.into(); + assert!(path.is_dir()); + let pathstr = path.to_str().expect("path converts to string").to_string(); + let dircstr = CString::new(pathstr).unwrap(); + let empty = CString::new("").unwrap(); + secstatus_to_res(unsafe { + nss::NSS_Initialize( dircstr.as_ptr(), empty.as_ptr(), empty.as_ptr(), nss::SECMOD_DB.as_ptr().cast(), nss::NSS_INIT_READONLY, - )) - .expect("NSS_Initialize failed"); - - secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); - secstatus_to_res(ssl::SSL_ConfigServerSessionIDCache( - 1024, - 0, - 0, - dircstr.as_ptr(), - )) - .expect("SSL_ConfigServerSessionIDCache failed"); - - #[cfg(debug_assertions)] - enable_ssl_trace(); - - NssLoaded::Db(path.into_boxed_path()) - }); - } + ) + }) + .expect("NSS_Initialize failed"); + + secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() }) + .expect("NSS_SetDomesticPolicy failed"); + secstatus_to_res(unsafe { + ssl::SSL_ConfigServerSessionIDCache(1024, 0, 0, dircstr.as_ptr()) + }) + .expect("SSL_ConfigServerSessionIDCache failed"); + + #[cfg(debug_assertions)] + enable_ssl_trace(); + + NssLoaded::Db(path.into_boxed_path()) + }); } /// # Panics /// /// If NSS isn't initialized. pub fn assert_initialized() { - unsafe { - INITIALIZED.call_once(|| { - panic!("NSS not initialized with init or init_db"); - }); - } + INITIALIZED + .get() + .expect("NSS not initialized with init or init_db"); } diff --git a/neqo-crypto/src/once.rs b/neqo-crypto/src/once.rs deleted file mode 100644 index 80657cfe26..0000000000 --- a/neqo-crypto/src/once.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::sync::Once; - -#[allow(clippy::module_name_repetitions)] -pub struct OnceResult { - once: Once, - v: Option, -} - -impl OnceResult { - #[must_use] - pub const fn new() -> Self { - Self { - once: Once::new(), - v: None, - } - } - - pub fn call_once T>(&mut self, f: F) -> &T { - let v = &mut self.v; - self.once.call_once(|| { - *v = Some(f()); - }); - self.v.as_ref().unwrap() - } -} - -#[cfg(test)] -mod test { - use super::OnceResult; - - static mut STATIC_ONCE_RESULT: OnceResult = OnceResult::new(); - - #[test] - fn static_update() { - assert_eq!(*unsafe { STATIC_ONCE_RESULT.call_once(|| 23) }, 23); - assert_eq!(*unsafe { STATIC_ONCE_RESULT.call_once(|| 24) }, 23); - } -} diff --git a/neqo-crypto/src/p11.rs b/neqo-crypto/src/p11.rs index 7a89f14c2b..2225d5b211 100644 --- a/neqo-crypto/src/p11.rs +++ b/neqo-crypto/src/p11.rs @@ -10,6 +10,7 @@ #![allow(non_snake_case)] use std::{ + cell::RefCell, convert::TryFrom, mem, ops::{Deref, DerefMut}, @@ -289,31 +290,107 @@ impl Item { } } -/// Generate a randomized buffer. +/// Fill a buffer with randomness. /// /// # Panics /// /// When `size` is too large or NSS fails. -#[must_use] -pub fn random(size: usize) -> Vec { - let mut buf = vec![0; size]; - secstatus_to_res(unsafe { - PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap()) - }) - .unwrap(); +pub fn randomize>(mut buf: B) -> B { + let m_buf = buf.as_mut(); + let len = c_int::try_from(m_buf.len()).unwrap(); + secstatus_to_res(unsafe { PK11_GenerateRandom(m_buf.as_mut_ptr(), len) }).unwrap(); buf } +struct RandomCache { + cache: [u8; Self::SIZE], + used: usize, +} + +impl RandomCache { + const SIZE: usize = 256; + const CUTOFF: usize = 32; + + fn new() -> Self { + RandomCache { + cache: [0; Self::SIZE], + used: Self::SIZE, + } + } + + fn randomize>(&mut self, mut buf: B) -> B { + let m_buf = buf.as_mut(); + debug_assert!(m_buf.len() <= Self::CUTOFF); + let avail = Self::SIZE - self.used; + if m_buf.len() <= avail { + m_buf.copy_from_slice(&self.cache[self.used..self.used + m_buf.len()]); + self.used += m_buf.len(); + } else { + if avail > 0 { + m_buf[..avail].copy_from_slice(&self.cache[self.used..]); + } + randomize(&mut self.cache[..]); + self.used = m_buf.len() - avail; + m_buf[avail..].copy_from_slice(&self.cache[..self.used]); + } + buf + } +} + +/// Generate a randomized array. +/// +/// # Panics +/// +/// When `size` is too large or NSS fails. +#[must_use] +pub fn random() -> [u8; N] { + thread_local! { static CACHE: RefCell = RefCell::new(RandomCache::new()) }; + + let buf = [0; N]; + if N <= RandomCache::CUTOFF { + CACHE.with_borrow_mut(|c| c.randomize(buf)) + } else { + randomize(buf) + } +} + #[cfg(test)] mod test { use test_fixture::fixture_init; - use super::random; + use super::RandomCache; + use crate::{random, randomize}; #[test] fn randomness() { fixture_init(); - // If this ever fails, there is either a bug, or it's time to buy a lottery ticket. - assert_ne!(random(16), random(16)); + // If any of these ever fail, there is either a bug, or it's time to buy a lottery ticket. + assert_ne!(random::<16>(), randomize([0; 16])); + assert_ne!([0; 16], random::<16>()); + assert_ne!([0; 64], random::<64>()); + } + + #[test] + fn cache_random_lengths() { + const ZERO: [u8; 256] = [0; 256]; + + fixture_init(); + let mut cache = RandomCache::new(); + let mut buf = [0; 256]; + let bits = usize::BITS - (RandomCache::CUTOFF - 1).leading_zeros(); + let mask = 0xff >> (u8::BITS - bits); + + for _ in 0..100 { + let len = loop { + let len = usize::from(random::<1>()[0] & mask) + 1; + if len <= RandomCache::CUTOFF { + break len; + } + }; + buf.fill(0); + if len >= 16 { + assert_ne!(&cache.randomize(&mut buf[..len])[..len], &ZERO[..len]); + } + } } } diff --git a/neqo-crypto/src/selfencrypt.rs b/neqo-crypto/src/selfencrypt.rs index b8a63153fd..1130c35250 100644 --- a/neqo-crypto/src/selfencrypt.rs +++ b/neqo-crypto/src/selfencrypt.rs @@ -82,7 +82,7 @@ impl SelfEncrypt { // opaque aead_encrypted(plaintext)[length as expanded]; // }; // AAD covers the entire header, plus the value of the AAD parameter that is provided. - let salt = random(Self::SALT_LENGTH); + let salt = random::<{ Self::SALT_LENGTH }>(); let cipher = self.make_aead(&self.key, &salt)?; let encoded_len = 2 + salt.len() + plaintext.len() + cipher.expansion(); diff --git a/neqo-crypto/src/time.rs b/neqo-crypto/src/time.rs index 84dbfdb4a5..205f32bbf0 100644 --- a/neqo-crypto/src/time.rs +++ b/neqo-crypto/src/time.rs @@ -12,13 +12,13 @@ use std::{ ops::Deref, os::raw::c_void, pin::Pin, + sync::OnceLock, time::{Duration, Instant}, }; use crate::{ agentio::as_c_void, err::{Error, Res}, - once::OnceResult, ssl::{PRFileDesc, SSLTimeFunc}, }; @@ -67,14 +67,13 @@ impl TimeZero { } } -static mut BASE_TIME: OnceResult = OnceResult::new(); +static BASE_TIME: OnceLock = OnceLock::new(); fn get_base() -> &'static TimeZero { - let f = || TimeZero { + BASE_TIME.get_or_init(|| TimeZero { instant: Instant::now(), prtime: unsafe { PR_Now() }, - }; - unsafe { BASE_TIME.call_once(f) } + }) } pub(crate) fn init() { @@ -97,9 +96,8 @@ impl Deref for Time { impl From for Time { /// Convert from an Instant into a Time. fn from(t: Instant) -> Self { - // Call `TimeZero::baseline(t)` so that time zero can be set. - let f = || TimeZero::baseline(t); - _ = unsafe { BASE_TIME.call_once(f) }; + // Initialize `BASE_TIME` using `TimeZero::baseline(t)`. + BASE_TIME.get_or_init(|| TimeZero::baseline(t)); Self { t } } } diff --git a/neqo-http3/Cargo.toml b/neqo-http3/Cargo.toml index 5aee1dda12..aa7d79f029 100644 --- a/neqo-http3/Cargo.toml +++ b/neqo-http3/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 enumset = "1.1" -lazy_static = "1.4" log = { version = "0.4", default-features = false } neqo-common = { path = "./../neqo-common" } neqo-crypto = { path = "./../neqo-crypto" } diff --git a/neqo-http3/src/frames/hframe.rs b/neqo-http3/src/frames/hframe.rs index 83e69ba894..e69f7b449e 100644 --- a/neqo-http3/src/frames/hframe.rs +++ b/neqo-http3/src/frames/hframe.rs @@ -74,10 +74,7 @@ impl HFrame { Self::MaxPushId { .. } => H3_FRAME_TYPE_MAX_PUSH_ID, Self::PriorityUpdateRequest { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST, Self::PriorityUpdatePush { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH, - Self::Grease => { - let r = random(7); - Decoder::from(&r).decode_uint(7).unwrap() * 0x1f + 0x21 - } + Self::Grease => Decoder::from(&random::<7>()).decode_uint(7).unwrap() * 0x1f + 0x21, } } @@ -120,7 +117,7 @@ impl HFrame { } Self::Grease => { // Encode some number of random bytes. - let r = random(8); + let r = random::<8>(); enc.encode_vvec(&r[1..usize::from(1 + (r[0] & 0x7))]); } Self::PriorityUpdateRequest { diff --git a/neqo-http3/tests/send_message.rs b/neqo-http3/tests/send_message.rs index 507c4bd552..fbf9a7a3ea 100644 --- a/neqo-http3/tests/send_message.rs +++ b/neqo-http3/tests/send_message.rs @@ -4,7 +4,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use lazy_static::lazy_static; +use std::sync::OnceLock; + use neqo_common::event::Provider; use neqo_crypto::AuthenticationStatus; use neqo_http3::{ @@ -15,14 +16,14 @@ use test_fixture::*; const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63]; -lazy_static! { - static ref RESPONSE_HEADER_NO_DATA: Vec
= - vec![Header::new(":status", "200"), Header::new("something", "3")]; +fn response_header_no_data() -> &'static Vec
{ + static HEADERS: OnceLock> = OnceLock::new(); + HEADERS.get_or_init(|| vec![Header::new(":status", "200"), Header::new("something", "3")]) } -lazy_static! { - static ref RESPONSE_HEADER_103: Vec
= - vec![Header::new(":status", "103"), Header::new("link", "...")]; +fn response_header_103() -> &'static Vec
{ + static HEADERS: OnceLock> = OnceLock::new(); + HEADERS.get_or_init(|| vec![Header::new(":status", "103"), Header::new("link", "...")]) } fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { @@ -68,7 +69,7 @@ fn send_trailers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { } fn send_informational_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { - request.send_headers(&RESPONSE_HEADER_103) + request.send_headers(response_header_103()) } fn send_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { @@ -90,7 +91,7 @@ fn process_client_events(conn: &mut Http3Client) { Header::new(":status", "200"), Header::new("content-length", "3"), ]) - || (headers.as_ref() == *RESPONSE_HEADER_103) + || (headers.as_ref() == *response_header_103()) ); assert!(!fin); response_header_found = true; @@ -116,7 +117,7 @@ fn process_client_events_no_data(conn: &mut Http3Client) { while let Some(event) = conn.next_event() { match event { Http3ClientEvent::HeaderReady { headers, fin, .. } => { - assert_eq!(headers.as_ref(), *RESPONSE_HEADER_NO_DATA); + assert_eq!(headers.as_ref(), *response_header_no_data()); fin_received = fin; response_header_found = true; } @@ -201,7 +202,7 @@ fn response_trailers3() { #[test] fn response_trailers_no_data() { let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); - request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + request.send_headers(response_header_no_data()).unwrap(); exchange_packets(&mut hconn_c, &mut hconn_s); send_trailers(&mut request).unwrap(); exchange_packets(&mut hconn_c, &mut hconn_s); @@ -258,10 +259,10 @@ fn trailers_after_close() { #[test] fn multiple_response_headers() { let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); - request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + request.send_headers(response_header_no_data()).unwrap(); assert_eq!( - request.send_headers(&RESPONSE_HEADER_NO_DATA), + request.send_headers(response_header_no_data()), Err(Error::InvalidHeader) ); @@ -273,7 +274,7 @@ fn multiple_response_headers() { #[test] fn informational_after_response_headers() { let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); - request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + request.send_headers(response_header_no_data()).unwrap(); assert_eq!( send_informational_headers(&mut request), @@ -307,7 +308,7 @@ fn non_trailers_headers_after_data() { exchange_packets(&mut hconn_c, &mut hconn_s); assert_eq!( - request.send_headers(&RESPONSE_HEADER_NO_DATA), + request.send_headers(response_header_no_data()), Err(Error::InvalidHeader) ); diff --git a/neqo-interop/Cargo.toml b/neqo-interop/Cargo.toml index e3c2869fa3..f5996032fb 100644 --- a/neqo-interop/Cargo.toml +++ b/neqo-interop/Cargo.toml @@ -12,7 +12,6 @@ license.workspace = true [dependencies] # neqo-interop is not used in Firefox, so we can be liberal with dependency versions clap = { version = "4.4", features = ["derive"] } -lazy_static = "1.4" neqo-common = { path = "./../neqo-common" } neqo-crypto = { path = "./../neqo-crypto" } neqo-http3 = { path = "./../neqo-http3" } diff --git a/neqo-interop/src/main.rs b/neqo-interop/src/main.rs index 455189dd61..fac76a4daa 100644 --- a/neqo-interop/src/main.rs +++ b/neqo-interop/src/main.rs @@ -14,12 +14,12 @@ use std::{ mem, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket}, rc::Rc, + sync::OnceLock, }; // use std::path::PathBuf; use std::{ str::FromStr, string::ParseError, - sync::Mutex, thread, time::{Duration, Instant}, }; @@ -67,22 +67,21 @@ fn emit_datagram(socket: &UdpSocket, d: Datagram) { } } -lazy_static::lazy_static! { - static ref TEST_TIMEOUT: Mutex = Mutex::new(Duration::from_secs(5)); -} - +static TEST_TIMEOUT: OnceLock = OnceLock::new(); struct Timer { end: Instant, } impl Timer { pub fn new() -> Self { Self { - end: Instant::now() + *TEST_TIMEOUT.lock().unwrap(), + end: Instant::now() + *TEST_TIMEOUT.get_or_init(|| Duration::from_secs(5)), } } pub fn set_timeout(t: Duration) { - *TEST_TIMEOUT.lock().unwrap() = t; + TEST_TIMEOUT + .set(t) + .expect("failed to set a timeout because one was already set"); } pub fn check(&self) -> Result { diff --git a/neqo-qpack/Cargo.toml b/neqo-qpack/Cargo.toml index 2be8513ecc..41f72f9ba2 100644 --- a/neqo-qpack/Cargo.toml +++ b/neqo-qpack/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true [dependencies] # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 -lazy_static = "1.4" log = { version = "0.4", default-features = false } neqo-common = { path = "./../neqo-common" } neqo-crypto = { path = "./../neqo-crypto" } diff --git a/neqo-qpack/src/huffman.rs b/neqo-qpack/src/huffman.rs index 283a501b32..ee53a4e041 100644 --- a/neqo-qpack/src/huffman.rs +++ b/neqo-qpack/src/huffman.rs @@ -7,7 +7,7 @@ use std::convert::TryFrom; use crate::{ - huffman_decode_helper::{HuffmanDecoderNode, HUFFMAN_DECODE_ROOT}, + huffman_decode_helper::{huffman_decoder_root, HuffmanDecoderNode}, huffman_table::HUFFMAN_TABLE, Error, Res, }; @@ -93,7 +93,7 @@ pub fn decode_huffman(input: &[u8]) -> Res> { } fn decode_character(reader: &mut BitReader) -> Res> { - let mut node: &HuffmanDecoderNode = &HUFFMAN_DECODE_ROOT; + let mut node: &HuffmanDecoderNode = huffman_decoder_root(); let mut i = 0; while node.value.is_none() { match reader.read_bit() { diff --git a/neqo-qpack/src/huffman_decode_helper.rs b/neqo-qpack/src/huffman_decode_helper.rs index 122226dd1f..1ce4485b0f 100644 --- a/neqo-qpack/src/huffman_decode_helper.rs +++ b/neqo-qpack/src/huffman_decode_helper.rs @@ -4,9 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::convert::TryFrom; - -use lazy_static::lazy_static; +use std::{convert::TryFrom, sync::OnceLock}; use crate::huffman_table::HUFFMAN_TABLE; @@ -15,8 +13,9 @@ pub struct HuffmanDecoderNode { pub value: Option, } -lazy_static! { - pub static ref HUFFMAN_DECODE_ROOT: HuffmanDecoderNode = make_huffman_tree(0, 0); +pub fn huffman_decoder_root() -> &'static HuffmanDecoderNode { + static ROOT: OnceLock = OnceLock::new(); + ROOT.get_or_init(|| make_huffman_tree(0, 0)) } fn make_huffman_tree(prefix: u32, len: u8) -> HuffmanDecoderNode { diff --git a/neqo-server/src/main.rs b/neqo-server/src/main.rs index e7dca12fe6..be56d63586 100644 --- a/neqo-server/src/main.rs +++ b/neqo-server/src/main.rs @@ -573,7 +573,7 @@ impl HttpServer for SimpleServer { fn enable_ech(&mut self) -> &[u8] { let (sk, pk) = generate_ech_keys().expect("should create ECH keys"); self.server - .enable_ech(random(1)[0], "public.example", &sk, &pk) + .enable_ech(random::<1>()[0], "public.example", &sk, &pk) .unwrap(); self.server.ech_config() } diff --git a/neqo-server/src/old_https.rs b/neqo-server/src/old_https.rs index f254446695..bb67ab5c9d 100644 --- a/neqo-server/src/old_https.rs +++ b/neqo-server/src/old_https.rs @@ -246,7 +246,7 @@ impl HttpServer for Http09Server { fn enable_ech(&mut self) -> &[u8] { let (sk, pk) = generate_ech_keys().expect("generate ECH keys"); self.server - .enable_ech(random(1)[0], "public.example", &sk, &pk) + .enable_ech(random::<1>()[0], "public.example", &sk, &pk) .expect("enable ECH"); self.server.ech_config() } diff --git a/neqo-transport/Cargo.toml b/neqo-transport/Cargo.toml index b1ff5b01bd..55cc117f66 100644 --- a/neqo-transport/Cargo.toml +++ b/neqo-transport/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 indexmap = "1.9" -lazy_static = "1.4" log = { version = "0.4", default-features = false } neqo-common = { path = "../neqo-common" } neqo-crypto = { path = "../neqo-crypto" } diff --git a/neqo-transport/src/cid.rs b/neqo-transport/src/cid.rs index 429751bef2..8a0f7a3070 100644 --- a/neqo-transport/src/cid.rs +++ b/neqo-transport/src/cid.rs @@ -16,8 +16,8 @@ use std::{ }; use neqo_common::{hex, hex_with_len, qinfo, Decoder, Encoder}; -use neqo_crypto::random; -use smallvec::SmallVec; +use neqo_crypto::{random, randomize}; +use smallvec::{smallvec, SmallVec}; use crate::{ frame::FRAME_TYPE_NEW_CONNECTION_ID, packet::PacketBuilder, recovery::RecoveryToken, @@ -41,14 +41,16 @@ pub struct ConnectionId { impl ConnectionId { pub fn generate(len: usize) -> Self { assert!(matches!(len, 0..=MAX_CONNECTION_ID_LEN)); - Self::from(random(len)) + let mut cid = smallvec![0; len]; + randomize(&mut cid); + Self { cid } } // Apply a wee bit of greasing here in picking a length between 8 and 20 bytes long. pub fn generate_initial() -> Self { - let v = random(1); + let v = random::<1>()[0]; // Bias selection toward picking 8 (>50% of the time). - let len: usize = max(8, 5 + (v[0] & (v[0] >> 4))).into(); + let len: usize = max(8, 5 + (v & (v >> 4))).into(); Self::generate(len) } @@ -75,12 +77,6 @@ impl From> for ConnectionId { } } -impl From> for ConnectionId { - fn from(cid: Vec) -> Self { - Self::from(SmallVec::from(cid)) - } -} - impl + ?Sized> From<&T> for ConnectionId { fn from(buf: &T) -> Self { Self::from(SmallVec::from(buf.as_ref())) @@ -222,7 +218,9 @@ impl ConnectionIdDecoder for RandomConnectionIdGenerator { impl ConnectionIdGenerator for RandomConnectionIdGenerator { fn generate_cid(&mut self) -> Option { - Some(ConnectionId::from(&random(self.len))) + let mut buf = smallvec![0; self.len]; + randomize(&mut buf); + Some(ConnectionId::from(buf)) } fn as_decoder(&self) -> &dyn ConnectionIdDecoder { @@ -250,8 +248,8 @@ pub struct ConnectionIdEntry { impl ConnectionIdEntry<[u8; 16]> { /// Create a random stateless reset token so that it is hard to guess the correct /// value and reset the connection. - fn random_srt() -> [u8; 16] { - <[u8; 16]>::try_from(&random(16)[..]).unwrap() + pub fn random_srt() -> [u8; 16] { + random::<16>() } /// Create the first entry, which won't have a stateless reset token. @@ -476,7 +474,7 @@ impl ConnectionIdManager { .add_local(ConnectionIdEntry::new(self.next_seqno, cid.clone(), ())); self.next_seqno += 1; - let srt = <[u8; 16]>::try_from(&random(16)[..]).unwrap(); + let srt = ConnectionIdEntry::random_srt(); Ok((cid, srt)) } else { Err(Error::ConnectionIdsExhausted) @@ -565,7 +563,7 @@ impl ConnectionIdManager { if let Some(cid) = maybe_cid { assert_ne!(cid.len(), 0); // TODO: generate the stateless reset tokens from the connection ID and a key. - let srt = <[u8; 16]>::try_from(&random(16)[..]).unwrap(); + let srt = ConnectionIdEntry::random_srt(); let seqno = self.next_seqno; self.next_seqno += 1; diff --git a/neqo-transport/src/connection/mod.rs b/neqo-transport/src/connection/mod.rs index d33f77ed6b..0b33bd15ec 100644 --- a/neqo-transport/src/connection/mod.rs +++ b/neqo-transport/src/connection/mod.rs @@ -23,7 +23,7 @@ use neqo_common::{ qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, Role, }; use neqo_crypto::{ - agent::CertificateInfo, random, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, Group, + agent::CertificateInfo, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, Group, HandshakeState, PrivateKey, PublicKey, ResumptionToken, SecretAgentInfo, SecretAgentPreInfo, Server, ZeroRttChecker, }; @@ -2405,7 +2405,7 @@ impl Connection { } else { // The other side didn't provide a stateless reset token. // That's OK, they can try guessing this. - <[u8; 16]>::try_from(&random(16)[..]).unwrap() + ConnectionIdEntry::random_srt() }; self.paths .primary() diff --git a/neqo-transport/src/connection/tests/mod.rs b/neqo-transport/src/connection/tests/mod.rs index d958ecd70c..5470c18000 100644 --- a/neqo-transport/src/connection/tests/mod.rs +++ b/neqo-transport/src/connection/tests/mod.rs @@ -79,7 +79,7 @@ impl ConnectionIdDecoder for CountingConnectionIdGenerator { impl ConnectionIdGenerator for CountingConnectionIdGenerator { fn generate_cid(&mut self) -> Option { - let mut r = random(20); + let mut r = random::<20>(); r[0] = 8; r[1] = u8::try_from(self.counter >> 24).unwrap(); r[2] = u8::try_from((self.counter >> 16) & 0xff).unwrap(); diff --git a/neqo-transport/src/packet/mod.rs b/neqo-transport/src/packet/mod.rs index ccfd212d5f..7e19beba5f 100644 --- a/neqo-transport/src/packet/mod.rs +++ b/neqo-transport/src/packet/mod.rs @@ -271,7 +271,7 @@ impl PacketBuilder { let mask = if quic_bit { PACKET_BIT_FIXED_QUIC } else { 0 } | if self.is_long() { 0 } else { PACKET_BIT_SPIN }; let first = self.header.start; - self.encoder.as_mut()[first] ^= random(1)[0] & mask; + self.encoder.as_mut()[first] ^= random::<1>()[0] & mask; } /// For an Initial packet, encode the token. @@ -424,7 +424,7 @@ impl PacketBuilder { PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC | (PacketType::Retry.to_byte(version) << 4) - | (random(1)[0] & 0xf), + | (random::<1>()[0] & 0xf), ); encoder.encode_uint(4, version.wire_version()); encoder.encode_vec(1, dcid); @@ -448,7 +448,7 @@ impl PacketBuilder { versions: &[Version], ) -> Vec { let mut encoder = Encoder::default(); - let mut grease = random(4); + let mut grease = random::<4>(); // This will not include the "QUIC bit" sometimes. Intentionally. encoder.encode_byte(PACKET_BIT_LONG | (grease[3] & 0x7f)); encoder.encode(&[0; 4]); // Zero version == VN. diff --git a/neqo-transport/src/path.rs b/neqo-transport/src/path.rs index d6920c8d94..4246e6ed1c 100644 --- a/neqo-transport/src/path.rs +++ b/neqo-transport/src/path.rs @@ -796,7 +796,7 @@ impl Path { // Send PATH_CHALLENGE. if let ProbeState::ProbeNeeded { probe_count } = self.state { qtrace!([self], "Initiating path challenge {}", probe_count); - let data = <[u8; 8]>::try_from(&random(8)[..]).unwrap(); + let data = random::<8>(); builder.encode_varint(FRAME_TYPE_PATH_CHALLENGE); builder.encode(&data); diff --git a/neqo-transport/src/tparams.rs b/neqo-transport/src/tparams.rs index 1297829094..638e3adf89 100644 --- a/neqo-transport/src/tparams.rs +++ b/neqo-transport/src/tparams.rs @@ -438,7 +438,7 @@ impl TransportParameters { /// Set version information. pub fn set_versions(&mut self, role: Role, versions: &VersionConfig) { - let rbuf = random(4); + let rbuf = random::<4>(); let mut other = Vec::with_capacity(versions.all().len() + 1); let mut dec = Decoder::new(&rbuf); let grease = (dec.decode_uint(4).unwrap() as u32) & 0xf0f0_f0f0 | 0x0a0a_0a0a; diff --git a/neqo-transport/src/tracking.rs b/neqo-transport/src/tracking.rs index 64d00257d3..012c895a18 100644 --- a/neqo-transport/src/tracking.rs +++ b/neqo-transport/src/tracking.rs @@ -746,8 +746,8 @@ impl Default for AckTracker { mod tests { use std::collections::HashSet; - use lazy_static::lazy_static; use neqo_common::Encoder; + use test_fixture::now; use super::{ AckTracker, Duration, Instant, PacketNumberSpace, PacketNumberSpaceSet, RecoveryToken, @@ -760,16 +760,13 @@ mod tests { }; const RTT: Duration = Duration::from_millis(100); - lazy_static! { - static ref NOW: Instant = Instant::now(); - } fn test_ack_range(pns: &[PacketNumber], nranges: usize) { let mut rp = RecvdPackets::new(PacketNumberSpace::Initial); // Any space will do. let mut packets = HashSet::new(); for pn in pns { - rp.set_received(*NOW, *pn, true); + rp.set_received(now(), *pn, true); packets.insert(*pn); } @@ -824,7 +821,7 @@ mod tests { // This will add one too many disjoint ranges. for i in 0..=MAX_TRACKED_RANGES { - rp.set_received(*NOW, (i * 2) as u64, true); + rp.set_received(now(), (i * 2) as u64, true); } assert_eq!(rp.ranges.len(), MAX_TRACKED_RANGES); @@ -843,22 +840,22 @@ mod tests { // Only application data packets are delayed. let mut rp = RecvdPackets::new(PacketNumberSpace::ApplicationData); assert!(rp.ack_time().is_none()); - assert!(!rp.ack_now(*NOW, RTT)); + assert!(!rp.ack_now(now(), RTT)); rp.ack_freq(0, COUNT, DELAY, false); // Some packets won't cause an ACK to be needed. for i in 0..COUNT { - rp.set_received(*NOW, i, true); - assert_eq!(Some(*NOW + DELAY), rp.ack_time()); - assert!(!rp.ack_now(*NOW, RTT)); - assert!(rp.ack_now(*NOW + DELAY, RTT)); + rp.set_received(now(), i, true); + assert_eq!(Some(now() + DELAY), rp.ack_time()); + assert!(!rp.ack_now(now(), RTT)); + assert!(rp.ack_now(now() + DELAY, RTT)); } // Exceeding COUNT will move the ACK time to now. - rp.set_received(*NOW, COUNT, true); - assert_eq!(Some(*NOW), rp.ack_time()); - assert!(rp.ack_now(*NOW, RTT)); + rp.set_received(now(), COUNT, true); + assert_eq!(Some(now()), rp.ack_time()); + assert!(rp.ack_now(now(), RTT)); } #[test] @@ -866,12 +863,12 @@ mod tests { for space in &[PacketNumberSpace::Initial, PacketNumberSpace::Handshake] { let mut rp = RecvdPackets::new(*space); assert!(rp.ack_time().is_none()); - assert!(!rp.ack_now(*NOW, RTT)); + assert!(!rp.ack_now(now(), RTT)); // Any packet in these spaces is acknowledged straight away. - rp.set_received(*NOW, 0, true); - assert_eq!(Some(*NOW), rp.ack_time()); - assert!(rp.ack_now(*NOW, RTT)); + rp.set_received(now(), 0, true); + assert_eq!(Some(now()), rp.ack_time()); + assert!(rp.ack_now(now(), RTT)); } } @@ -879,12 +876,12 @@ mod tests { fn ooo_no_ack_delay_new() { let mut rp = RecvdPackets::new(PacketNumberSpace::ApplicationData); assert!(rp.ack_time().is_none()); - assert!(!rp.ack_now(*NOW, RTT)); + assert!(!rp.ack_now(now(), RTT)); // Anything other than packet 0 is acknowledged immediately. - rp.set_received(*NOW, 1, true); - assert_eq!(Some(*NOW), rp.ack_time()); - assert!(rp.ack_now(*NOW, RTT)); + rp.set_received(now(), 1, true); + assert_eq!(Some(now()), rp.ack_time()); + assert!(rp.ack_now(now(), RTT)); } fn write_frame_at(rp: &mut RecvdPackets, now: Instant) { @@ -897,37 +894,37 @@ mod tests { } fn write_frame(rp: &mut RecvdPackets) { - write_frame_at(rp, *NOW); + write_frame_at(rp, now()); } #[test] fn ooo_no_ack_delay_fill() { let mut rp = RecvdPackets::new(PacketNumberSpace::ApplicationData); - rp.set_received(*NOW, 1, true); + rp.set_received(now(), 1, true); write_frame(&mut rp); // Filling in behind the largest acknowledged causes immediate ACK. - rp.set_received(*NOW, 0, true); + rp.set_received(now(), 0, true); write_frame(&mut rp); // Receiving the next packet won't elicit an ACK. - rp.set_received(*NOW, 2, true); - assert!(!rp.ack_now(*NOW, RTT)); + rp.set_received(now(), 2, true); + assert!(!rp.ack_now(now(), RTT)); } #[test] fn immediate_ack_after_rtt() { let mut rp = RecvdPackets::new(PacketNumberSpace::ApplicationData); - rp.set_received(*NOW, 1, true); + rp.set_received(now(), 1, true); write_frame(&mut rp); // Filling in behind the largest acknowledged causes immediate ACK. - rp.set_received(*NOW, 0, true); + rp.set_received(now(), 0, true); write_frame(&mut rp); // A new packet ordinarily doesn't result in an ACK, but this time it does. - rp.set_received(*NOW + RTT, 2, true); - write_frame_at(&mut rp, *NOW + RTT); + rp.set_received(now() + RTT, 2, true); + write_frame_at(&mut rp, now() + RTT); } #[test] @@ -937,29 +934,29 @@ mod tests { // Set tolerance to 2 and then it takes three packets. rp.ack_freq(0, 2, Duration::from_millis(10), true); - rp.set_received(*NOW, 1, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 2, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 3, true); - assert_eq!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 1, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 2, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 3, true); + assert_eq!(Some(now()), rp.ack_time()); } #[test] fn ooo_no_ack_delay_threshold_gap() { let mut rp = RecvdPackets::new(PacketNumberSpace::ApplicationData); - rp.set_received(*NOW, 1, true); + rp.set_received(now(), 1, true); write_frame(&mut rp); // Set tolerance to 2 and then it takes three packets. rp.ack_freq(0, 2, Duration::from_millis(10), true); - rp.set_received(*NOW, 3, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 4, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 5, true); - assert_eq!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 3, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 4, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 5, true); + assert_eq!(Some(now()), rp.ack_time()); } /// Test that an in-order packet that is not ack-eliciting doesn't @@ -970,13 +967,13 @@ mod tests { rp.ack_freq(0, 1, Duration::from_millis(10), true); // This should be ignored. - rp.set_received(*NOW, 0, false); - assert_ne!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 0, false); + assert_ne!(Some(now()), rp.ack_time()); // Skip 1 (it has no effect). - rp.set_received(*NOW, 2, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 3, true); - assert_eq!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 2, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 3, true); + assert_eq!(Some(now()), rp.ack_time()); } /// If a packet that is not ack-eliciting is reordered, that's fine too. @@ -986,16 +983,16 @@ mod tests { rp.ack_freq(0, 1, Duration::from_millis(10), false); // These are out of order, but they are not ack-eliciting. - rp.set_received(*NOW, 1, false); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 0, false); - assert_ne!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 1, false); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 0, false); + assert_ne!(Some(now()), rp.ack_time()); // These are in order. - rp.set_received(*NOW, 2, true); - assert_ne!(Some(*NOW), rp.ack_time()); - rp.set_received(*NOW, 3, true); - assert_eq!(Some(*NOW), rp.ack_time()); + rp.set_received(now(), 2, true); + assert_ne!(Some(now()), rp.ack_time()); + rp.set_received(now(), 3, true); + assert_eq!(Some(now()), rp.ack_time()); } #[test] @@ -1007,23 +1004,23 @@ mod tests { tracker .get_mut(PacketNumberSpace::Handshake) .unwrap() - .set_received(*NOW, 0, false); - assert_eq!(None, tracker.ack_time(*NOW)); + .set_received(now(), 0, false); + assert_eq!(None, tracker.ack_time(now())); // This should be delayed. tracker .get_mut(PacketNumberSpace::ApplicationData) .unwrap() - .set_received(*NOW, 0, true); - assert_eq!(Some(*NOW + DELAY), tracker.ack_time(*NOW)); + .set_received(now(), 0, true); + assert_eq!(Some(now() + DELAY), tracker.ack_time(now())); // This should move the time forward. - let later = *NOW + (DELAY / 2); + let later = now() + (DELAY / 2); tracker .get_mut(PacketNumberSpace::Initial) .unwrap() .set_received(later, 0, true); - assert_eq!(Some(later), tracker.ack_time(*NOW)); + assert_eq!(Some(later), tracker.ack_time(now())); } #[test] @@ -1047,17 +1044,17 @@ mod tests { tracker .get_mut(PacketNumberSpace::Initial) .unwrap() - .set_received(*NOW, 0, true); + .set_received(now(), 0, true); // The reference time for `ack_time` has to be in the past or we filter out the timer. assert!(tracker - .ack_time(NOW.checked_sub(Duration::from_millis(1)).unwrap()) + .ack_time(now().checked_sub(Duration::from_millis(1)).unwrap()) .is_some()); let mut tokens = Vec::new(); let mut stats = FrameStats::default(); tracker.write_frame( PacketNumberSpace::Initial, - *NOW, + now(), RTT, &mut builder, &mut tokens, @@ -1069,9 +1066,9 @@ mod tests { tracker .get_mut(PacketNumberSpace::Initial) .unwrap() - .set_received(*NOW, 1, true); + .set_received(now(), 1, true); assert!(tracker - .ack_time(NOW.checked_sub(Duration::from_millis(1)).unwrap()) + .ack_time(now().checked_sub(Duration::from_millis(1)).unwrap()) .is_some()); // Now drop that space. @@ -1079,11 +1076,11 @@ mod tests { assert!(tracker.get_mut(PacketNumberSpace::Initial).is_none()); assert!(tracker - .ack_time(NOW.checked_sub(Duration::from_millis(1)).unwrap()) + .ack_time(now().checked_sub(Duration::from_millis(1)).unwrap()) .is_none()); tracker.write_frame( PacketNumberSpace::Initial, - *NOW, + now(), RTT, &mut builder, &mut tokens, @@ -1103,9 +1100,9 @@ mod tests { tracker .get_mut(PacketNumberSpace::Initial) .unwrap() - .set_received(*NOW, 0, true); + .set_received(now(), 0, true); assert!(tracker - .ack_time(NOW.checked_sub(Duration::from_millis(1)).unwrap()) + .ack_time(now().checked_sub(Duration::from_millis(1)).unwrap()) .is_some()); let mut builder = PacketBuilder::short(Encoder::new(), false, []); @@ -1114,7 +1111,7 @@ mod tests { let mut stats = FrameStats::default(); tracker.write_frame( PacketNumberSpace::Initial, - *NOW, + now(), RTT, &mut builder, &mut Vec::new(), @@ -1130,13 +1127,13 @@ mod tests { tracker .get_mut(PacketNumberSpace::Initial) .unwrap() - .set_received(*NOW, 0, true); + .set_received(now(), 0, true); tracker .get_mut(PacketNumberSpace::Initial) .unwrap() - .set_received(*NOW, 2, true); + .set_received(now(), 2, true); assert!(tracker - .ack_time(NOW.checked_sub(Duration::from_millis(1)).unwrap()) + .ack_time(now().checked_sub(Duration::from_millis(1)).unwrap()) .is_some()); let mut builder = PacketBuilder::short(Encoder::new(), false, []); @@ -1145,7 +1142,7 @@ mod tests { let mut stats = FrameStats::default(); tracker.write_frame( PacketNumberSpace::Initial, - *NOW, + now(), RTT, &mut builder, &mut Vec::new(), @@ -1168,19 +1165,19 @@ mod tests { let mut tracker = AckTracker::default(); // While we have multiple PN spaces, we ignore ACK timers from the past. - // Send out of order to cause the delayed ack timer to be set to `*NOW`. + // Send out of order to cause the delayed ack timer to be set to `now()`. tracker .get_mut(PacketNumberSpace::ApplicationData) .unwrap() - .set_received(*NOW, 3, true); - assert!(tracker.ack_time(*NOW + Duration::from_millis(1)).is_none()); + .set_received(now(), 3, true); + assert!(tracker.ack_time(now() + Duration::from_millis(1)).is_none()); // When we are reduced to one space, that filter is off. tracker.drop_space(PacketNumberSpace::Initial); tracker.drop_space(PacketNumberSpace::Handshake); assert_eq!( - tracker.ack_time(*NOW + Duration::from_millis(1)), - Some(*NOW) + tracker.ack_time(now() + Duration::from_millis(1)), + Some(now()) ); } diff --git a/test-fixture/Cargo.toml b/test-fixture/Cargo.toml index aebeba04f4..18bdb114be 100644 --- a/test-fixture/Cargo.toml +++ b/test-fixture/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true [dependencies] # Sync with https://searchfox.org/mozilla-central/source/Cargo.lock 2024-02-08 -lazy_static = "1.4" log = { version = "0.4", default-features = false } neqo-common = { path = "../neqo-common" } neqo-crypto = { path = "../neqo-crypto" } diff --git a/test-fixture/src/lib.rs b/test-fixture/src/lib.rs index 96ed335a83..923aa76c15 100644 --- a/test-fixture/src/lib.rs +++ b/test-fixture/src/lib.rs @@ -8,7 +8,7 @@ #![warn(clippy::pedantic)] use std::{ - cell::RefCell, + cell::{OnceCell, RefCell}, cmp::max, convert::TryFrom, io::{Cursor, Result, Write}, @@ -19,7 +19,6 @@ use std::{ time::{Duration, Instant}, }; -use lazy_static::lazy_static; use neqo_common::{ event::Provider, hex, @@ -51,13 +50,14 @@ pub fn fixture_init() { // NSS operates in milliseconds and halves any value it is provided. pub const ANTI_REPLAY_WINDOW: Duration = Duration::from_millis(10); -lazy_static! { - static ref BASE_TIME: Instant = Instant::now(); -} - +/// A baseline time for all tests. This needs to be earlier than what `now()` produces +/// because of the need to have a span of time elapse for anti-replay purposes. fn earlier() -> Instant { + // Note: It is only OK to have a different base time for each thread because our tests are + // single-threaded. + thread_local!(static EARLIER: OnceCell = OnceCell::new()); fixture_init(); - *BASE_TIME + EARLIER.with(|b| *b.get_or_init(Instant::now)) } /// The current time for the test. Which is in the future, @@ -131,7 +131,7 @@ impl ConnectionIdDecoder for CountingConnectionIdGenerator { impl ConnectionIdGenerator for CountingConnectionIdGenerator { fn generate_cid(&mut self) -> Option { - let mut r = random(20); + let mut r = random::<20>(); // Randomize length, but ensure that the connection ID is long // enough to pass for an original destination connection ID. r[0] = max(8, 5 + ((r[0] >> 4) & r[0])); diff --git a/test-fixture/src/sim/mod.rs b/test-fixture/src/sim/mod.rs index f4b7a52739..cbea621f1b 100644 --- a/test-fixture/src/sim/mod.rs +++ b/test-fixture/src/sim/mod.rs @@ -158,7 +158,7 @@ impl Simulator { } pub fn seed(&mut self, seed: [u8; 32]) { - self.rng = Rc::new(RefCell::new(Random::new(seed))); + self.rng = Rc::new(RefCell::new(Random::new(&seed))); } /// Seed from a hex string. diff --git a/test-fixture/src/sim/rng.rs b/test-fixture/src/sim/rng.rs index 094c5fd791..913d7eae7a 100644 --- a/test-fixture/src/sim/rng.rs +++ b/test-fixture/src/sim/rng.rs @@ -4,7 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::{convert::TryFrom, ops::Range}; +use std::ops::Range; use neqo_common::Decoder; @@ -16,7 +16,7 @@ pub struct Random { impl Random { #[must_use] #[allow(clippy::missing_panics_doc)] // These are impossible. - pub fn new(seed: [u8; 32]) -> Self { + pub fn new(seed: &[u8; 32]) -> Self { assert!(seed.iter().any(|&x| x != 0)); let mut dec = Decoder::from(&seed); Self { @@ -78,7 +78,6 @@ impl Random { impl Default for Random { fn default() -> Self { - let buf = neqo_crypto::random(32); - Random::new(<[u8; 32]>::try_from(&buf[..]).unwrap()) + Random::new(&neqo_crypto::random::<32>()) } }