Files
irtt-rs/crates/irtt-stats/tests/common/mod.rs
T
Nils Andreas Svee 0977db1fe2 docs(stats): clarify server receive-window semantics
An audit of the server-reported receive window found no defect in the
statistics pipeline: the bitmap is decoded raw, stored raw, never
interpreted, and never feeds the directional loss estimates, which use
the cumulative server receive count.

The public rustdoc was silent on all of that, leaving consumers of
PacketCounts free to read a window of 0x1 as a 63-packet loss burst.
Document the field's contract, name the count as the only server input
to LossStats, and cover the window-only case with a regression test.
2026-08-11 23:21:58 +07:00

144 lines
4.1 KiB
Rust

use std::time::{Duration, Instant, UNIX_EPOCH};
use irtt_client::{
ClientEvent, ClientTimestamp, OneWayDelaySample, PacketMeta, ReceivedStatsSample, RttSample,
ServerTiming, SignedDuration,
};
pub fn ts(ms: u64) -> ClientTimestamp {
ClientTimestamp {
mono: Instant::now() + Duration::from_millis(ms),
wall: UNIX_EPOCH + Duration::from_millis(ms),
}
}
pub fn adjusted_rtt(raw_ms: u64, adjusted_ms: i128) -> RttSample {
let adjusted = SignedDuration::from_nanos(adjusted_ms * 1_000_000);
RttSample {
raw: Duration::from_millis(raw_ms),
adjusted: Some(adjusted),
effective: adjusted,
}
}
pub fn unadjusted_rtt(raw_ms: u64) -> RttSample {
let raw = Duration::from_millis(raw_ms);
RttSample {
raw,
adjusted: None,
effective: SignedDuration::from_duration(raw),
}
}
pub fn sent(seq: u32, sent_at: ClientTimestamp) -> ClientEvent {
ClientEvent::EchoSent {
seq,
remote: "127.0.0.1:2112".parse().unwrap(),
scheduled_at: sent_at.mono,
sent_at,
bytes: 32,
send_call: Duration::from_micros(10),
timer_error: Duration::from_micros(2),
}
}
pub fn adjusted_reply(seq: u32, raw_ms: u64, adjusted_ms: i128) -> ClientEvent {
reply_with_rtt(seq, raw_ms, adjusted_rtt(raw_ms, adjusted_ms))
}
pub fn unadjusted_reply(seq: u32, raw_ms: u64) -> ClientEvent {
reply_with_rtt(seq, raw_ms, unadjusted_rtt(raw_ms))
}
fn reply_with_rtt(seq: u32, raw_ms: u64, rtt: RttSample) -> ClientEvent {
let sent_at = ts(seq as u64 * 10);
let received_at = ClientTimestamp {
mono: sent_at.mono + Duration::from_millis(raw_ms),
wall: sent_at.wall + Duration::from_millis(raw_ms),
};
ClientEvent::EchoReply {
seq,
remote: "127.0.0.1:2112".parse().unwrap(),
sent_at,
received_at,
rtt,
server_timing: Some(ServerTiming {
receive_wall_ns: Some(unix_time_ns_after_epoch(sent_at.wall) as i64 + 1_000_000),
receive_mono_ns: Some(seq as i64 * 10_000_000 + 1_000_000),
send_wall_ns: Some(unix_time_ns_after_epoch(sent_at.wall) as i64 + 2_000_000),
send_mono_ns: Some(seq as i64 * 10_000_000 + 2_000_000),
midpoint_wall_ns: None,
midpoint_mono_ns: None,
processing: Some(Duration::from_millis(1)),
}),
one_way: Some(OneWayDelaySample {
client_to_server: Some(SignedDuration::from_nanos(1_000_000)),
server_to_client: Some(SignedDuration::from_nanos(2_000_000)),
}),
received_stats: Some(ReceivedStatsSample {
count: Some(seq + 1),
window: Some(0xff),
}),
bytes: 64,
packet_meta: PacketMeta::default(),
}
}
/// Builds a normal reply carrying exactly the supplied server received stats.
pub fn reply_with_received_stats(
seq: u32,
raw_ms: u64,
count: Option<u32>,
window: Option<u64>,
) -> ClientEvent {
let mut event = unadjusted_reply(seq, raw_ms);
let ClientEvent::EchoReply { received_stats, .. } = &mut event else {
unreachable!();
};
*received_stats =
(count.is_some() || window.is_some()).then_some(ReceivedStatsSample { count, window });
event
}
pub fn unadjusted_late_reply(seq: u32, raw_ms: u64) -> ClientEvent {
late_reply_from(unadjusted_reply(seq, raw_ms))
}
fn late_reply_from(reply: ClientEvent) -> ClientEvent {
let ClientEvent::EchoReply {
seq,
remote,
sent_at,
received_at,
rtt,
server_timing,
one_way,
received_stats,
bytes,
packet_meta,
..
} = reply
else {
unreachable!();
};
ClientEvent::LateReply {
seq,
highest_seen: seq + 1,
remote,
sent_at: Some(sent_at),
received_at,
rtt: Some(rtt),
server_timing,
one_way,
received_stats,
bytes,
packet_meta,
}
}
fn unix_time_ns_after_epoch(time: std::time::SystemTime) -> i128 {
time.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos() as i128)
.unwrap()
}