From 3f33f630b2eb2d17a146c6652b38326844d67053 Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Sun, 3 May 2026 22:50:42 +0700 Subject: [PATCH] test(client): split client tests into behavior-focused modules --- crates/irtt-client/src/client/tests/close.rs | 97 ++++ crates/irtt-client/src/client/tests/config.rs | 117 ++++ .../src/client/tests/config_open.rs | 515 ------------------ crates/irtt-client/src/client/tests/hmac.rs | 184 +++++++ crates/irtt-client/src/client/tests/mod.rs | 7 +- .../src/client/tests/negotiation.rs | 30 + .../irtt-client/src/client/tests/no_test.rs | 125 +++++ crates/irtt-client/src/client/tests/open.rs | 132 +++++ .../src/client/tests/probes_replies.rs | 185 ------- .../irtt-client/src/client/tests/support.rs | 20 + 10 files changed, 711 insertions(+), 701 deletions(-) create mode 100644 crates/irtt-client/src/client/tests/close.rs create mode 100644 crates/irtt-client/src/client/tests/config.rs delete mode 100644 crates/irtt-client/src/client/tests/config_open.rs create mode 100644 crates/irtt-client/src/client/tests/hmac.rs create mode 100644 crates/irtt-client/src/client/tests/negotiation.rs create mode 100644 crates/irtt-client/src/client/tests/no_test.rs create mode 100644 crates/irtt-client/src/client/tests/open.rs diff --git a/crates/irtt-client/src/client/tests/close.rs b/crates/irtt-client/src/client/tests/close.rs new file mode 100644 index 0000000..804686b --- /dev/null +++ b/crates/irtt-client/src/client/tests/close.rs @@ -0,0 +1,97 @@ +use super::*; + +#[test] +fn open_fails_after_close() { + let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + let _ = recv_request(&socket, &tx); + }); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + client.close(ClientTimestamp::now()).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::AlreadyClosed) + )); + server.join(); +} + +#[test] +fn close_sends_one_close_packet_with_negotiated_token() { + let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + let _ = recv_request(&socket, &tx); + }); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + let events = client.close(ClientTimestamp::now()).unwrap(); + assert_eq!(events.len(), 1); + let packets: Vec<_> = server.rx.iter().take(2).collect(); + let close = &packets[1]; + assert_eq!(close[3], flags::FLAG_CLOSE); + assert_eq!(u64::from_le_bytes(close[4..12].try_into().unwrap()), TOKEN); + server.join(); +} + +#[test] +fn send_probe_fails_after_close() { + let params = default_params(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + socket + .set_read_timeout(Some(Duration::from_secs(2))) + .unwrap(); + loop { + let mut buf = [0_u8; 512]; + match socket.recv_from(&mut buf) { + Ok((size, _)) => { + tx.send(buf[..size].to_vec()).unwrap(); + } + Err(_) => break, + } + } + }); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + client.close(ClientTimestamp::now()).unwrap(); + assert!(matches!( + client.send_probe(), + Err(ClientError::AlreadyClosed) + )); + server.join(); +} + +#[test] +fn close_clears_timed_out_metadata() { + let params = default_params(); + let server = silent_open_server(params); + let config = ClientConfig { + probe_timeout: Duration::from_millis(40), + socket_config: crate::SocketConfig { + recv_timeout: Some(Duration::from_millis(50)), + ..Default::default() + }, + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + + client.send_probe().unwrap(); + thread::sleep(Duration::from_millis(60)); + client.poll_timeouts(ClientTimestamp::now()).unwrap(); + assert_eq!(client.session.as_ref().unwrap().timed_out.len(), 1); + + client.close(ClientTimestamp::now()).unwrap(); + assert!(client.session.is_none()); + server.join(); +} diff --git a/crates/irtt-client/src/client/tests/config.rs b/crates/irtt-client/src/client/tests/config.rs new file mode 100644 index 0000000..790fa72 --- /dev/null +++ b/crates/irtt-client/src/client/tests/config.rs @@ -0,0 +1,117 @@ +use super::*; + +#[test] +fn client_config_default() { + let config = ClientConfig::default(); + assert_eq!(config.duration, Some(Duration::from_secs(3))); + assert_eq!(config.interval, Duration::from_secs(1)); + assert_eq!(config.length, 0); + assert_eq!(config.received_stats, ReceivedStats::Both); + assert_eq!(config.stamp_at, StampAt::Both); + assert_eq!(config.clock, Clock::Both); + assert_eq!(config.dscp, 0); + assert_eq!(config.hmac_key, None); + assert_eq!(config.server_fill, None); + assert_eq!(config.open_timeouts, DEFAULT_OPEN_TIMEOUTS); + assert_eq!(config.run_mode, RunMode::Normal); + assert_eq!(config.negotiation_policy, NegotiationPolicy::Strict); + assert_eq!(config.probe_timeout, Duration::from_secs(4)); + assert_eq!(config.max_pending_probes, 4096); +} + +#[test] +fn params_from_config_maps_compatibility_fields() { + let config = ClientConfig { + duration: Some(Duration::from_secs(5)), + interval: Duration::from_millis(250), + length: 1472, + received_stats: ReceivedStats::Window, + stamp_at: StampAt::Midpoint, + clock: Clock::Wall, + dscp: 46, + hmac_key: Some(b"secret".to_vec()), + server_fill: Some("rand".to_owned()), + ..ClientConfig::default() + }; + + let params = params_from_config(&config).unwrap(); + assert_eq!(params.protocol_version, PROTOCOL_VERSION); + assert_eq!(params.duration_ns, 5_000_000_000); + assert_eq!(params.interval_ns, 250_000_000); + assert_eq!(params.length, 1472); + assert_eq!(params.received_stats, ReceivedStats::Window); + assert_eq!(params.stamp_at, StampAt::Midpoint); + assert_eq!(params.clock, Clock::Wall); + assert_eq!(params.dscp, 46, "config DSCP codepoint must not be shifted"); + assert_eq!( + params.server_fill.as_ref().map(|fill| fill.value.as_str()), + Some("rand") + ); + assert_eq!(config.hmac_key.as_deref(), Some(b"secret".as_slice())); +} + +#[test] +fn params_from_config_encodes_continuous_duration_as_zero() { + let config = ClientConfig { + duration: None, + ..ClientConfig::default() + }; + assert_eq!(params_from_config(&config).unwrap().duration_ns, 0); +} + +#[test] +fn params_from_config_accepts_max_dscp_codepoint() { + let config = ClientConfig { + dscp: 63, + ..ClientConfig::default() + }; + assert_eq!(params_from_config(&config).unwrap().dscp, 63); +} + +#[test] +fn params_from_config_rejects_invalid_dscp_codepoint() { + let config = ClientConfig { + dscp: 64, + ..ClientConfig::default() + }; + assert!(matches!( + params_from_config(&config), + Err(ClientError::InvalidConfig { .. }) + )); +} + +#[test] +fn params_from_config_rejects_oversized_server_fill() { + let config = ClientConfig { + server_fill: Some("0123456789abcdef0123456789abcdefx".to_owned()), + ..ClientConfig::default() + }; + assert!(matches!( + params_from_config(&config), + Err(ClientError::InvalidConfig { .. }) + )); +} + +#[test] +fn minimum_open_timeout_under_200ms_is_rejected() { + let config = ClientConfig { + open_timeouts: vec![Duration::from_millis(199)], + ..ClientConfig::default() + }; + assert!(matches!( + Client::connect(config), + Err(ClientError::OpenTimeoutTooSmall { .. }) + )); +} + +#[test] +fn empty_open_timeouts_is_rejected() { + let config = ClientConfig { + open_timeouts: vec![], + ..ClientConfig::default() + }; + assert!(matches!( + Client::connect(config), + Err(ClientError::NoOpenTimeouts) + )); +} diff --git a/crates/irtt-client/src/client/tests/config_open.rs b/crates/irtt-client/src/client/tests/config_open.rs deleted file mode 100644 index fc3423f..0000000 --- a/crates/irtt-client/src/client/tests/config_open.rs +++ /dev/null @@ -1,515 +0,0 @@ -use super::*; - -#[test] -fn client_config_default() { - let config = ClientConfig::default(); - assert_eq!(config.duration, Some(Duration::from_secs(3))); - assert_eq!(config.interval, Duration::from_secs(1)); - assert_eq!(config.length, 0); - assert_eq!(config.received_stats, ReceivedStats::Both); - assert_eq!(config.stamp_at, StampAt::Both); - assert_eq!(config.clock, Clock::Both); - assert_eq!(config.dscp, 0); - assert_eq!(config.hmac_key, None); - assert_eq!(config.server_fill, None); - assert_eq!(config.open_timeouts, DEFAULT_OPEN_TIMEOUTS); - assert_eq!(config.run_mode, RunMode::Normal); - assert_eq!(config.negotiation_policy, NegotiationPolicy::Strict); - assert_eq!(config.probe_timeout, Duration::from_secs(4)); - assert_eq!(config.max_pending_probes, 4096); -} - -#[test] -fn params_from_config_maps_compatibility_fields() { - let config = ClientConfig { - duration: Some(Duration::from_secs(5)), - interval: Duration::from_millis(250), - length: 1472, - received_stats: ReceivedStats::Window, - stamp_at: StampAt::Midpoint, - clock: Clock::Wall, - dscp: 46, - hmac_key: Some(b"secret".to_vec()), - server_fill: Some("rand".to_owned()), - ..ClientConfig::default() - }; - - let params = params_from_config(&config).unwrap(); - assert_eq!(params.protocol_version, PROTOCOL_VERSION); - assert_eq!(params.duration_ns, 5_000_000_000); - assert_eq!(params.interval_ns, 250_000_000); - assert_eq!(params.length, 1472); - assert_eq!(params.received_stats, ReceivedStats::Window); - assert_eq!(params.stamp_at, StampAt::Midpoint); - assert_eq!(params.clock, Clock::Wall); - assert_eq!(params.dscp, 46, "config DSCP codepoint must not be shifted"); - assert_eq!( - params.server_fill.as_ref().map(|fill| fill.value.as_str()), - Some("rand") - ); - assert_eq!(config.hmac_key.as_deref(), Some(b"secret".as_slice())); -} - -#[test] -fn params_from_config_encodes_continuous_duration_as_zero() { - let config = ClientConfig { - duration: None, - ..ClientConfig::default() - }; - assert_eq!(params_from_config(&config).unwrap().duration_ns, 0); -} - -#[test] -fn params_from_config_accepts_max_dscp_codepoint() { - let config = ClientConfig { - dscp: 63, - ..ClientConfig::default() - }; - assert_eq!(params_from_config(&config).unwrap().dscp, 63); -} - -#[test] -fn params_from_config_rejects_invalid_dscp_codepoint() { - let config = ClientConfig { - dscp: 64, - ..ClientConfig::default() - }; - assert!(matches!( - params_from_config(&config), - Err(ClientError::InvalidConfig { .. }) - )); -} - -#[test] -fn params_from_config_rejects_oversized_server_fill() { - let config = ClientConfig { - server_fill: Some("0123456789abcdef0123456789abcdefx".to_owned()), - ..ClientConfig::default() - }; - assert!(matches!( - params_from_config(&config), - Err(ClientError::InvalidConfig { .. }) - )); -} - -#[test] -fn address_resolution_connects_to_local_fake_server() { - let server = start_fake_server(|socket, tx| { - let _ = recv_request(&socket, &tx); - }); - let client = Client::connect(default_test_config(server.addr)).unwrap(); - client.socket.send(b"ping").unwrap(); - assert_eq!(server.rx.recv().unwrap(), b"ping"); - server.join(); -} - -#[test] -fn successful_open_handshake() { - let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - let params = params_from_config(&config).unwrap(); - let server = open_success_server(params.clone()); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - - let negotiated = assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - assert_eq!(negotiated.params, params); - assert!(matches!(client.phase, ClientPhase::Open { token: TOKEN })); - server.join(); -} - -#[test] -fn open_fails_when_already_open() { - let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - let params = params_from_config(&config).unwrap(); - let server = open_success_server(params); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::AlreadyOpen) - )); - server.join(); -} - -#[test] -fn open_fails_after_close() { - let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - let _ = recv_request(&socket, &tx); - }); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - client.close(ClientTimestamp::now()).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::AlreadyClosed) - )); - server.join(); -} - -#[test] -fn open_fails_after_no_test_completed() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = no_test_server(params, 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::AlreadyCompleted) - )); - server.join(); -} - -#[test] -fn open_retries_after_first_timeout() { - let server = start_fake_server(|socket, tx| { - let (first, _) = recv_request(&socket, &tx); - let (_, peer) = recv_request(&socket, &tx); - let params = Params::decode(&first[4..]).unwrap(); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - assert_eq!(first[3] & FLAG_OPEN, FLAG_OPEN); - socket.send_to(&reply, peer).unwrap(); - }); - let config = ClientConfig { - open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(500)], - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - let outcome = client.open(ClientTimestamp::now()).unwrap(); - assert_open_started(outcome); - assert_eq!(server.rx.iter().take(2).count(), 2); - server.join(); -} - -#[test] -fn open_timeout_after_all_timeouts() { - let server = timeout_server(Duration::from_millis(700)); - let config = ClientConfig { - open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(200)], - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::OpenTimeout) - )); - assert_eq!(server.rx.iter().take(2).count(), 2); - server.join(); -} - -#[test] -fn open_restores_configured_read_timeout_after_timeout() { - let server = timeout_server(Duration::from_millis(700)); - let config = ClientConfig { - open_timeouts: vec![Duration::from_millis(200)], - socket_config: crate::SocketConfig { - recv_timeout: Some(Duration::from_millis(450)), - ..crate::SocketConfig::default() - }, - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::OpenTimeout) - )); - - let start = std::time::Instant::now(); - let mut buf = [0_u8; 1]; - assert!(client.socket.recv(&mut buf).is_err()); - assert!(start.elapsed() >= Duration::from_millis(350)); - server.join(); -} - -#[test] -fn strict_negotiation_accepts_identical_params() { - let config = ClientConfig::default(); - let params = params_from_config(&config).unwrap(); - assert!(validate_negotiated_params(¶ms, ¶ms, NegotiationPolicy::Strict).is_ok()); -} - -#[test] -fn strict_negotiation_rejects_changed_params() { - let config = ClientConfig::default(); - let requested = params_from_config(&config).unwrap(); - let mut returned = requested.clone(); - returned.dscp = 1; - assert!(matches!( - validate_negotiated_params(&requested, &returned, NegotiationPolicy::Strict), - Err(ClientError::NegotiationRejected { .. }) - )); -} - -#[test] -fn loose_negotiation_accepts_server_restricted_params() { - let config = ClientConfig::default(); - let requested = params_from_config(&config).unwrap(); - let mut returned = requested.clone(); - returned.duration_ns /= 2; - returned.length = 0; - assert!(validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose).is_ok()); -} - -#[test] -fn protocol_version_mismatch_fails() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.negotiation_policy = NegotiationPolicy::Loose; - let mut params = params_from_config(&config).unwrap(); - params.protocol_version = 2; - let server = open_success_server(params); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::ProtocolVersionMismatch { received: 2, .. }) - )); - server.join(); -} - -#[test] -fn server_rejection_fails_in_normal_mode() { - let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY | flags::FLAG_CLOSE, 0, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - }); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::ServerRejected) - )); - server.join(); -} - -#[test] -fn no_test_open_close_succeeds_on_open_reply_close() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = no_test_server(params.clone(), 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); - assert_eq!(negotiated.params, params); - assert_eq!(client.negotiated.as_ref(), Some(&negotiated)); - assert!(matches!( - client.close(ClientTimestamp::now()), - Err(ClientError::NotOpen) - )); - server.join(); -} - -#[test] -fn no_test_success_validates_params() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = no_test_server(params.clone(), 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); - assert_eq!(negotiated.params, params); - server.join(); -} - -#[test] -fn no_test_rejects_non_close_open_reply() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = open_success_server(params); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::UnexpectedNoTestReply) - )); - server.join(); -} - -#[test] -fn no_test_rejects_non_zero_token_with_close_reply() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = no_test_server(params, TOKEN); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::NonZeroNoTestToken { token: TOKEN }) - )); - server.join(); -} - -#[test] -fn no_test_strict_negotiation_rejects_changed_params() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let mut params = params_from_config(&config).unwrap(); - params.dscp = 1; - let server = no_test_server(params, 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::NegotiationRejected { .. }) - )); - server.join(); -} - -#[test] -fn no_test_loose_negotiation_accepts_restricted_params() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - config.negotiation_policy = NegotiationPolicy::Loose; - let mut params = params_from_config(&config).unwrap(); - params.duration_ns /= 2; - let server = no_test_server(params.clone(), 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); - assert_eq!(negotiated.params, params); - server.join(); -} - -#[test] -fn close_sends_one_close_packet_with_negotiated_token() { - let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - let _ = recv_request(&socket, &tx); - }); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - let events = client.close(ClientTimestamp::now()).unwrap(); - assert_eq!(events.len(), 1); - let packets: Vec<_> = server.rx.iter().take(2).collect(); - let close = &packets[1]; - assert_eq!(close[3], flags::FLAG_CLOSE); - assert_eq!(u64::from_le_bytes(close[4..12].try_into().unwrap()), TOKEN); - server.join(); -} - -#[test] -fn hmac_open_success() { - let key = b"secret".to_vec(); - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.hmac_key = Some(key.clone()); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (request, peer) = recv_request(&socket, &tx); - verify_hmac(&key, &request, HMAC_OFFSET).unwrap(); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&key)); - socket.send_to(&reply, peer).unwrap(); - }); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - server.join(); -} - -#[test] -fn hmac_open_rejects_missing_hmac() { - let key = b"secret".to_vec(); - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.hmac_key = Some(key); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - }); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::Protocol( - irtt_proto::ProtoError::HmacPresenceMismatch - )) - )); - server.join(); -} - -#[test] -fn hmac_open_rejects_bad_hmac() { - let key = b"secret".to_vec(); - let wrong_key = b"wrong".to_vec(); - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.hmac_key = Some(key); - let params = params_from_config(&config).unwrap(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&wrong_key)); - socket.send_to(&reply, peer).unwrap(); - }); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert!(matches!( - client.open(ClientTimestamp::now()), - Err(ClientError::Protocol(irtt_proto::ProtoError::BadHmac)) - )); - server.join(); -} - -#[test] -fn hmac_close_packet_includes_valid_hmac() { - let key = b"secret".to_vec(); - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.hmac_key = Some(key.clone()); - let params = params_from_config(&config).unwrap(); - let server_key = key.clone(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); - socket.send_to(&reply, peer).unwrap(); - let _ = recv_request(&socket, &tx); - }); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - client.close(ClientTimestamp::now()).unwrap(); - let packets: Vec<_> = server.rx.iter().take(2).collect(); - let close = &packets[1]; - assert_eq!(close[3], flags::FLAG_CLOSE | FLAG_HMAC); - verify_hmac(&key, close, HMAC_OFFSET).unwrap(); - assert_eq!( - u64::from_le_bytes(close[4 + HMAC_SIZE..12 + HMAC_SIZE].try_into().unwrap()), - TOKEN - ); - server.join(); -} - -#[test] -fn minimum_open_timeout_under_200ms_is_rejected() { - let config = ClientConfig { - open_timeouts: vec![Duration::from_millis(199)], - ..ClientConfig::default() - }; - assert!(matches!( - Client::connect(config), - Err(ClientError::OpenTimeoutTooSmall { .. }) - )); -} - -#[test] -fn empty_open_timeouts_is_rejected() { - let config = ClientConfig { - open_timeouts: vec![], - ..ClientConfig::default() - }; - assert!(matches!( - Client::connect(config), - Err(ClientError::NoOpenTimeouts) - )); -} diff --git a/crates/irtt-client/src/client/tests/hmac.rs b/crates/irtt-client/src/client/tests/hmac.rs new file mode 100644 index 0000000..2afcc8e --- /dev/null +++ b/crates/irtt-client/src/client/tests/hmac.rs @@ -0,0 +1,184 @@ +use super::*; + +#[test] +fn hmac_open_success() { + let key = b"secret".to_vec(); + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.hmac_key = Some(key.clone()); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (request, peer) = recv_request(&socket, &tx); + verify_hmac(&key, &request, HMAC_OFFSET).unwrap(); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&key)); + socket.send_to(&reply, peer).unwrap(); + }); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + server.join(); +} + +#[test] +fn hmac_open_rejects_missing_hmac() { + let key = b"secret".to_vec(); + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.hmac_key = Some(key); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + }); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::Protocol( + irtt_proto::ProtoError::HmacPresenceMismatch + )) + )); + server.join(); +} + +#[test] +fn hmac_open_rejects_bad_hmac() { + let key = b"secret".to_vec(); + let wrong_key = b"wrong".to_vec(); + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.hmac_key = Some(key); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&wrong_key)); + socket.send_to(&reply, peer).unwrap(); + }); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::Protocol(irtt_proto::ProtoError::BadHmac)) + )); + server.join(); +} + +#[test] +fn hmac_close_packet_includes_valid_hmac() { + let key = b"secret".to_vec(); + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.hmac_key = Some(key.clone()); + let params = params_from_config(&config).unwrap(); + let server_key = key.clone(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); + socket.send_to(&reply, peer).unwrap(); + let _ = recv_request(&socket, &tx); + }); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + client.close(ClientTimestamp::now()).unwrap(); + let packets: Vec<_> = server.rx.iter().take(2).collect(); + let close = &packets[1]; + assert_eq!(close[3], flags::FLAG_CLOSE | FLAG_HMAC); + verify_hmac(&key, close, HMAC_OFFSET).unwrap(); + assert_eq!( + u64::from_le_bytes(close[4 + HMAC_SIZE..12 + HMAC_SIZE].try_into().unwrap()), + TOKEN + ); + server.join(); +} + +#[test] +fn bad_hmac_reply_is_dropped() { + let key = b"secret".to_vec(); + let wrong_key = b"wrong".to_vec(); + let params = default_params(); + let server_key = key.clone(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); + socket.send_to(&reply, peer).unwrap(); + + socket + .set_read_timeout(Some(Duration::from_secs(2))) + .unwrap(); + let mut buf = [0_u8; 2048]; + if let Ok((size, _)) = socket.recv_from(&mut buf) { + tx.send(buf[..size].to_vec()).unwrap(); + let seq = u32::from_le_bytes( + buf[4 + HMAC_SIZE + 8..4 + HMAC_SIZE + 12] + .try_into() + .unwrap(), + ); + let ts = TimestampFields::default(); + let reply_packet = echo_reply_packet(TOKEN, seq, ¶ms, &ts, Some(&wrong_key)); + socket.send_to(&reply_packet, peer).unwrap(); + } + }); + let config = ClientConfig { + hmac_key: Some(key), + socket_config: crate::SocketConfig { + recv_timeout: Some(Duration::from_millis(200)), + ..Default::default() + }, + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + client.send_probe().unwrap(); + thread::sleep(Duration::from_millis(30)); + let events = client.recv_once().unwrap(); + assert_eq!(events.len(), 1); + assert!(matches!(&events[0], ClientEvent::Warning { .. })); + server.join(); +} + +#[test] +fn hmac_echo_request_reply_works() { + let key = b"testkey".to_vec(); + let params = default_params(); + let server_key = key.clone(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); + socket.send_to(&reply, peer).unwrap(); + + socket + .set_read_timeout(Some(Duration::from_secs(2))) + .unwrap(); + let mut buf = [0_u8; 2048]; + if let Ok((size, _)) = socket.recv_from(&mut buf) { + tx.send(buf[..size].to_vec()).unwrap(); + verify_hmac(&server_key, &buf[..size], HMAC_OFFSET).unwrap(); + let seq = u32::from_le_bytes( + buf[4 + HMAC_SIZE + 8..4 + HMAC_SIZE + 12] + .try_into() + .unwrap(), + ); + let ts = TimestampFields { + recv_mono: Some(100), + send_mono: Some(200), + ..Default::default() + }; + let reply_packet = echo_reply_packet(TOKEN, seq, ¶ms, &ts, Some(&server_key)); + socket.send_to(&reply_packet, peer).unwrap(); + } + }); + let config = ClientConfig { + hmac_key: Some(key), + socket_config: crate::SocketConfig { + recv_timeout: Some(Duration::from_millis(200)), + ..Default::default() + }, + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + client.send_probe().unwrap(); + thread::sleep(Duration::from_millis(50)); + let events = client.recv_once().unwrap(); + assert_eq!(events.len(), 1); + assert!(matches!(&events[0], ClientEvent::EchoReply { .. })); + server.join(); +} diff --git a/crates/irtt-client/src/client/tests/mod.rs b/crates/irtt-client/src/client/tests/mod.rs index 53964eb..e933994 100644 --- a/crates/irtt-client/src/client/tests/mod.rs +++ b/crates/irtt-client/src/client/tests/mod.rs @@ -9,5 +9,10 @@ use std::{thread, time::SystemTime}; mod support; use support::*; -mod config_open; +mod close; +mod config; +mod hmac; +mod negotiation; +mod no_test; +mod open; mod probes_replies; diff --git a/crates/irtt-client/src/client/tests/negotiation.rs b/crates/irtt-client/src/client/tests/negotiation.rs new file mode 100644 index 0000000..ef0525f --- /dev/null +++ b/crates/irtt-client/src/client/tests/negotiation.rs @@ -0,0 +1,30 @@ +use super::*; + +#[test] +fn strict_negotiation_accepts_identical_params() { + let config = ClientConfig::default(); + let params = params_from_config(&config).unwrap(); + assert!(validate_negotiated_params(¶ms, ¶ms, NegotiationPolicy::Strict).is_ok()); +} + +#[test] +fn strict_negotiation_rejects_changed_params() { + let config = ClientConfig::default(); + let requested = params_from_config(&config).unwrap(); + let mut returned = requested.clone(); + returned.dscp = 1; + assert!(matches!( + validate_negotiated_params(&requested, &returned, NegotiationPolicy::Strict), + Err(ClientError::NegotiationRejected { .. }) + )); +} + +#[test] +fn loose_negotiation_accepts_server_restricted_params() { + let config = ClientConfig::default(); + let requested = params_from_config(&config).unwrap(); + let mut returned = requested.clone(); + returned.duration_ns /= 2; + returned.length = 0; + assert!(validate_negotiated_params(&requested, &returned, NegotiationPolicy::Loose).is_ok()); +} diff --git a/crates/irtt-client/src/client/tests/no_test.rs b/crates/irtt-client/src/client/tests/no_test.rs new file mode 100644 index 0000000..fb6a195 --- /dev/null +++ b/crates/irtt-client/src/client/tests/no_test.rs @@ -0,0 +1,125 @@ +use super::*; + +#[test] +fn no_test_open_close_succeeds_on_open_reply_close() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = no_test_server(params.clone(), 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); + assert_eq!(negotiated.params, params); + assert_eq!(client.negotiated.as_ref(), Some(&negotiated)); + assert!(matches!( + client.close(ClientTimestamp::now()), + Err(ClientError::NotOpen) + )); + server.join(); +} + +#[test] +fn no_test_success_validates_params() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = no_test_server(params.clone(), 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); + assert_eq!(negotiated.params, params); + server.join(); +} + +#[test] +fn no_test_rejects_non_close_open_reply() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = open_success_server(params); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::UnexpectedNoTestReply) + )); + server.join(); +} + +#[test] +fn no_test_rejects_non_zero_token_with_close_reply() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = no_test_server(params, TOKEN); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::NonZeroNoTestToken { token: TOKEN }) + )); + server.join(); +} + +#[test] +fn no_test_strict_negotiation_rejects_changed_params() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let mut params = params_from_config(&config).unwrap(); + params.dscp = 1; + let server = no_test_server(params, 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::NegotiationRejected { .. }) + )); + server.join(); +} + +#[test] +fn no_test_loose_negotiation_accepts_restricted_params() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + config.negotiation_policy = NegotiationPolicy::Loose; + let mut params = params_from_config(&config).unwrap(); + params.duration_ns /= 2; + let server = no_test_server(params.clone(), 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + let negotiated = assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); + assert_eq!(negotiated.params, params); + server.join(); +} + +#[test] +fn send_probe_fails_after_no_test_completed() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = no_test_server(params, 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); + assert!(matches!( + client.send_probe(), + Err(ClientError::AlreadyCompleted) + )); + server.join(); +} + +#[test] +fn open_fails_after_no_test_completed() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.run_mode = RunMode::NoTest; + let params = params_from_config(&config).unwrap(); + let server = no_test_server(params, 0); + config.server_addr = server.addr.to_string(); + let mut client = Client::connect(config).unwrap(); + assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::AlreadyCompleted) + )); + server.join(); +} diff --git a/crates/irtt-client/src/client/tests/open.rs b/crates/irtt-client/src/client/tests/open.rs new file mode 100644 index 0000000..1fea096 --- /dev/null +++ b/crates/irtt-client/src/client/tests/open.rs @@ -0,0 +1,132 @@ +use super::*; + +#[test] +fn address_resolution_connects_to_local_fake_server() { + let server = start_fake_server(|socket, tx| { + let _ = recv_request(&socket, &tx); + }); + let client = Client::connect(default_test_config(server.addr)).unwrap(); + client.socket.send(b"ping").unwrap(); + assert_eq!(server.rx.recv().unwrap(), b"ping"); + server.join(); +} + +#[test] +fn successful_open_handshake() { + let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + let params = params_from_config(&config).unwrap(); + let server = open_success_server(params.clone()); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + + let negotiated = assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + assert_eq!(negotiated.params, params); + assert!(matches!(client.phase, ClientPhase::Open { token: TOKEN })); + server.join(); +} + +#[test] +fn open_fails_when_already_open() { + let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + let params = params_from_config(&config).unwrap(); + let server = open_success_server(params); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::AlreadyOpen) + )); + server.join(); +} + +#[test] +fn open_retries_after_first_timeout() { + let server = start_fake_server(|socket, tx| { + let (first, _) = recv_request(&socket, &tx); + let (_, peer) = recv_request(&socket, &tx); + let params = Params::decode(&first[4..]).unwrap(); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + assert_eq!(first[3] & FLAG_OPEN, FLAG_OPEN); + socket.send_to(&reply, peer).unwrap(); + }); + let config = ClientConfig { + open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(500)], + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + let outcome = client.open(ClientTimestamp::now()).unwrap(); + assert_open_started(outcome); + assert_eq!(server.rx.iter().take(2).count(), 2); + server.join(); +} + +#[test] +fn open_timeout_after_all_timeouts() { + let server = timeout_server(Duration::from_millis(700)); + let config = ClientConfig { + open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(200)], + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::OpenTimeout) + )); + assert_eq!(server.rx.iter().take(2).count(), 2); + server.join(); +} + +#[test] +fn open_restores_configured_read_timeout_after_timeout() { + let server = timeout_server(Duration::from_millis(700)); + let config = ClientConfig { + open_timeouts: vec![Duration::from_millis(200)], + socket_config: crate::SocketConfig { + recv_timeout: Some(Duration::from_millis(450)), + ..crate::SocketConfig::default() + }, + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::OpenTimeout) + )); + + let start = std::time::Instant::now(); + let mut buf = [0_u8; 1]; + assert!(client.socket.recv(&mut buf).is_err()); + assert!(start.elapsed() >= Duration::from_millis(350)); + server.join(); +} + +#[test] +fn protocol_version_mismatch_fails() { + let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + config.negotiation_policy = NegotiationPolicy::Loose; + let mut params = params_from_config(&config).unwrap(); + params.protocol_version = 2; + let server = open_success_server(params); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::ProtocolVersionMismatch { received: 2, .. }) + )); + server.join(); +} + +#[test] +fn server_rejection_fails_in_normal_mode() { + let config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); + let params = params_from_config(&config).unwrap(); + let server = start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY | flags::FLAG_CLOSE, 0, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + }); + let mut client = Client::connect(default_test_config(server.addr)).unwrap(); + assert!(matches!( + client.open(ClientTimestamp::now()), + Err(ClientError::ServerRejected) + )); + server.join(); +} diff --git a/crates/irtt-client/src/client/tests/probes_replies.rs b/crates/irtt-client/src/client/tests/probes_replies.rs index be11a8d..ef58c6c 100644 --- a/crates/irtt-client/src/client/tests/probes_replies.rs +++ b/crates/irtt-client/src/client/tests/probes_replies.rs @@ -8,72 +8,6 @@ fn send_probe_fails_before_open() { server.join(); } -#[test] -fn send_probe_fails_after_no_test_completed() { - let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1))); - config.run_mode = RunMode::NoTest; - let params = params_from_config(&config).unwrap(); - let server = no_test_server(params, 0); - config.server_addr = server.addr.to_string(); - let mut client = Client::connect(config).unwrap(); - assert_no_test_completed(client.open(ClientTimestamp::now()).unwrap()); - assert!(matches!( - client.send_probe(), - Err(ClientError::AlreadyCompleted) - )); - server.join(); -} - -#[test] -fn send_probe_fails_after_close() { - let params = default_params(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - socket - .set_read_timeout(Some(Duration::from_secs(2))) - .unwrap(); - loop { - let mut buf = [0_u8; 512]; - match socket.recv_from(&mut buf) { - Ok((size, _)) => { - tx.send(buf[..size].to_vec()).unwrap(); - } - Err(_) => break, - } - } - }); - let mut client = Client::connect(default_test_config(server.addr)).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - client.close(ClientTimestamp::now()).unwrap(); - assert!(matches!( - client.send_probe(), - Err(ClientError::AlreadyClosed) - )); - server.join(); -} - -fn silent_open_server(params: Params) -> FakeServer { - start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); - socket.send_to(&reply, peer).unwrap(); - socket - .set_read_timeout(Some(Duration::from_secs(5))) - .unwrap(); - loop { - let mut buf = [0_u8; 2048]; - match socket.recv_from(&mut buf) { - Ok((size, _)) => { - tx.send(buf[..size].to_vec()).unwrap(); - } - Err(_) => break, - } - } - }) -} - #[test] fn send_probe_sends_valid_echo_request() { let params = default_params(); @@ -497,51 +431,6 @@ fn wrong_token_reply_is_dropped() { server.join(); } -#[test] -fn bad_hmac_reply_is_dropped() { - let key = b"secret".to_vec(); - let wrong_key = b"wrong".to_vec(); - let params = default_params(); - let server_key = key.clone(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); - socket.send_to(&reply, peer).unwrap(); - - socket - .set_read_timeout(Some(Duration::from_secs(2))) - .unwrap(); - let mut buf = [0_u8; 2048]; - if let Ok((size, _)) = socket.recv_from(&mut buf) { - tx.send(buf[..size].to_vec()).unwrap(); - let seq = u32::from_le_bytes( - buf[4 + HMAC_SIZE + 8..4 + HMAC_SIZE + 12] - .try_into() - .unwrap(), - ); - let ts = TimestampFields::default(); - let reply_packet = echo_reply_packet(TOKEN, seq, ¶ms, &ts, Some(&wrong_key)); - socket.send_to(&reply_packet, peer).unwrap(); - } - }); - let config = ClientConfig { - hmac_key: Some(key), - socket_config: crate::SocketConfig { - recv_timeout: Some(Duration::from_millis(200)), - ..Default::default() - }, - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - client.send_probe().unwrap(); - thread::sleep(Duration::from_millis(30)); - let events = client.recv_once().unwrap(); - assert_eq!(events.len(), 1); - assert!(matches!(&events[0], ClientEvent::Warning { .. })); - server.join(); -} - #[test] fn duplicate_reply_emits_duplicate_event() { let params = default_params(); @@ -790,31 +679,6 @@ fn late_reply_after_timeout_preserves_measurement_metadata() { server.join(); } -#[test] -fn close_clears_timed_out_metadata() { - let params = default_params(); - let server = silent_open_server(params); - let config = ClientConfig { - probe_timeout: Duration::from_millis(40), - socket_config: crate::SocketConfig { - recv_timeout: Some(Duration::from_millis(50)), - ..Default::default() - }, - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - - client.send_probe().unwrap(); - thread::sleep(Duration::from_millis(60)); - client.poll_timeouts(ClientTimestamp::now()).unwrap(); - assert_eq!(client.session.as_ref().unwrap().timed_out.len(), 1); - - client.close(ClientTimestamp::now()).unwrap(); - assert!(client.session.is_none()); - server.join(); -} - #[test] fn pending_map_bounded() { let params = Params { @@ -901,55 +765,6 @@ fn minimal_negotiated_layout_works() { server.join(); } -#[test] -fn hmac_echo_request_reply_works() { - let key = b"testkey".to_vec(); - let params = default_params(); - let server_key = key.clone(); - let server = start_fake_server(move |socket, tx| { - let (_, peer) = recv_request(&socket, &tx); - let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(&server_key)); - socket.send_to(&reply, peer).unwrap(); - - socket - .set_read_timeout(Some(Duration::from_secs(2))) - .unwrap(); - let mut buf = [0_u8; 2048]; - if let Ok((size, _)) = socket.recv_from(&mut buf) { - tx.send(buf[..size].to_vec()).unwrap(); - verify_hmac(&server_key, &buf[..size], HMAC_OFFSET).unwrap(); - let seq = u32::from_le_bytes( - buf[4 + HMAC_SIZE + 8..4 + HMAC_SIZE + 12] - .try_into() - .unwrap(), - ); - let ts = TimestampFields { - recv_mono: Some(100), - send_mono: Some(200), - ..Default::default() - }; - let reply_packet = echo_reply_packet(TOKEN, seq, ¶ms, &ts, Some(&server_key)); - socket.send_to(&reply_packet, peer).unwrap(); - } - }); - let config = ClientConfig { - hmac_key: Some(key), - socket_config: crate::SocketConfig { - recv_timeout: Some(Duration::from_millis(200)), - ..Default::default() - }, - ..default_test_config(server.addr) - }; - let mut client = Client::connect(config).unwrap(); - assert_open_started(client.open(ClientTimestamp::now()).unwrap()); - client.send_probe().unwrap(); - thread::sleep(Duration::from_millis(50)); - let events = client.recv_once().unwrap(); - assert_eq!(events.len(), 1); - assert!(matches!(&events[0], ClientEvent::EchoReply { .. })); - server.join(); -} - #[test] fn late_reply_with_pending_preserves_rtt() { let params = default_params(); diff --git a/crates/irtt-client/src/client/tests/support.rs b/crates/irtt-client/src/client/tests/support.rs index b5ee4d5..9f847c2 100644 --- a/crates/irtt-client/src/client/tests/support.rs +++ b/crates/irtt-client/src/client/tests/support.rs @@ -176,6 +176,26 @@ pub(super) fn recv_request_timeout( } } +pub(super) fn silent_open_server(params: Params) -> FakeServer { + start_fake_server(move |socket, tx| { + let (_, peer) = recv_request(&socket, &tx); + let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None); + socket.send_to(&reply, peer).unwrap(); + socket + .set_read_timeout(Some(Duration::from_secs(5))) + .unwrap(); + loop { + let mut buf = [0_u8; 2048]; + match socket.recv_from(&mut buf) { + Ok((size, _)) => { + tx.send(buf[..size].to_vec()).unwrap(); + } + Err(_) => break, + } + } + }) +} + pub(super) fn echo_server(params: Params) -> FakeServer { start_fake_server(move |socket, tx| { let (_, peer) = recv_request(&socket, &tx);