transport: ACK Frequency extension — ACK_FREQUENCY (0xaf) + IMMEDIATE_ACK (0x1f)#242
Merged
Conversation
) Implements draft-ietf-quic-ack-frequency so peers (quinn / quiche / msquic all ship it) can tune our ACK cadence: - src/frames/ack_frequency.zig (new): ACK_FREQUENCY (0xaf) parse/ serialize — sequence number, ack-eliciting threshold, requested max ack delay (µs), reordering threshold — and IMMEDIATE_ACK (0x1f). 0xaf > 0x3f so the type is a 2-byte varint on the wire; both frame loops already varint-decode the type. - quic_tls.zig: advertise + parse the min_ack_delay transport parameter (0xff04de1b, MICROSECONDS). Advertised by default at 1000 µs (1 ms — our drive-tick ACK granularity; well under the 25 ms max_ack_delay we advertise). Set TransportParamsOpts.min_ack_delay_us = 0 to disable. - io.zig frame loops (server + client): 0x1f sets ack_immediate; 0xaf is sequence-gated (stale frames ignored per draft §4), malformed bodies → FRAME_ENCODING_ERROR, requested delay below our advertised min_ack_delay → PROTOCOL_VIOLATION. - ACK cadence: with no ACK_FREQUENCY received the flush path is UNCHANGED (every drive tick). Once armed, flushAllConnAppAcks holds a conn's ACK until the eliciting-packet threshold is crossed, the requested max ack delay expires, a reorder event beyond the reordering threshold occurs, or IMMEDIATE_ACK arrives. Ack-eliciting counting piggybacks on noteFrameReceived (anything but PADDING/ACK/ CONNECTION_CLOSE). The interop client keeps its eager per-batch flush — acking more often than requested is explicitly permitted by the draft. - PTO probes (both sides) ride an IMMEDIATE_ACK alongside the PING when the peer advertised min_ack_delay, so probes are answered without waiting out the peer's (possibly relaxed) delayed-ack timer. - Peer TP validation: min_ack_delay > max_ack_delay is treated defensively as not-advertised (extension disabled for that peer) rather than tearing down the handshake from the void apply path. Tests: frame codec round-trip + truncation, TP round-trip + omit-when- zero, ConnState apply/stale/violation gating, threshold + delay-timer + IMMEDIATE_ACK flush gating, reorder trigger incl. threshold=0 disable. 296/297 pass (1 pre-existing skip). Local interop smoke: handshake + transfer green with both endpoints advertising the new TP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #187.
Summary
Implements draft-ietf-quic-ack-frequency: the
min_ack_delaytransport parameter (0xff04de1b), the ACK_FREQUENCY frame (0xaf), and the IMMEDIATE_ACK frame (0x1f). quinn / quiche / msquic all ship this; until now those peers saw zquic as extension-less and fell back to the RFC 9000 baseline.What's in
src/frames/ack_frequency.zig(new): frame codec + unit tests. Note 0xaf > 0x3f → 2-byte varint type on the wire (0x40 0xaf); both 1-RTT frame loops already varint-decode types so dispatch is clean.max_ack_delaywe advertise).TransportParamsOpts.min_ack_delay_us = 0disables. Peer's value is parsed and stored; a peer advertisingmin_ack_delay > max_ack_delayis defensively treated as not-advertised rather than tearing down the handshake from inside the void TP-apply path (draft says TRANSPORT_PARAMETER_ERROR — noted inline).IMMEDIATE_ACK→ flush the pending app-ACK now.ACK_FREQUENCY→ sequence-gated apply (stale/duplicate ignored per draft §4); malformed body → FRAME_ENCODING_ERROR close; requested max ack delay below our advertisedmin_ack_delay→ PROTOCOL_VIOLATION close.flushAllConnAppAcksholds a conn's ACK until the ack-eliciting threshold is crossed, the requested delay expires, a reorder beyond the reordering threshold occurs, or IMMEDIATE_ACK arrives. The interop client keeps its eager per-batch flush (acking more often than requested is explicitly permitted by the draft).Not in (deliberate)
Sending ACK_FREQUENCY to tune the peer's cadence (an embedder-facing API). Receive-side honor is the interop-relevant half; the send half can follow when an embedder wants it.
Tests
handshake+transfergreen with both endpoints advertising the new TP.