test(client): add integration tests for packet length and server fill

Verify that the client correctly handles custom packet lengths and server fill negotiation to ensure protocol compliance and support for larger MTUs. Add validation to reject empty server fill strings during configuration.
This commit is contained in:
Nils Andreas Svee committed 2026-05-05 00:04:12 +07:00
1 parent 0262a2af0c
commit 36ca426ff5
6 files changed
+384 -2

No files matched your search

+214
View File
@@ -0,0 +1,214 @@
mod support;
use irtt_client::{
Client, ClientConfig, ClientError, ClientEvent, ClientTimestamp, NegotiationPolicy,
};
use irtt_proto::{echo_packet_len, Params, TimestampFields};
use support::{
config_for_params, default_params, run_one_probe, run_one_probe_with_config, server_fill,
start_open_server, OneProbeRun, ServerObservation,
};
fn open_observation_params(run: &OneProbeRun) -> &Params {
match &run.observations[0] {
ServerObservation::Open { params, .. } => params,
other => panic!("expected Open observation, got {other:?}"),
}
}
fn echo_observation(run: &OneProbeRun) -> (usize, bool, u64, u32) {
match run.observations[1] {
ServerObservation::Echo {
len,
hmac,
token,
sequence,
} => (len, hmac, token, sequence),
ref other => panic!("expected Echo observation, got {other:?}"),
}
}
// ─── Config mapping ───
#[test]
fn server_fill_none_produces_no_fill_in_open_params() {
let params = default_params();
let run = run_one_probe(params, TimestampFields::default());
assert!(open_observation_params(&run).server_fill.is_none());
}
#[test]
fn server_fill_short_value_maps_into_open_params() {
let mut params = default_params();
params.server_fill = server_fill("abc");
let run = run_one_probe(params, TimestampFields::default());
assert_eq!(
open_observation_params(&run)
.server_fill
.as_ref()
.map(|f| f.value.as_str()),
Some("abc")
);
}
#[test]
fn server_fill_32_byte_value_maps_into_open_params() {
let mut params = default_params();
let fill = "0123456789abcdef0123456789abcdef";
params.server_fill = server_fill(fill);
let run = run_one_probe(params, TimestampFields::default());
assert_eq!(
open_observation_params(&run)
.server_fill
.as_ref()
.map(|f| f.value.as_str()),
Some(fill)
);
}
#[test]
fn server_fill_over_32_bytes_rejected_at_config_boundary() {
let config = ClientConfig {
server_fill: Some("0123456789abcdef0123456789abcdefx".to_owned()),
..ClientConfig::default()
};
assert!(matches!(
Client::connect(config),
Err(ClientError::InvalidConfig { .. })
));
}
#[test]
fn server_fill_empty_rejected_at_config_boundary() {
let config = ClientConfig {
server_fill: Some("".to_owned()),
..ClientConfig::default()
};
assert!(matches!(
Client::connect(config),
Err(ClientError::InvalidConfig { .. })
));
}
// ─── Open negotiation ───
#[test]
fn server_fill_negotiated_params_reflect_accepted_value() {
let mut params = default_params();
params.server_fill = server_fill("rand");
let run = run_one_probe(params.clone(), TimestampFields::default());
assert_eq!(run.negotiated.params.server_fill, params.server_fill);
}
#[test]
fn server_fill_strict_rejects_removed_fill() {
let mut requested = default_params();
requested.server_fill = server_fill("rand");
let mut returned = requested.clone();
returned.server_fill = None;
let server = start_open_server(returned, None);
let mut client = Client::connect(config_for_params(server.addr, &requested)).unwrap();
assert!(matches!(
client.open(ClientTimestamp::now()),
Err(ClientError::NegotiationRejected { .. })
));
server.join();
}
#[test]
fn server_fill_strict_rejects_different_fill_value() {
let mut requested = default_params();
requested.server_fill = server_fill("abc");
let mut returned = requested.clone();
returned.server_fill = server_fill("xyz");
let server = start_open_server(returned, None);
let mut client = Client::connect(config_for_params(server.addr, &requested)).unwrap();
assert!(matches!(
client.open(ClientTimestamp::now()),
Err(ClientError::NegotiationRejected { .. })
));
server.join();
}
#[test]
fn server_fill_strict_rejects_unexpected_fill_from_server() {
let requested = default_params();
let mut returned = requested.clone();
returned.server_fill = server_fill("unexpected");
let server = start_open_server(returned, None);
let mut client = Client::connect(config_for_params(server.addr, &requested)).unwrap();
assert!(matches!(
client.open(ClientTimestamp::now()),
Err(ClientError::NegotiationRejected { .. })
));
server.join();
}
#[test]
fn server_fill_loose_allows_server_to_remove_fill() {
let mut requested = default_params();
requested.server_fill = server_fill("rand");
let mut returned = requested.clone();
returned.server_fill = None;
let requested_for_config = requested.clone();
let run = run_one_probe_with_config(returned, TimestampFields::default(), None, |addr| {
ClientConfig {
negotiation_policy: NegotiationPolicy::Loose,
..config_for_params(addr, &requested_for_config)
}
});
assert_eq!(
open_observation_params(&run).server_fill,
requested.server_fill
);
assert_eq!(run.negotiated.params.server_fill, None);
}
// ─── Echo behavior ───
#[test]
fn server_fill_echo_reply_decoded_with_fill_configured() {
let mut params = default_params();
params.server_fill = server_fill("rand");
let run = run_one_probe(params, TimestampFields::default());
assert!(matches!(run.reply, ClientEvent::EchoReply { .. }));
}
// server_fill is a negotiation/config parameter only; it does not affect
// client-observable reply payload layout. The client sends server_fill in
// the open request and expects the server to use it for reply payload
// content, but the client's decode/echo layout logic is independent of
// server_fill. Only the length parameter expands the packet beyond the
// protocol header.
#[test]
fn server_fill_does_not_affect_client_side_packet_layout() {
let mut params_with_fill = default_params();
params_with_fill.server_fill = server_fill("rand");
let params_without_fill = default_params();
let run_with = run_one_probe(params_with_fill, TimestampFields::default());
let run_without = run_one_probe(params_without_fill, TimestampFields::default());
let (echo_len_with, _, _, _) = echo_observation(&run_with);
let (echo_len_without, _, _, _) = echo_observation(&run_without);
assert_eq!(echo_len_with, echo_len_without);
assert_eq!(
echo_packet_len(false, &run_with.negotiated.params),
echo_len_with
);
assert_eq!(
echo_packet_len(false, &run_without.negotiated.params),
echo_len_without
);
}