forked from unom/punktfunk
test(core/session): pin the low-MTU chunk-aligned guarantee at clamped shard sizes
PyroWave sessions are gated out of mid-session renegotiation, so a constrained path serves them through the leg-1 SESSION-START clamp. This pins the consistency that guarantee rests on: everything chunk-aligned derives from the one Welcome::shard_payload number — the host packetizes at it, the client's C-ABI parse window reads it back, and partial delivery zero-fills exact windows of it — verified at the two clamp shapes a constrained path actually produces (1216, the WARP/Tailscale budget, and the 512 floor) over the sealed loopback wire with real loss.
This commit is contained in:
@@ -1086,6 +1086,89 @@ mod wire_equivalence_tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The low-MTU PyroWave guarantee (design/shard-payload-reneg.md): mid-session
|
||||
/// renegotiation is gated OFF for chunk-aligned sessions, so a constrained path serves
|
||||
/// them through the leg-1 SESSION-START clamp instead — the learned budget (or
|
||||
/// `PUNKTFUNK_WIRE_MTU`) sizes `Welcome::shard_payload`, and everything chunk-aligned
|
||||
/// derives from that ONE number fixed at the handshake: the host packetizes at it, the
|
||||
/// client's parse window reads it back ([`Session::shard_payload`] → the C-ABI
|
||||
/// `punktfunk_connection_shard_payload` every embedder walks windows with), and partial
|
||||
/// delivery zero-fills exact windows of it. Pin that consistency at the clamp shapes a
|
||||
/// constrained path actually produces: the WARP/Tailscale budget (1216) and the floor
|
||||
/// (512) — chunk-aligned frames deliver, lose whole windows (never splice), and the
|
||||
/// window arithmetic matches the session value end to end.
|
||||
#[test]
|
||||
fn chunk_aligned_sessions_work_at_clamped_shard_sizes() {
|
||||
use crate::packet::USER_FLAG_CHUNK_ALIGNED;
|
||||
for shard in [1216usize, crate::config::MIN_SHARD_PAYLOAD] {
|
||||
let mk = |role| Config {
|
||||
role,
|
||||
phase: ProtocolPhase::P2Punktfunk,
|
||||
fec: FecConfig {
|
||||
scheme: FecScheme::Gf16,
|
||||
fec_percent: 0, // no parity — any drop leaves a hole
|
||||
max_data_per_block: 64,
|
||||
},
|
||||
shard_payload: shard,
|
||||
max_frame_bytes: 8 * 1024 * 1024,
|
||||
encrypt: true,
|
||||
key: SessionKey::Aes128Gcm([7u8; 16]),
|
||||
salt: [3, 1, 4, 1],
|
||||
loopback_drop_period: 0,
|
||||
};
|
||||
let (h, c) = crate::transport::loopback_pair(3, 1);
|
||||
let mut host = Session::new(mk(Role::Host), Box::new(h)).unwrap();
|
||||
let mut client = Session::new(mk(Role::Client), Box::new(c)).unwrap();
|
||||
client.set_deliver_partial_frames(true);
|
||||
// The window every embedder parses with IS the clamped session value.
|
||||
assert_eq!(client.shard_payload(), shard);
|
||||
assert_eq!(host.shard_payload(), shard);
|
||||
|
||||
let frame = pattern(8 * shard);
|
||||
host.submit_frame(&frame, 1_000, USER_FLAG_CHUNK_ALIGNED)
|
||||
.unwrap();
|
||||
let mut got_partial = None;
|
||||
let mut completes = 0;
|
||||
for i in 0..80u64 {
|
||||
host.submit_frame(&pattern(shard), 2_000 + i, USER_FLAG_CHUNK_ALIGNED)
|
||||
.unwrap();
|
||||
loop {
|
||||
match client.poll_frame() {
|
||||
Ok(f) if !f.complete => got_partial = Some(f),
|
||||
Ok(_) => completes += 1,
|
||||
Err(PunktfunkError::NoFrame) => break,
|
||||
Err(e) => panic!("shard {shard}: unexpected: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = got_partial.expect("the lossy frame must be delivered partial");
|
||||
assert_eq!(p.data.len(), frame.len(), "shard {shard}");
|
||||
// Loss lands on exact `shard`-sized window boundaries: zeroed windows for the
|
||||
// dropped datagrams, byte-identical survivors — nothing spliced across windows.
|
||||
let mut zero_windows = 0;
|
||||
for w in 0..8 {
|
||||
let win = &p.data[w * shard..(w + 1) * shard];
|
||||
if win.iter().all(|&b| b == 0) {
|
||||
zero_windows += 1;
|
||||
} else {
|
||||
assert_eq!(
|
||||
win,
|
||||
&frame[w * shard..(w + 1) * shard],
|
||||
"shard {shard}: window {w} corrupt"
|
||||
);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
(1..8).contains(&zero_windows),
|
||||
"shard {shard}: dropped shards zero-filled (got {zero_windows})"
|
||||
);
|
||||
assert!(
|
||||
completes > 40,
|
||||
"shard {shard}: surviving filler frames flow normally"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mid-session shard renegotiation end to end over the SEALED loopback wire
|
||||
/// (design/shard-payload-reneg.md): one host session re-keys its packetizer between AUs
|
||||
/// — shrink, jumbo grow, revert — through one continuous crypto/replay stream, and one
|
||||
|
||||
Reference in New Issue
Block a user