@@ -18,7 +18,11 @@ pub const Connection = struct {
1818 /// Packet number tracking
1919 next_packet_number : u32 ,
2020 largest_received_packet : u32 ,
21- largest_acked_packet : u32 ,
21+ next_expected_packet : u32 ,
22+ last_ack_sent : ? u32 ,
23+ largest_peer_acked_packet : ? u32 ,
24+ pending_ack : bool ,
25+ received_packets : std .AutoHashMap (u32 , void ),
2226
2327 /// Connection IDs
2428 local_conn_id : []const u8 ,
@@ -50,14 +54,21 @@ pub const Connection = struct {
5054
5155 try streams .put (0 , stream0 );
5256
57+ var received_packets = std .AutoHashMap (u32 , void ).init (allocator );
58+ errdefer received_packets .deinit ();
59+
5360 return Self {
5461 .allocator = allocator ,
5562 .is_server = is_server ,
5663 .streams = streams ,
5764 .next_stream_id = initial_stream_id ,
5865 .next_packet_number = 0 ,
5966 .largest_received_packet = 0 ,
60- .largest_acked_packet = 0 ,
67+ .next_expected_packet = 0 ,
68+ .last_ack_sent = null ,
69+ .largest_peer_acked_packet = null ,
70+ .pending_ack = false ,
71+ .received_packets = received_packets ,
6172 .local_conn_id = local_conn_id ,
6273 .remote_conn_id = remote_conn_id ,
6374 };
@@ -71,6 +82,7 @@ pub const Connection = struct {
7182 self .allocator .destroy (stream .* );
7283 }
7384 self .streams .deinit ();
85+ self .received_packets .deinit ();
7486 }
7587
7688 /// Open a new bidirectional stream
@@ -146,17 +158,40 @@ pub const Connection = struct {
146158 return pn ;
147159 }
148160
149- /// Record received packet number
150- pub fn recordReceivedPacket (self : * Self , packet_number : u32 ) void {
161+ /// Record received packet number.
162+ ///
163+ /// Returns true if the packet number is new.
164+ pub fn recordReceivedPacket (self : * Self , packet_number : u32 ) ! bool {
151165 if (packet_number > self .largest_received_packet ) {
152166 self .largest_received_packet = packet_number ;
153167 }
168+
169+ if (packet_number < self .next_expected_packet ) {
170+ return false ;
171+ }
172+
173+ const gop = try self .received_packets .getOrPut (packet_number );
174+ if (gop .found_existing ) {
175+ return false ;
176+ }
177+ gop .value_ptr .* = {};
178+
179+ while (self .received_packets .remove (self .next_expected_packet )) {
180+ self .next_expected_packet += 1 ;
181+ }
182+
183+ return true ;
184+ }
185+
186+ /// Schedule an ACK for transmission.
187+ pub fn scheduleAck (self : * Self ) void {
188+ self .pending_ack = true ;
154189 }
155190
156- /// Record ACKed packet
157- pub fn recordAckedPacket (self : * Self , packet_number : u32 ) void {
158- if (packet_number > self .largest_acked_packet ) {
159- self .largest_acked_packet = packet_number ;
191+ /// Record the largest packet number the peer has ACKed from us.
192+ pub fn recordPeerAckedPacket (self : * Self , packet_number : u32 ) void {
193+ if (self . largest_peer_acked_packet == null or packet_number > self .largest_peer_acked_packet .? ) {
194+ self .largest_peer_acked_packet = packet_number ;
160195 }
161196 }
162197
@@ -179,13 +214,22 @@ pub const Connection = struct {
179214
180215 /// Check if connection needs to send ACK
181216 pub fn needsAck (self : * const Self ) bool {
182- // Send ACK if we've received packets that haven't been acked
183- return self .largest_received_packet > self .largest_acked_packet ;
217+ return self .pending_ack and (self .next_expected_packet > 0 or self .received_packets .count () > 0 );
184218 }
185219
186220 /// Get packet number to acknowledge
187221 pub fn getAckNumber (self : * const Self ) u32 {
188- return self .largest_received_packet ;
222+ std .debug .assert (self .next_expected_packet > 0 );
223+ return self .next_expected_packet - 1 ;
224+ }
225+
226+ pub fn markAckSent (self : * Self ) void {
227+ self .last_ack_sent = if (self .next_expected_packet > 0 ) self .getAckNumber () else null ;
228+ self .pending_ack = false ;
229+ }
230+
231+ pub fn getLargestPeerAckedPacket (self : * const Self ) ? u32 {
232+ return self .largest_peer_acked_packet ;
189233 }
190234};
191235
@@ -255,11 +299,83 @@ test "Connection - packet number tracking" {
255299 try testing .expectEqual (@as (u32 , 2 ), conn .nextPacketNumber ());
256300
257301 // Record received packets
258- conn .recordReceivedPacket (5 );
259- conn .recordReceivedPacket (10 );
260- conn .recordReceivedPacket (7 ); // Out of order
302+ try testing . expect ( try conn .recordReceivedPacket (5 ) );
303+ try testing . expect ( try conn .recordReceivedPacket (10 ) );
304+ try testing . expect ( try conn .recordReceivedPacket (7 ) ); // Out of order
261305
262306 try testing .expectEqual (@as (u32 , 10 ), conn .largest_received_packet );
307+ try testing .expect (! conn .needsAck ());
308+ }
309+
310+ test "Connection - ACK scheduling is explicit" {
311+ const testing = std .testing ;
312+ const allocator = testing .allocator ;
313+
314+ var conn = try Connection .init (
315+ allocator ,
316+ "local" ,
317+ "remote" ,
318+ false ,
319+ );
320+ defer conn .deinit ();
321+
322+ try testing .expect (try conn .recordReceivedPacket (0 ));
323+ try testing .expect (! conn .needsAck ());
324+
325+ conn .scheduleAck ();
326+ try testing .expect (conn .needsAck ());
327+ }
328+
329+ test "Connection - cumulative ACK tracking handles gaps" {
330+ const testing = std .testing ;
331+ const allocator = testing .allocator ;
332+
333+ var conn = try Connection .init (
334+ allocator ,
335+ "local" ,
336+ "remote" ,
337+ false ,
338+ );
339+ defer conn .deinit ();
340+
341+ try testing .expect (try conn .recordReceivedPacket (0 ));
342+ conn .scheduleAck ();
343+ try testing .expect (conn .needsAck ());
344+ try testing .expectEqual (@as (u32 , 0 ), conn .getAckNumber ());
345+ conn .markAckSent ();
346+ try testing .expect (! conn .needsAck ());
347+
348+ try testing .expect (try conn .recordReceivedPacket (2 ));
349+ conn .scheduleAck ();
350+ try testing .expect (conn .needsAck ());
351+ try testing .expectEqual (@as (u32 , 0 ), conn .getAckNumber ());
352+ conn .markAckSent ();
353+
354+ try testing .expect (try conn .recordReceivedPacket (1 ));
355+ conn .scheduleAck ();
356+ try testing .expect (conn .needsAck ());
357+ try testing .expectEqual (@as (u32 , 2 ), conn .getAckNumber ());
358+ }
359+
360+ test "Connection - peer ACK state is separate from outbound ACK generation" {
361+ const testing = std .testing ;
362+ const allocator = testing .allocator ;
363+
364+ var conn = try Connection .init (
365+ allocator ,
366+ "local" ,
367+ "remote" ,
368+ false ,
369+ );
370+ defer conn .deinit ();
371+
372+ conn .recordPeerAckedPacket (99 );
373+ try testing .expectEqual (@as (? u32 , 99 ), conn .getLargestPeerAckedPacket ());
374+
375+ try testing .expect (try conn .recordReceivedPacket (0 ));
376+ conn .scheduleAck ();
377+ try testing .expect (conn .needsAck ());
378+ try testing .expectEqual (@as (u32 , 0 ), conn .getAckNumber ());
263379}
264380
265381test "Connection - getOrCreateStream" {
0 commit comments