use std::net::SocketAddr;
use std::time::{Duration, Instant};
use crate::constants;
use crate::core::connection::Connection;
use crate::core::connection::stream_id::Dir;
use crate::core::connection::testfix::{
Drained, Pair, Solo, Wire, drain, put, t0, tick, v4, write_all,
};
use crate::packet::ReferenceSuite;
type Suite = ReferenceSuite;
const OVERHEAD: u64 = (constants::DATA_HEADER_LEN + constants::AEAD_TAG_LEN) as u64;
fn c_addr() -> SocketAddr {
v4(9, 41_000)
}
fn room(conn: &Connection<Suite>) -> u64 {
let (sent, recv) = conn
.amplification_budget()
.expect("this test needs an armed, unvalidated budget");
constants::AMPLIFICATION_FACTOR
.saturating_mul(recv)
.saturating_sub(sent)
}
fn sizes(d: &Drained) -> Vec<u64> {
d.transmits().iter().map(|t| t.data.len() as u64).collect()
}
fn stream_bytes(packets: &[Vec<Wire>]) -> usize {
packets
.iter()
.flatten()
.filter_map(|f| match f {
Wire::Stream { data, .. } => Some(data.len()),
_ => None,
})
.sum()
}
fn is_ack_eliciting(frames: &[Wire]) -> bool {
frames
.iter()
.any(|f| !matches!(f, Wire::Padding | Wire::Ack { .. } | Wire::Close { .. }))
}
fn counter_of(dgram: &[u8]) -> u64 {
u64::from_le_bytes(
dgram[6..14]
.try_into()
.expect("§3.4's 8-byte counter at offset 6"),
)
}
fn path_response_frame(value: [u8; 8]) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, constants::FRAME_PATH_RESPONSE);
f.extend_from_slice(&value);
f
}
fn carries_challenge(solo: &mut Solo, d: &Drained, value: [u8; 8]) -> bool {
let mut needle = vec![constants::FRAME_PATH_CHALLENGE as u8];
needle.extend_from_slice(&value);
d.transmits().iter().any(|t| {
let pt = solo.peer.open_dgram(&t.data);
pt.windows(needle.len()).any(|w| w == needle)
})
}
fn ack_frame(largest: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, constants::FRAME_ACK);
put(&mut f, largest);
put(&mut f, 0); put(&mut f, 0); put(&mut f, 0); f
}
fn roamed_with(now: Instant, plaintext: usize) -> Solo {
let mut solo = Solo::installed_at(now);
assert!(
solo.conn.amplification_budget().is_none(),
"fixture premise: this core starts **validated** (ruling 200 — the \
budget arms on the msg1 anchor or on a roam, and this constructor \
is neither), so the roam below is demonstrably what arms it"
);
let pad = vec![constants::FRAME_PADDING as u8; plaintext];
let _ = solo.deliver_from(now, c_addr(), &pad);
let credit = OVERHEAD + plaintext as u64;
assert_eq!(
solo.conn.amplification_budget(),
Some((0, credit)),
"fixture premise: `commit_roam` arms at exactly the roaming \
datagram's length (rulings 168/169) and nothing has been spent — a \
PADDING-only plaintext is not ack-eliciting, so no ACK is owed"
);
solo
}
#[test]
fn a_tight_budget_after_a_roam_still_moves_application_data_at_once() {
let now = t0();
let mut solo = roamed_with(now, 0);
let before = room(&solo.conn);
assert_eq!(
before, 90,
"ruling 203's own arithmetic: 3 × a 30-byte keepalive"
);
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0xC3u8; 2048]);
let d = drain(&mut solo.conn);
let s = sizes(&d);
assert!(
!s.is_empty(),
"ruling 203: a ~90-byte packet fits *now*, and holding a full-size \
candidate is the one behaviour that prevents the escape the budget \
exists to permit"
);
assert!(
s.iter().all(|&n| n <= before),
"§7.3 binds all output: {s:?} against {before} bytes of room"
);
assert!(
s.iter().sum::<u64>() <= before,
"§7.3 binds the *episode*, not just each packet: {s:?} sums past \
{before}"
);
let packets = solo.packets(&d);
assert!(
stream_bytes(&packets) > 0,
"the point of the fix is that **application data** moves; a packet \
carrying no STREAM bytes leaves the stall exactly where it was, one \
indirection later (ruling 207(b))"
);
assert!(
is_ack_eliciting(&packets[0]),
"**[ruling 208]** an ACK no longer validates anything; what ends the \
unvalidated state is a PATH_RESPONSE, and the challenge that asks \
for one is itself ack-eliciting — so a non-ack-eliciting first \
packet means neither mechanism is running"
);
}
#[test]
fn the_shrunken_packet_carries_the_challenge_whose_echo_validates() {
let now = t0();
let mut solo = roamed_with(now, 0);
let before = room(&solo.conn);
let challenge = solo
.conn
.outstanding_challenge()
.expect("the roam armed the budget and drew a challenge");
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x5Au8; 2048]);
let d = drain(&mut solo.conn);
let ts = d.transmits();
assert!(
!ts.is_empty(),
"nothing left the sender, so nothing carried the challenge and \
ruling 208's proof can never be asked for (ruling 203)"
);
let counter = counter_of(&ts[0].data);
let packets = solo.packets(&d);
assert!(
is_ack_eliciting(&packets[0]),
"ruling 207(b): a packet sized to fit the budget is useless if what \
fits is a bare ACK"
);
assert!(
carries_challenge(&mut solo, &d, challenge),
"**[ruling 208]** the shrunken packet must carry the arming's own \
challenge; a PING here elicits an ACK that validates nothing and \
reproduces ruling 203's stall one indirection later"
);
let d_ack = solo.deliver_from(now, c_addr(), &ack_frame(counter));
assert!(
solo.conn.amplification_budget().is_some(),
"**[ruling 208]** an ACK is an assertion by whoever holds the key, \
and §7.3's roaming threat model *is* the key holder: the budget \
stays armed"
);
let _ = solo.deliver_from(now, c_addr(), &path_response_frame(challenge));
assert!(
solo.conn.amplification_budget().is_none(),
"**[ruling 208]** a PATH_RESPONSE echoing the arming's challenge, \
from the address in question, is return routability proven"
);
let _ = write_all(&mut solo.conn, now, r, &[0x5Au8; 8192]);
let d_after = drain(&mut solo.conn);
let biggest = sizes(&d_ack)
.into_iter()
.chain(sizes(&d_after))
.max()
.unwrap_or(0);
assert!(
biggest > before,
"after validation nothing is bound by the old {before}-byte room; \
the largest packet was {biggest}"
);
}
#[test]
fn every_tight_room_admits_a_data_bearing_packet_that_nearly_fills_it() {
for plaintext in [0usize, 10, 30, 90, 200, 350] {
let now = t0();
let mut solo = roamed_with(now, plaintext);
let before = room(&solo.conn);
assert!(
before < constants::MAX_DATAGRAM as u64,
"premise: room {before} must be *tight*, or the sizing rule is \
not exercised at all"
);
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x11u8; 4096]);
let d = drain(&mut solo.conn);
let s = sizes(&d);
assert!(
!s.is_empty(),
"room {before}: nothing left the sender, with 4 KiB pending"
);
assert!(
s.iter().sum::<u64>() <= before,
"room {before}: §7.3 is exceeded by {s:?}"
);
assert!(
s[0] > before.saturating_sub(OVERHEAD),
"room {before}: the first packet is {} bytes, which is a whole \
{OVERHEAD}-byte overhead short — ruling 207(c), the two units \
differ by exactly the overhead",
s[0]
);
let packets = solo.packets(&d);
assert!(
stream_bytes(&packets) > 0,
"room {before}: the packet that fits carried no application data"
);
assert!(
is_ack_eliciting(&packets[0]),
"room {before}: ruling 207(b)"
);
}
}
#[test]
fn the_packing_target_is_min_of_max_datagram_and_the_room_on_both_sides() {
for (plaintext, expected) in [(369usize, 1_197u64), (370, 1_200), (371, 1_203)] {
let now = t0();
let mut solo = roamed_with(now, plaintext);
let before = room(&solo.conn);
assert_eq!(before, expected, "premise: the room straddles MAX_DATAGRAM");
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x99u8; 8192]);
let d = drain(&mut solo.conn);
let s = sizes(&d);
assert!(
!s.is_empty(),
"room {before}: nothing left the sender, with 8 KiB pending"
);
assert!(
s.iter().all(|&n| n <= constants::MAX_DATAGRAM as u64),
"room {before}: the MAX_DATAGRAM half of `min(MAX_DATAGRAM, \
room)` — {s:?} exceeds §3.1's non-fragmenting bound"
);
assert!(
s.iter().all(|&n| n <= before),
"room {before}: the `room` half of the `min` — {s:?}"
);
assert!(
s.iter().sum::<u64>() <= before,
"room {before}: §7.3 over the episode — {s:?}"
);
let target = before.min(constants::MAX_DATAGRAM as u64);
assert!(
s[0] > target - OVERHEAD,
"room {before}: the first packet is {} against a target of \
{target}; a whole {OVERHEAD}-byte overhead is missing",
s[0]
);
}
}
#[test]
fn the_packet_grows_with_the_room_rather_than_sitting_at_a_fixed_size() {
let mut observed: Vec<(u64, u64)> = Vec::new();
for plaintext in [0usize, 90, 300] {
let now = t0();
let mut solo = roamed_with(now, plaintext);
let before = room(&solo.conn);
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x22u8; 4096]);
let d = drain(&mut solo.conn);
let s = sizes(&d);
assert!(!s.is_empty(), "room {before}: nothing left the sender");
observed.push((before, s[0]));
}
for w in observed.windows(2) {
let (small_room, small) = w[0];
let (big_room, big) = w[1];
assert!(
big_room > small_room,
"the fixture must present a strictly larger room: {small_room} \
then {big_room}"
);
assert!(
big > small,
"the packing target is bounded by the *room* (ruling 203), so a \
larger room must produce a larger first packet: {small} bytes \
at room {small_room}, {big} bytes at room {big_room}"
);
}
}
#[test]
fn shrinking_to_fit_does_not_loosen_what_the_budget_permits() {
let now = t0();
let mut solo = roamed_with(now, 0);
let before = room(&solo.conn);
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x33u8; 4096]);
let first = drain(&mut solo.conn);
let spent: u64 = sizes(&first).iter().sum();
assert!(
spent > 0,
"ruling 203: something must move (working rule 9)"
);
assert!(
solo.conn.amplification_budget().is_some(),
"ruling 168: the address validates on an **ACK covering the floor** \
and on nothing else. None has arrived, so the budget must still be \
armed — a build that disarmed it to make its own packet fit passes \
every size bound here and is the reflector §7.3 exists to close"
);
let after = room(&solo.conn);
assert!(
after < before,
"the shrunken packet must be **charged**: {after} against {before}"
);
tick(&mut solo.conn, now);
let second = drain(&mut solo.conn);
let more: u64 = sizes(&second).iter().sum();
assert!(
spent + more <= before,
"§7.3 over the whole episode: {spent} + {more} against {before} \
bytes of room (ruling 207(a) — the predicate does not move)"
);
if after < OVERHEAD + 1 {
assert!(
second.transmits().is_empty(),
"{after} bytes of room cannot hold the minimum {}-byte datagram, \
and §7.3 **holds** what does not fit — it does not truncate and \
does not send anyway",
OVERHEAD + 1
);
}
}
#[test]
fn each_credited_round_moves_application_data_instead_of_banking_it() {
let now = t0();
let mut solo = roamed_with(now, 0);
let r = solo.conn.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut solo.conn, now, r, &[0x44u8; 8192]);
let _ = drain(&mut solo.conn);
let mut delivered = 0usize;
for round in 0..6 {
let d = solo.deliver_from(now, c_addr(), &[]);
let moved_on_recv = stream_bytes(&solo.packets(&d));
tick(&mut solo.conn, now);
let d2 = drain(&mut solo.conn);
let moved_on_tick = stream_bytes(&solo.packets(&d2));
let moved = moved_on_recv + moved_on_tick;
assert!(
moved > 0,
"round {round}: 90 further bytes of room arrived and a \
data-bearing packet fits them. Ruling 203 — the budget exists \
to be *escaped* by a round trip, and holding a full packet is \
the one behaviour that prevents the escape"
);
delivered += moved;
let (sent, recv) = solo
.conn
.amplification_budget()
.expect("no ACK has been sent, so the address is still unvalidated");
assert!(
sent <= constants::AMPLIFICATION_FACTOR.saturating_mul(recv),
"round {round}: §7.3's inequality — {sent} sent against {recv} \
received"
);
}
assert!(
delivered > 60,
"six credited rounds moved {delivered} application bytes in total — \
less than the plaintext room of a *single* round, so the sizing is \
not tracking the budget"
);
}
#[test]
fn two_cores_validate_the_roamed_address_within_one_round_trip() {
let now = t0();
let mut p = Pair::installed_at(now);
assert!(
p.b.amplification_budget().is_none(),
"fixture premise: both halves of `installed_at` start validated"
);
let ra = p.a.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut p.a, now, ra, &[0x77u8; 64]);
let _ = p.drain_a();
let _ = p.flush_a_to_b_from(now, c_addr());
let before = room(&p.b);
assert!(
before < constants::MAX_DATAGRAM as u64,
"premise: the roam leaves a room of {before}, which must be tight \
enough that a full-size candidate does not fit"
);
let rb = p.b.open(Dir::Uni).expect("a uni stream opens");
let _ = write_all(&mut p.b, now, rb, &[0x88u8; 4096]);
let d = p.drain_b();
let s = sizes(&d);
assert!(
!s.is_empty(),
"room {before}: `b` put nothing on the wire with 4 KiB pending"
);
assert!(
s.iter().sum::<u64>() <= before,
"room {before}: §7.3 binds `b`'s output — {s:?}"
);
let later = now + constants::MAX_ACK_DELAY + Duration::from_millis(1);
let _ = p.flush_b_to_a(later);
let settled = later + constants::MAX_ACK_DELAY + Duration::from_millis(1);
p.a.handle_timeout(settled);
let _ = p.drain_a();
let db = p.flush_a_to_b_from(settled, c_addr());
assert!(
p.b.amplification_budget().is_none(),
"**[ruling 208]** `b`'s post-roam output must carry its \
`PATH_CHALLENGE`, so that `a`'s echo disarms the budget within one \
round trip. Still armed means the output was a bare ACK, a PING \
where the challenge belongs, or nothing at all — ruling 207(b)'s \
stall, ruling 208's, or ruling 203's"
);
let total: u64 = s.iter().sum::<u64>() + sizes(&db).iter().sum::<u64>();
assert!(
total > before,
"the address validated, so more than the {before}-byte budget must \
have left `b`: {total} bytes did"
);
}