From 85bc4c71b55d1e49d972ad885a55e8c5aba9fd12 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:36:36 +0200 Subject: [PATCH] Align the replay-window test with the reliable sequence domain Separating the sequence domains gated duplicate suppression on packet.reliable in Rust, TypeScript and C# alike, so lossy datagrams are remembered nowhere and a replayed one is delivered again. That is the intended contract -- lossy traffic is fire-and-forget and its history is bounded by not existing -- but replay_history_is_bounded_and_idle_hub_sessions_expire still asserted the old behaviour with a lossy packet and had been left failing on the branch. The three tests the change added in each runtime cover ordered-requires- reliable, per-channel sequenced scope, and lossy gaps. None covers the bounded replay window, which was this test's real subject, so removing the assertion would have dropped the only coverage of a security property. The lossy assertion now states the new contract, and the reliable domain gets its own replay check: 4,100 acknowledged reliable sends, then the oldest replayed and rejected by the window. Rust 151, C# 148, TypeScript 48, Python 138 all pass. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/tests/rudp_server_hub.rs | 34 +++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/cultnet-rs/tests/rudp_server_hub.rs b/packages/cultnet-rs/tests/rudp_server_hub.rs index cdc6e390..801312aa 100644 --- a/packages/cultnet-rs/tests/rudp_server_hub.rs +++ b/packages/cultnet-rs/tests/rudp_server_hub.rs @@ -453,10 +453,42 @@ fn replay_history_is_bounded_and_idle_hub_sessions_expire() -> Result<()> { } assert_eq!(receiver.receive(&packet, sequence)?.delivered.len(), 1); } - assert!( + // Separating the sequence domains moved duplicate suppression onto the + // reliable domain, so a replayed lossy datagram is delivered again rather + // than silently dropped. Lossy traffic is remembered nowhere, which is what + // bounds its history. + assert_eq!( receiver .receive(&oldest.expect("oldest packet"), 4_101)? .delivered + .len(), + 1 + ); + + // The bounded replay window still guards the reliable domain, and nothing + // else asserts it once the lossy path stopped being remembered. + let mut oldest_reliable = None; + for step in 0..4_100u64 { + let packet = sender.send( + "schema", + vec![(step % 251) as u8], + CultNetRudpSendOptions { + reliable: true, + ordered: false, + sequenced: false, + now_ms: step, + }, + )?; + if oldest_reliable.is_none() { + oldest_reliable = Some(packet.clone()); + } + assert_eq!(receiver.receive(&packet, step)?.delivered.len(), 1); + sender.receive(&receiver.create_ack_for(packet.sequence), step)?; + } + assert!( + receiver + .receive(&oldest_reliable.expect("oldest reliable packet"), 4_101)? + .delivered .is_empty() );