Skip to content

Commit 90d153a

Browse files
committed
Do not fully clear ACK ranges when ACK is acknowledged
When a packet containing an ACK is acknowledged, we are allowed to discard ACK ranges up to and including the highest acknowledged packet in the sent ACK: https://datatracker.ietf.org/doc/html/rfc9000#ack-tracking However, doing so prevents us from reporting a gap if the immediately following packet(s) are lost. We therefore retain at least the highest acknowledged packet in our ACK ranges. Fixes: aiortc#495.
1 parent ff3281f commit 90d153a

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

src/aioquic/quic/connection.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -2347,8 +2347,13 @@ def _on_ack_delivery(
23472347
"""
23482348
Callback when an ACK frame is acknowledged or lost.
23492349
"""
2350-
if delivery == QuicDeliveryState.ACKED:
2351-
space.ack_queue.subtract(0, highest_acked + 1)
2350+
if delivery == QuicDeliveryState.ACKED and highest_acked > 0:
2351+
# When a packet containing an ACK is acknowledged, we *could* discard ACK
2352+
# ranges up to and including the highest acknowledged packet. However, doing
2353+
# so would prevent us from reporting a gap if the very next packet(s) are
2354+
# lost. We therefore retain the highest acknowledged packet in our ACK
2355+
# ranges.
2356+
space.ack_queue.subtract(0, highest_acked)
23522357

23532358
def _on_connection_limit_delivery(
23542359
self, delivery: QuicDeliveryState, limit: Limit

tests/test_connection.py

+50
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,56 @@ def test_receive_datagram_retry_wrong_integrity_tag(self):
12871287
)
12881288
self.assertEqual(drop(client), 0)
12891289

1290+
def test_handle_ack_delivery(self):
1291+
data_chunk = b"h" * 1000
1292+
1293+
with client_and_server() as (client, server):
1294+
# Check handshake completed.
1295+
self.check_handshake(client=client, server=server)
1296+
1297+
# Check the initial state.
1298+
server_space = server._spaces[tls.Epoch.ONE_RTT]
1299+
self.assertEqual(list(server_space.ack_queue), [range(3, 5)])
1300+
1301+
# Client packet 5: stream data => received.
1302+
stream_id = client.get_next_available_stream_id()
1303+
client.send_stream_data(stream_id, data_chunk)
1304+
self.assertEqual(transfer(client, server), 1)
1305+
self.assertEqual(list(server_space.ack_queue), [range(3, 6)])
1306+
1307+
# Client packet 6: stream data => lost.
1308+
client.send_stream_data(stream_id, data_chunk)
1309+
self.assertEqual(drop(client), 1)
1310+
self.assertEqual(list(server_space.ack_queue), [range(3, 6)])
1311+
1312+
# Client packet 7: stream data => received.
1313+
client.send_stream_data(stream_id, data_chunk)
1314+
self.assertEqual(transfer(client, server), 1)
1315+
# The server is aware of a gap, packet 6 is missing.
1316+
self.assertEqual(list(server_space.ack_queue), [range(3, 6), range(7, 8)])
1317+
1318+
# Server sends ACK.
1319+
self.assertEqual(transfer(server, client), 1)
1320+
1321+
# Client packet 8: sends stream data => lost.
1322+
client.send_stream_data(stream_id, data_chunk)
1323+
self.assertEqual(drop(client), 1)
1324+
self.assertEqual(list(server_space.ack_queue), [range(3, 6), range(7, 8)])
1325+
1326+
# Server sends ACK + PING.
1327+
server.send_ping(0)
1328+
self.assertEqual(transfer(server, client), 1)
1329+
1330+
# Client packet 9: ACK + PING => received.
1331+
client.send_stream_data(stream_id, data_chunk)
1332+
self.assertEqual(transfer(client, server), 1)
1333+
# The server discards some ACK ranges, but keeps enough information
1334+
# to note packet 8 is missing.
1335+
self.assertEqual(list(server_space.ack_queue), [range(7, 8), range(9, 10)])
1336+
1337+
# Server sends ACK.
1338+
self.assertEqual(transfer(server, client), 1)
1339+
12901340
def test_handle_ack_frame_ecn(self):
12911341
client = create_standalone_client(self)
12921342

0 commit comments

Comments
 (0)