use std::net::SocketAddr;
use std::sync::OnceLock;
use std::time::{Duration, Instant};
use crate::constants;
use crate::core::connection::mobility::Contested;
use crate::core::connection::testfix::{
Pair, Solo, Wire, a_addr, b_addr, drain, put, t0, v4, write_all,
};
use crate::core::connection::timers::TimerKind;
use crate::core::connection::{ConnEvent, ConnOutput};
use crate::error::{ConfigError, ConnectionLost};
fn origin() -> Instant {
static ORIGIN: OnceLock<Instant> = OnceLock::new();
*ORIGIN.get_or_init(t0)
}
fn c_addr() -> SocketAddr {
v4(9, 41_000)
}
fn ack_frame(largest: u64) -> Vec<u8> {
let mut out = Vec::new();
put(&mut out, constants::FRAME_ACK);
put(&mut out, largest);
put(&mut out, 0); put(&mut out, 0); put(&mut out, 0); out
}
fn ack_range_frame(largest: u64, first_range: u64) -> Vec<u8> {
let mut out = Vec::new();
put(&mut out, constants::FRAME_ACK);
put(&mut out, largest);
put(&mut out, 0); put(&mut out, 0); put(&mut out, first_range);
out
}
fn count_moved(d: &crate::core::connection::testfix::Drained) -> usize {
d.count_events(|e| matches!(e, ConnEvent::AddressMoved { .. }))
}
fn count_contested(d: &crate::core::connection::testfix::Drained) -> usize {
d.count_events(|e| matches!(e, ConnEvent::Contested))
}
fn count_cleared(d: &crate::core::connection::testfix::Drained) -> usize {
d.count_events(|e| matches!(e, ConnEvent::ContestCleared))
}
#[test]
fn the_beacon_band_is_one_second_inclusive_to_dead_timeout_exclusive() {
let mut solo = Solo::installed_at(origin());
let now = origin();
for rejected_low in [
Duration::ZERO,
Duration::from_millis(1),
Duration::from_millis(500),
Duration::from_millis(999),
] {
assert_eq!(
solo.conn.set_persistent_keepalive(now, Some(rejected_low)),
Err(ConfigError::KeepaliveTooShort),
"{rejected_low:?} is below the 1 s floor"
);
}
assert_eq!(
solo.conn
.set_persistent_keepalive(now, Some(constants::PERSISTENT_KEEPALIVE_MIN)),
Ok(()),
"the floor is INCLUSIVE (ruling 42)"
);
assert_eq!(
solo.conn.persistent_keepalive(),
Some(Duration::from_secs(1))
);
for accepted in [
Duration::from_millis(1_001),
Duration::from_secs(10),
constants::DEAD_TIMEOUT - Duration::from_millis(1),
] {
assert_eq!(
solo.conn.set_persistent_keepalive(now, Some(accepted)),
Ok(())
);
assert_eq!(solo.conn.persistent_keepalive(), Some(accepted));
}
for rejected_high in [
constants::DEAD_TIMEOUT,
Duration::from_secs(30),
Duration::MAX,
] {
assert_eq!(
solo.conn.set_persistent_keepalive(now, Some(rejected_high)),
Err(ConfigError::KeepaliveTooLong),
"{rejected_high:?} is at or above DEAD_TIMEOUT (ruling 40)"
);
}
assert_eq!(solo.conn.set_persistent_keepalive(now, None), Ok(()));
assert_eq!(solo.conn.persistent_keepalive(), None);
}
#[test]
fn a_rejected_interval_leaves_the_previous_one_untouched() {
let mut solo = Solo::installed_at(origin());
let now = origin();
solo.conn
.set_persistent_keepalive(now, Some(Duration::from_secs(5)))
.expect("5 s is inside the band");
assert!(
solo.conn
.set_persistent_keepalive(now, Some(Duration::from_secs(60)))
.is_err()
);
assert_eq!(
solo.conn.persistent_keepalive(),
Some(Duration::from_secs(5)),
"no clamp to DEAD_TIMEOUT − ε"
);
assert!(
solo.conn
.set_persistent_keepalive(now, Some(Duration::from_millis(10)))
.is_err()
);
assert_eq!(
solo.conn.persistent_keepalive(),
Some(Duration::from_secs(5)),
"no clamp to the 1 s floor"
);
}
#[test]
fn the_passive_keepalive_arms_only_after_a_receive() {
let mut solo = Solo::installed_at(origin());
assert_eq!(
solo.conn.timer(TimerKind::Keepalive),
None,
"S == R at install: R > S is false"
);
let now = origin() + Duration::from_secs(1);
let _ = solo.deliver(now, &[constants::FRAME_PING as u8]);
assert_eq!(
solo.conn.timer(TimerKind::Keepalive),
Some(origin() + constants::KEEPALIVE_TIMEOUT),
"armed at S + KEEPALIVE_TIMEOUT, where S is still the install"
);
}
#[test]
fn the_passive_keepalive_sends_the_empty_plaintext_and_then_disarms() {
let mut solo = Solo::installed_at(origin());
let recv_at = origin() + Duration::from_secs(1);
let _ = solo.deliver(recv_at, &[constants::FRAME_PING as u8]);
let fires = origin() + constants::KEEPALIVE_TIMEOUT;
solo.conn.handle_timeout(fires);
let d = drain(&mut solo.conn);
let transmits = d.transmits();
assert_eq!(transmits.len(), 1, "one keepalive: {:?}", d.outs);
assert_eq!(
transmits[0].data.len(),
constants::DATA_HEADER_LEN + constants::AEAD_TAG_LEN,
"§3.4's empty plaintext is a 30-byte datagram"
);
assert_eq!(
solo.conn.timer(TimerKind::Keepalive),
None,
"S has moved to `now`, so R > S is false again"
);
}
#[test]
fn a_quiet_send_neither_advances_s_nor_suppresses_the_keepalive() {
let mut solo = Solo::installed_at(origin());
let recv_at = origin() + Duration::from_secs(2);
let d = solo.deliver(recv_at, &[constants::FRAME_PING as u8]);
assert!(
!d.transmits().is_empty(),
"the PING owes an ACK, and §12.4 sends it"
);
assert_eq!(
solo.conn.timer(TimerKind::Keepalive),
Some(origin() + constants::KEEPALIVE_TIMEOUT),
"the quiet ACK did not move S off the install instant"
);
}
#[test]
fn the_beacon_fires_without_a_receive_and_re_arms_itself() {
let mut solo = Solo::installed_at(origin());
solo.conn
.set_persistent_keepalive(origin(), Some(Duration::from_secs(3)))
.expect("3 s is inside the band");
assert_eq!(
solo.conn.timer(TimerKind::PersistentKeepalive),
Some(origin() + Duration::from_secs(3))
);
let at = origin() + Duration::from_secs(3);
solo.conn.handle_timeout(at);
let d = drain(&mut solo.conn);
assert_eq!(
d.transmits().len(),
1,
"the beacon fires with no receive at all"
);
assert_eq!(
solo.conn.timer(TimerKind::PersistentKeepalive),
Some(at + Duration::from_secs(3)),
"re-armed from the marking send it just made"
);
}
#[test]
fn a_beacon_only_connection_still_dies_at_the_dead_timeout() {
let mut solo = Solo::installed_at(origin());
solo.conn
.set_persistent_keepalive(origin(), Some(Duration::from_secs(2)))
.expect("2 s is inside the band");
let mut beacons = 0usize;
let mut death = None;
for step in 1..=30u64 {
let at = origin() + Duration::from_secs(step);
solo.conn.handle_timeout(at);
let d = drain(&mut solo.conn);
beacons += d.transmits().len();
if let Some(reason) = d.closed() {
death = Some((at, reason));
break;
}
}
assert!(beacons >= 10, "the beacon did fire repeatedly: {beacons}");
assert_eq!(
death,
Some((origin() + constants::DEAD_TIMEOUT, ConnectionLost::TimedOut)),
"arming enables death, never defers it (ruling 40)"
);
}
#[test]
fn an_authenticated_fresh_packet_from_a_new_source_roams() {
let mut solo = Solo::installed_at(origin());
assert_eq!(solo.conn.remote_address(), Some(a_addr()));
assert_eq!(solo.conn.path_generation(), 0);
let now = origin() + Duration::from_millis(10);
let d = solo.deliver_from(now, c_addr(), &[constants::FRAME_PING as u8]);
assert_eq!(
solo.conn.remote_address(),
Some(c_addr()),
"the anchor moved"
);
assert_eq!(solo.conn.path_generation(), 1, "one committed roam");
assert_eq!(count_moved(&d), 1, "one AddressMoved: {:?}", d.outs);
assert!(
d.outs.iter().any(|o| matches!(
o,
ConnOutput::Event(ConnEvent::AddressMoved { from, to })
if *from == a_addr() && *to == c_addr()
)),
"it carries the old and the new anchor: {:?}",
d.outs
);
assert!(
d.transmits().iter().all(|t| t.to == c_addr()),
"everything after the roam goes to the new anchor"
);
}
#[test]
fn a_keepalive_from_a_new_source_roams_and_its_event_reaches_the_drain() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let d = solo.deliver_from(now, c_addr(), &[]);
assert_eq!(solo.conn.remote_address(), Some(c_addr()));
assert_eq!(
count_moved(&d),
1,
"the event is in *this* drain: {:?}",
d.outs
);
}
#[test]
fn neither_a_forgery_nor_a_replay_ever_moves_the_anchor() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let mut forged = crate::core::connection::testfix::data_header(0xdead_beef, 0);
forged.extend_from_slice(&[0u8; constants::AEAD_TAG_LEN]);
solo.conn.handle_datagram(now, c_addr(), &forged);
let _ = drain(&mut solo.conn);
assert_eq!(
solo.conn.remote_address(),
Some(a_addr()),
"unauthenticated bytes move nothing"
);
assert_eq!(solo.conn.path_generation(), 0);
let dgram = solo.peer.seal(&[constants::FRAME_PING as u8]);
solo.conn.handle_datagram(now, a_addr(), &dgram);
let _ = drain(&mut solo.conn);
solo.conn.handle_datagram(now, c_addr(), &dgram);
let d = drain(&mut solo.conn);
assert_eq!(
solo.conn.remote_address(),
Some(a_addr()),
"§7.2: no replayed packet ever moves the endpoint"
);
assert_eq!(solo.conn.path_generation(), 0);
assert_eq!(count_moved(&d), 0);
}
#[test]
fn a_closing_connection_does_not_roam() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
solo.conn.close(now, 0, b"");
let _ = drain(&mut solo.conn);
assert!(
solo.conn.remote_address().is_some(),
"the linger keeps the session"
);
let later = now + Duration::from_millis(10);
let d = solo.deliver_from(later, c_addr(), &[constants::FRAME_PING as u8]);
assert_eq!(
solo.conn.remote_address(),
Some(a_addr()),
"the closing state keeps its anchor"
);
assert_eq!(solo.conn.path_generation(), 0);
assert_eq!(count_moved(&d), 0);
assert!(
d.transmits().iter().all(|t| t.to == a_addr()),
"§15.2's reply goes to the anchor, never to the triggering source"
);
}
#[test]
fn a_dialled_connection_starts_validated_and_a_roam_arms_the_budget() {
let mut solo = Solo::installed_at(origin());
assert_eq!(
solo.conn.amplification_budget(),
None,
"dialled ⇒ validated"
);
let now = origin() + Duration::from_millis(10);
let _ = solo.deliver_from(now, c_addr(), &[]);
let (spent, credited) = solo
.conn
.amplification_budget()
.expect("the roam arms the budget");
assert_eq!(
credited,
(constants::DATA_HEADER_LEN + constants::AEAD_TAG_LEN) as u64,
"the triggering packet — and only it — credits the received counter"
);
assert!(
spent <= constants::AMPLIFICATION_FACTOR * credited,
"the gate held: {spent} sent against 3 × {credited}"
);
}
#[test]
fn an_accepted_connection_is_armed_from_its_msg1_anchor() {
let (_, sa, sb) = Solo::connecting();
let conn = crate::core::Connection::established(
origin(),
[0x11u8; 32],
sb,
crate::core::Role::Responder,
);
let solo = Solo::around(conn, sa);
assert_eq!(
solo.conn.amplification_budget(),
Some((
constants::RESP_PACKET_LEN as u64,
constants::INIT_PACKET_LEN as u64
)),
"the msg1 credits the budget and the msg2 it provoked is charged to it"
);
assert!(
solo.conn.outstanding_challenge().is_some(),
"an armed budget owes a challenge to the address it is armed against"
);
}
#[test]
fn the_budget_holds_output_and_a_receive_releases_it() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let _ = solo.deliver_from(now, c_addr(), &[]);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
solo.conn
.write(now, r, &[7u8; 4_000])
.expect("the write is admitted into send state");
solo.conn.flush(now);
let d = drain(&mut solo.conn);
let first: u64 = d.transmits().iter().map(|t| t.data.len() as u64).sum();
assert!(
first <= constants::AMPLIFICATION_FACTOR * 30,
"held at 3 × 30 bytes, not sent whole: {first}"
);
let before = solo.conn.amplification_budget().expect("still unvalidated");
let _ = solo.deliver_from(now, c_addr(), &[constants::FRAME_PADDING as u8; 200]);
if let Some((_, credited)) = solo.conn.amplification_budget() {
assert!(
credited > before.1,
"an authenticated fresh packet credits the budget"
);
}
}
#[test]
fn a_replayed_packet_funds_no_budget() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let _ = solo.deliver_from(now, c_addr(), &[]);
let dgram = solo.peer.seal(&[constants::FRAME_PING as u8]);
solo.conn.handle_datagram(now, c_addr(), &dgram);
let _ = drain(&mut solo.conn);
let Some((_, credited)) = solo.conn.amplification_budget() else {
return;
};
solo.conn.handle_datagram(now, c_addr(), &dgram);
let _ = drain(&mut solo.conn);
assert_eq!(
solo.conn.amplification_budget().map(|(_, r)| r),
Some(credited),
"a replay credits nothing"
);
}
#[test]
fn a_mark_on_a_validated_address_transmits_at_once() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let floor = solo.conn.next_counter().expect("established");
solo.conn.mark_contested(now);
let d = drain(&mut solo.conn);
assert_eq!(
solo.conn.contested(),
Contested::Armed {
floor,
armed_at: now,
deadline: now + constants::KEEPALIVE_TIMEOUT,
}
);
assert_eq!(
solo.conn.timer(TimerKind::Contested),
Some(now + constants::KEEPALIVE_TIMEOUT)
);
assert_eq!(d.transmits().len(), 1, "one PING: {:?}", d.outs);
assert_eq!(count_contested(&d), 1, "one Contested, at the transmission");
let transmit = d
.position(|o| matches!(o, ConnOutput::Transmit(_)))
.expect("the PING");
let event = d
.position(|o| matches!(o, ConnOutput::Event(ConnEvent::Contested)))
.expect("the event");
assert!(transmit < event, "§8.1: the Transmit, then the Event");
assert!(
solo.conn.bytes_in_flight() > 0,
"ruling 43: the probe is in the sent map and in bytes_in_flight"
);
}
#[test]
fn a_second_mark_is_a_total_no_op_and_the_deadline_does_not_move() {
let mut solo = Solo::installed_at(origin());
let first = origin() + Duration::from_millis(10);
solo.conn.mark_contested(first);
let _ = drain(&mut solo.conn);
let deadline = solo.conn.timer(TimerKind::Contested);
assert!(deadline.is_some());
for step in 1..=5u64 {
let again = first + Duration::from_secs(step);
solo.conn.mark_contested(again);
let d = drain(&mut solo.conn);
assert_eq!(d.transmits().len(), 0, "no second PING");
assert_eq!(count_contested(&d), 0, "no second event");
assert_eq!(
solo.conn.timer(TimerKind::Contested),
deadline,
"the deadline is NOT re-armed — a security property, not an optimisation"
);
}
}
#[test]
fn a_mark_on_a_closing_connection_does_nothing() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
solo.conn.close(now, 0, b"");
let _ = drain(&mut solo.conn);
solo.conn.mark_contested(now + Duration::from_millis(1));
let d = drain(&mut solo.conn);
assert_eq!(solo.conn.contested(), Contested::No);
assert_eq!(d.transmits().len(), 0);
assert_eq!(count_contested(&d), 0);
assert_eq!(solo.conn.timer(TimerKind::Contested), None);
}
#[test]
fn an_unanswered_probe_times_the_connection_out_and_sends_nothing() {
let mut solo = Solo::installed_at(origin());
let marked = origin() + Duration::from_millis(10);
solo.conn.mark_contested(marked);
let _ = drain(&mut solo.conn);
let verdict = marked + constants::KEEPALIVE_TIMEOUT;
solo.conn.handle_timeout(verdict);
let d = drain(&mut solo.conn);
assert_eq!(d.closed(), Some(ConnectionLost::TimedOut));
assert_eq!(d.transmits().len(), 0, "§15.4: nothing at the verdict");
assert_eq!(count_contested(&d), 0);
assert_eq!(
count_cleared(&d),
0,
"no third notification — the death arrives on closed()"
);
}
#[test]
fn the_contested_verdict_precedes_the_liveness_deadline() {
let mut solo = Solo::installed_at(origin());
let marked = origin() + Duration::from_millis(10);
solo.conn.mark_contested(marked);
let _ = drain(&mut solo.conn);
let contested = solo.conn.timer(TimerKind::Contested).expect("armed");
let liveness = solo.conn.timer(TimerKind::Liveness).expect("armed");
assert!(
contested < liveness,
"the probe reclaims at {contested:?}, ahead of liveness at {liveness:?}"
);
}
#[test]
fn any_ack_covering_the_floor_clears_the_mark() {
let mut pair = Pair::installed_at(origin());
let now = origin() + Duration::from_millis(10);
pair.a.mark_contested(now);
let _ = pair.drain_a();
assert!(matches!(pair.a.contested(), Contested::Armed { .. }));
pair.a_to_b.clear();
let later = now + Duration::from_millis(20);
pair.a
.send_datagram(later, b"data")
.expect("a small datagram");
let _ = pair.drain_a();
let _ = pair.flush_a_to_b(later);
let d = pair.flush_b_to_a(later + Duration::from_millis(20));
assert_eq!(pair.a.contested(), Contested::No, "cleared by a later ACK");
assert_eq!(pair.a.timer(TimerKind::Contested), None);
assert_eq!(count_cleared(&d), 1, "one ContestCleared: {:?}", d.outs);
}
#[test]
fn a_re_mark_after_a_clear_records_a_strictly_greater_floor() {
let mut pair = Pair::installed_at(origin());
let now = origin() + Duration::from_millis(10);
pair.a.mark_contested(now);
let _ = pair.drain_a();
let first = pair.a.contested().floor().expect("marked");
let later = now + Duration::from_millis(20);
let _ = pair.flush_a_to_b(later);
let _ = pair.flush_b_to_a(later + Duration::from_millis(20));
assert_eq!(pair.a.contested(), Contested::No, "the ACK cleared it");
let again = later + Duration::from_millis(50);
pair.a.mark_contested(again);
let d = pair.drain_a();
let second = pair.a.contested().floor().expect("marked a second time");
assert!(
second > first,
"a fresh floor: {second} must exceed {first} (ruling 175)"
);
assert_eq!(
count_contested(&d),
1,
"a full second mark, and a second probe"
);
}
#[test]
fn a_roam_resets_the_controller_and_keeps_the_flight() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
let _ = write_all(&mut solo.conn, now, r, &[7u8; 4096]);
let d = drain(&mut solo.conn);
let burst = d.transmits().len() as u64;
assert!(
burst >= 2,
"premise: more than one packet, so at least one is not app-limited \
and §14.2's slow start has something to grow on (burst {burst})",
);
let sealed = solo.conn.next_counter().expect("established") - 1;
let ack_at = now + Duration::from_millis(20);
let _ = solo.deliver(ack_at, &ack_range_frame(sealed, burst - 1));
assert_eq!(
solo.conn.bytes_in_flight(),
0,
"premise: the ACK covered the whole burst, so no packet was left \
behind to be declared lost and halve the window",
);
let resend_at = ack_at + Duration::from_millis(10);
let _ = write_all(&mut solo.conn, resend_at, r, &[7u8; 2048]);
let _ = drain(&mut solo.conn);
let in_flight = solo.conn.bytes_in_flight();
assert!(in_flight > 0, "something is in flight");
let grown = solo.conn.congestion_window();
assert!(
grown > constants::INITIAL_WINDOW,
"**precondition, and it guards this test's own validity**: slow \
start has moved cwnd off its initial value ({grown} vs \
{}). Without this the assertion below is satisfied by a `reset` \
with an empty body.",
constants::INITIAL_WINDOW,
);
assert!(
solo.conn.recovery().rtt().min_rtt().is_some(),
"**precondition**: the first burst's ACK took an RTT sample, so \
`min_rtt` has a floor for the roam to re-seed — otherwise the \
`min_rtt` assertion below is vacuous too",
);
let roam_at = resend_at + Duration::from_millis(30);
let _ = solo.deliver_from(roam_at, c_addr(), &[]);
assert_eq!(
solo.conn.congestion_window(),
constants::INITIAL_WINDOW,
"§14.6: cwnd resets to INITIAL_WINDOW"
);
assert_eq!(
solo.conn.bytes_in_flight(),
in_flight,
"§13.6: the sent map is kept and bytes_in_flight is not reset"
);
assert_eq!(solo.conn.path_generation(), 1);
assert_eq!(
solo.conn.recovery().rtt().min_rtt(),
None,
"§13.1: min_rtt is re-seeded so it may rise"
);
}
#[test]
fn a_roam_lifts_the_slow_start_threshold_back_to_u64_max() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
assert_eq!(
solo.conn.congestion.ssthresh(),
u64::MAX,
"§14.2: ssthresh starts at u64::MAX, which is why a loss episode \
has to come before the roam for this test to assert anything",
);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
let _ = write_all(&mut solo.conn, now, r, &[7u8; 8192]);
let d = drain(&mut solo.conn);
let burst = d.transmits().len() as u64;
assert!(
burst > constants::K_PACKET_THRESHOLD + 1,
"premise: the burst is long enough for a head-only ACK to leave \
packets past the threshold (burst {burst}, threshold {})",
constants::K_PACKET_THRESHOLD,
);
let sealed = solo.conn.next_counter().expect("established") - 1;
let ack_at = now + Duration::from_millis(20);
let _ = solo.deliver(ack_at, &ack_frame(sealed));
let cut = solo.conn.congestion.ssthresh();
assert!(
cut < u64::MAX,
"**precondition, and it guards this test's own validity**: §14.3's \
congestion event cut ssthresh to the halved window ({cut}). \
Without it the assertion below is satisfied by a `reset` that \
never assigns ssthresh at all.",
);
let roam_at = ack_at + Duration::from_millis(30);
let _ = solo.deliver_from(roam_at, c_addr(), &[]);
assert_eq!(solo.conn.path_generation(), 1, "the roam did happen");
assert_eq!(
solo.conn.congestion.ssthresh(),
u64::MAX,
"§14.6 / §13.6: the controller resets to **initial state** on the \
roam seam, and ssthresh = u64::MAX is half of that state — a new \
path carries no continuity evidence, including no threshold",
);
}
#[test]
fn an_ack_for_a_pre_roam_packet_feeds_no_rtt_sample() {
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
solo.conn.write(now, r, &[7u8; 200]).expect("a write");
solo.conn.flush(now);
let d = drain(&mut solo.conn);
assert_eq!(d.transmits().len(), 1);
let sealed_counter = solo.conn.next_counter().expect("established") - 1;
let roam_at = now + Duration::from_millis(30);
let _ = solo.deliver_from(roam_at, c_addr(), &[]);
assert_eq!(solo.conn.path_generation(), 1);
let ack_at = roam_at + Duration::from_millis(40);
let _ = solo.deliver_from(ack_at, c_addr(), &ack_frame(sealed_counter));
assert_eq!(
solo.conn.recovery().rtt().min_rtt(),
None,
"the pre-roam packet's round trip never became the new path's floor"
);
assert_eq!(
solo.conn.smoothed_rtt(),
constants::K_INITIAL_RTT,
"and it never entered the estimator at all"
);
assert_eq!(
solo.conn.bytes_in_flight(),
0,
"but it did leave the flight"
);
}
#[test]
fn an_ack_for_a_post_roam_packet_does_feed_the_estimator() {
let mut solo = Solo::installed_at(origin());
let roam_at = origin() + Duration::from_millis(10);
let _ = solo.deliver_from(roam_at, c_addr(), &[]);
let sent_at = roam_at + Duration::from_millis(5);
let _ = solo.deliver_from(sent_at, c_addr(), &[constants::FRAME_PADDING as u8; 600]);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
solo.conn.write(sent_at, r, &[7u8; 100]).expect("a write");
solo.conn.flush(sent_at);
let d = drain(&mut solo.conn);
assert_eq!(
d.transmits().len(),
1,
"the budget admitted it: {:?}",
d.outs
);
let sealed_counter = solo.conn.next_counter().expect("established") - 1;
let ack_at = sent_at + Duration::from_millis(40);
let _ = solo.deliver_from(ack_at, c_addr(), &ack_frame(sealed_counter));
assert_eq!(
solo.conn.recovery().rtt().min_rtt(),
Some(Duration::from_millis(40)),
"a same-generation packet's round trip is a sample"
);
}
#[allow(dead_code)]
fn _addresses_are_distinct() {
assert_ne!(a_addr(), b_addr());
assert_ne!(a_addr(), c_addr());
}
#[test]
fn an_owed_ack_is_emitted_before_the_keepalive_at_one_instant() {
let mut solo = Solo::installed_at(origin());
let recv_at = origin() + Duration::from_millis(1);
let d = solo.deliver(recv_at, &[constants::FRAME_PING as u8]);
let ack_delay = solo.conn.timer(TimerKind::AckDelay);
let keepalive = solo
.conn
.timer(TimerKind::Keepalive)
.expect("§7.5 armed it");
let Some(ack_delay) = ack_delay else {
assert!(!d.transmits().is_empty());
return;
};
let both = ack_delay.max(keepalive) + Duration::from_millis(1);
solo.conn.handle_timeout(both);
let d = drain(&mut solo.conn);
let packets = solo.packets(&d);
assert_eq!(packets.len(), 2, "one ACK packet and one keepalive");
assert!(
packets[0].iter().any(|f| matches!(f, Wire::Ack { .. })),
"the owed ACK is emitted first: {packets:?}"
);
assert!(
packets[1].is_empty(),
"then §3.4's empty plaintext, which carries no frames at all: {packets:?}"
);
}
#[test]
fn a_marking_send_in_the_same_evaluation_suppresses_the_keepalive() {
let mut solo = Solo::installed_at(origin());
let recv_at = origin() + Duration::from_millis(1);
let _ = solo.deliver(recv_at, &[constants::FRAME_PING as u8]);
let keepalive = solo
.conn
.timer(TimerKind::Keepalive)
.expect("§7.5 armed it");
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
solo.conn.write(keepalive, r, &[9u8; 64]).expect("a write");
solo.conn.handle_timeout(keepalive);
let d = drain(&mut solo.conn);
let packets = solo.packets(&d);
assert!(!packets.is_empty(), "the data went out: {:?}", d.outs);
assert!(
packets.iter().all(|p| !p.is_empty()),
"and no empty-plaintext keepalive rode behind it: {packets:?}"
);
assert_eq!(
solo.conn.timer(TimerKind::Keepalive),
None,
"the marking send made `R > S` false, which is what the keepalive was for"
);
}
#[test]
fn a_packed_path_challenge_is_traced_under_roam() {
use crate::testutil::Capture;
let capture = Capture::install();
let mut solo = Solo::installed_at(origin());
let now = origin() + Duration::from_millis(10);
let _ = solo.deliver_from(now, c_addr(), &[]);
let sent_before = capture
.with_target("slither::roam")
.into_iter()
.filter(|e| e.field("event") == Some("path_challenge_sent"))
.count();
assert_eq!(
sent_before, 0,
"arming owes a challenge; it does not send one. A build that traces at the arming would report a send before any frame was packed"
);
let r = solo.conn.open(crate::core::Dir::Uni).expect("a uni stream");
solo.conn
.write(now, r, &[7u8; 4_000])
.expect("the write is admitted into send state");
solo.conn.flush(now);
let d = drain(&mut solo.conn);
assert!(
!d.transmits().is_empty(),
"fixture check: nothing was pumped, so nothing could carry a challenge"
);
let sent: Vec<_> = capture
.with_target("slither::roam")
.into_iter()
.filter(|e| e.field("event") == Some("path_challenge_sent"))
.collect();
assert_eq!(
sent.len(),
1,
"§18.2: the challenge sent at this arming must be traced, exactly once — it is packed once and the budget owes one"
);
}