Ack Cut 1 tests: three held frames, the Rust byte bound, reset reseeds

A gap now holds three ordered frames so a watermark that advances one step
per call fails; the reset test follows a second handshake at a different
sequence so a watermark that survives the reset fails; Rust gains the
4 MiB hold bound in bytes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 00:20:04 +02:00
commit 43d7e53efd
4 changed files with 95 additions and 23 deletions

View file

@ -10,7 +10,6 @@ import unittest
from cultnet_py.transport import (
CultNetRudpPacket,
CultNetRudpPacketType,
CultNetRudpSendOptions,
CultNetRudpSession,
CultNetRudpSessionOptions,
@ -40,11 +39,13 @@ class CultNetRudpOrderedDeliveryTests(unittest.TestCase):
u = send(sender, "rel", "u", False)
o2 = send(sender, "schema", "o2", True)
o3 = send(sender, "schema", "o3", True)
o4 = send(sender, "schema", "o4", True)
self.assertEqual(names(receiver.receive(o1, 1)), ["o1"])
self.assertEqual(names(receiver.receive(o2, 2)), [], "u is missing")
self.assertEqual(names(receiver.receive(o3, 3)), [], "u is missing")
self.assertEqual(names(receiver.receive(u, 4)), ["u", "o2", "o3"])
self.assertEqual(names(receiver.receive(o4, 4)), [], "u is missing")
self.assertEqual(names(receiver.receive(u, 5)), ["u", "o2", "o3", "o4"])
def test_two_ordered_channels_release_each_other_in_sequence_order(self) -> None:
sender, receiver = handshake()
@ -93,16 +94,21 @@ class CultNetRudpOrderedDeliveryTests(unittest.TestCase):
self.assertEqual(names(receiver.receive(s1, 3)), ["s1", "s2"])
self.assertEqual(names(receiver.receive(s2, 4)), [])
def test_reset_peer_state_forgets_held_frames_and_the_watermark(self) -> None:
def test_reset_peer_state_forgets_held_frames_and_reseeds_the_watermark_from_the_next_handshake(self) -> None:
sender, receiver = handshake()
s1 = send(sender, "schema", "s1", True)
send(sender, "schema", "s1", True)
s2 = send(sender, "schema", "s2", True)
self.assertEqual(names(receiver.receive(s2, 1)), [])
receiver.reset_peer_state()
receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 410, 1, 0, 0, "control"), 2)
following = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=410, initial_sequence=100))
following.receive(receiver.accept_connect(following.create_connect(0), 2), 2)
d1 = send(following, "schema", "d1", True)
d2 = send(following, "schema", "d2", True)
self.assertEqual(names(receiver.receive(s1, 3)), ["s1"], "the held s2 died with the reset")
# The held s2 died with the reset, and the watermark follows the new peer.
self.assertEqual(names(receiver.receive(d2, 3)), [])
self.assertEqual(names(receiver.receive(d1, 4)), ["d1", "d2"])
if __name__ == "__main__":

View file

@ -2762,16 +2762,18 @@ fn rudp_ordered_frame_waits_for_a_gap_filled_by_another_channel() -> Result<()>
let u = sender.send("media", b"u".to_vec(), rudp_send_options(false))?;
let o2 = sender.send("schema", b"o2".to_vec(), rudp_send_options(true))?;
let o3 = sender.send("schema", b"o3".to_vec(), rudp_send_options(true))?;
let o4 = sender.send("schema", b"o4".to_vec(), rudp_send_options(true))?;
assert_eq!(rudp_delivered_names(&receiver.receive(&o1, 1)?.delivered), ["o1"]);
// `u` is lost, so nothing behind it may be delivered.
assert!(receiver.receive(&o2, 2)?.delivered.is_empty());
assert!(receiver.receive(&o3, 3)?.delivered.is_empty());
// The other channel's packet fills the gap and releases the ordered frames
// in the same call.
assert!(receiver.receive(&o4, 4)?.delivered.is_empty());
// The other channel's packet fills the gap and releases every ordered
// frame behind it in the same call.
assert_eq!(
rudp_delivered_names(&receiver.receive(&u, 4)?.delivered),
["u", "o2", "o3"]
rudp_delivered_names(&receiver.receive(&u, 5)?.delivered),
["u", "o2", "o3", "o4"]
);
Ok(())
}
@ -2826,6 +2828,30 @@ fn rudp_accept_seeds_the_watermark_of_the_connecting_side() -> Result<()> {
Ok(())
}
#[test]
fn rudp_reset_forgets_held_frames_and_reseeds_the_watermark_from_the_next_handshake() -> Result<()> {
let (mut sender, mut receiver) = rudp_ordered_pair()?;
sender.send("schema", b"s1".to_vec(), rudp_send_options(true))?;
let s2 = sender.send("schema", b"s2".to_vec(), rudp_send_options(true))?;
assert!(receiver.receive(&s2, 1)?.delivered.is_empty());
receiver.reset_peer_state();
let mut next = CultNetRudpSession::new(CultNetRudpSessionOptions {
connection_id: 410,
initial_sequence: 100,
..CultNetRudpSessionOptions::default()
});
let accept = receiver.accept_connect(&next.create_connect(0, Vec::new())?, 2, Vec::new())?;
next.receive(&accept, 2)?;
let d1 = next.send("schema", b"d1".to_vec(), rudp_send_options(true))?;
let d2 = next.send("schema", b"d2".to_vec(), rudp_send_options(true))?;
// The held s2 died with the reset, and the watermark follows the new peer.
assert!(receiver.receive(&d2, 3)?.delivered.is_empty());
assert_eq!(rudp_delivered_names(&receiver.receive(&d1, 4)?.delivered), ["d1", "d2"]);
Ok(())
}
#[test]
fn rudp_large_ordered_frame_without_a_gap_is_never_held_or_refused() -> Result<()> {
let (mut sender, mut receiver) = rudp_ordered_pair()?;
@ -2851,6 +2877,32 @@ fn rudp_large_ordered_frame_without_a_gap_is_never_held_or_refused() -> Result<(
Ok(())
}
#[test]
fn rudp_ordered_hold_buffer_is_bounded_in_bytes() -> Result<()> {
let (mut sender, mut receiver) = rudp_ordered_pair()?;
let withheld = sender.send("schema", b"first".to_vec(), rudp_send_options(true))?;
sender.receive(&receiver.create_ack_for(withheld.sequence), 1)?;
// Four 1 MiB frames fit the 4 MiB hold; the fifth does not.
let mut refused_at = None;
for step in 0..8u32 {
let packet = sender.send("schema", vec![7u8; 1024 * 1024], rudp_send_options(true))?;
match receiver.receive(&packet, 2) {
Ok(result) => {
assert!(result.delivered.is_empty(), "the gap holds every frame");
sender.receive(&receiver.create_ack_for(packet.sequence), 2)?;
}
Err(refusal) => {
assert!(refusal.to_string().contains("ordered hold buffer is full"), "{refusal}");
refused_at = Some(step);
break;
}
}
}
assert_eq!(refused_at, Some(4));
Ok(())
}
#[test]
fn rudp_far_ahead_sequence_cannot_starve_the_session() -> Result<()> {
let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions {

View file

@ -27,11 +27,13 @@ test("an ordered frame waits for a gap filled by another channel", () => {
const u = send(sender, "rel", "u", false);
const o2 = send(sender, "schema", "o2", true);
const o3 = send(sender, "schema", "o3", true);
const o4 = send(sender, "schema", "o4", true);
assert.deepEqual(names(receiver.receive(o1, 1)), ["o1"]);
assert.deepEqual(names(receiver.receive(o2, 2)), [], "u is missing");
assert.deepEqual(names(receiver.receive(o3, 3)), [], "u is missing");
assert.deepEqual(names(receiver.receive(u, 4)), ["u", "o2", "o3"]);
assert.deepEqual(names(receiver.receive(o4, 4)), [], "u is missing");
assert.deepEqual(names(receiver.receive(u, 5)), ["u", "o2", "o3", "o4"]);
});
test("two ordered channels release each other in sequence order", () => {
@ -90,14 +92,19 @@ test("a duplicate of a held frame is not delivered twice", () => {
assert.deepEqual(names(receiver.receive(s2, 4)), []);
});
test("resetting peer state forgets held frames and the watermark", () => {
test("resetting peer state forgets held frames and reseeds the watermark from the next handshake", () => {
const { client: sender, server: receiver } = handshake();
const s1 = send(sender, "schema", "s1", true);
send(sender, "schema", "s1", true);
const s2 = send(sender, "schema", "s2", true);
assert.deepEqual(names(receiver.receive(s2, 1)), []);
receiver.resetPeerState();
receiver.receive({ packetType: "accept", connectionId: 410, sequence: 1, ack: 0, ackMask: 0, channelId: "control" }, 2);
const next = new CultNetRudpSession({ connectionId: 410, initialSequence: 100 });
next.receive(receiver.acceptConnect(next.createConnect(0), 2), 2);
const d1 = send(next, "schema", "d1", true);
const d2 = send(next, "schema", "d2", true);
assert.deepEqual(names(receiver.receive(s1, 3)), ["s1"], "the held s2 died with the reset");
// The held s2 died with the reset, and the watermark follows the new peer.
assert.deepEqual(names(receiver.receive(d2, 3)), []);
assert.deepEqual(names(receiver.receive(d1, 4)), ["d1", "d2"]);
});

View file

@ -38,11 +38,13 @@ namespace GameCult.Networking.Tests
var u = Send(sender, "rel", "u", false);
var o2 = Send(sender, "schema", "o2", true);
var o3 = Send(sender, "schema", "o3", true);
var o4 = Send(sender, "schema", "o4", true);
Assert.That(Names(receiver.Receive(o1, 1)), Is.EqualTo(new[] { "o1" }));
Assert.That(Names(receiver.Receive(o2, 2)), Is.Empty, "u is missing");
Assert.That(Names(receiver.Receive(o3, 3)), Is.Empty, "u is missing");
Assert.That(Names(receiver.Receive(u, 4)), Is.EqualTo(new[] { "u", "o2", "o3" }));
Assert.That(Names(receiver.Receive(o4, 4)), Is.Empty, "u is missing");
Assert.That(Names(receiver.Receive(u, 5)), Is.EqualTo(new[] { "u", "o2", "o3", "o4" }));
}
[Test]
@ -111,17 +113,22 @@ namespace GameCult.Networking.Tests
}
[Test]
public void ResetPeerStateForgetsHeldFramesAndTheWatermark()
public void ResetPeerStateForgetsHeldFramesAndReseedsTheWatermarkFromTheNextHandshake()
{
var (sender, receiver) = Handshake();
var s1 = Send(sender, "schema", "s1", true);
Send(sender, "schema", "s1", true);
var s2 = Send(sender, "schema", "s2", true);
Assert.That(Names(receiver.Receive(s2, 1)), Is.Empty);
receiver.ResetPeerState();
receiver.Receive(new CultNetRudpPacket { PacketType = CultNetRudpPacketType.Accept, ConnectionId = 410, Sequence = 1, ChannelId = "control" }, 2);
var next = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 410, InitialSequence = 100 });
next.Receive(receiver.AcceptConnect(next.CreateConnect(0), 2), 2);
var d1 = Send(next, "schema", "d1", true);
var d2 = Send(next, "schema", "d2", true);
Assert.That(Names(receiver.Receive(s1, 3)), Is.EqualTo(new[] { "s1" }), "the held s2 died with the reset");
// The held s2 died with the reset, and the watermark follows the new peer.
Assert.That(Names(receiver.Receive(d2, 3)), Is.Empty);
Assert.That(Names(receiver.Receive(d1, 4)), Is.EqualTo(new[] { "d1", "d2" }));
}
}
}