Files
amethyst/quic
Claude 0c847b4f69 feat(quic): wire CRYPTO retransmit per encryption level
Closes commits D + E of the deferred-follow-ups pass. With
SendBuffer's retain-until-ACK semantics in commit B and the
Crypto / ResetStream / StopSending / NewConnectionId tokens added
in commit A, the writer now records a Crypto token per CRYPTO
frame at each encryption level (Initial, Handshake) so RFC 9002
retransmit recovers lost handshake bytes.

# Writer side

QuicConnectionWriter.collectHandshakeLevelFrames returns a new
HandshakeLevelContents(frames, tokens) pair instead of a bare
frame list. For each emitted CryptoFrame it appends a matching
RecoveryToken.Crypto(level, offset, length).

QuicConnectionWriter.buildLongHeaderFromFrames now also takes the
parallel tokens list, and after encryption records a SentPacket
in the matching LevelState.sentPackets map. Initial-level rebuilds
with padding (RFC 9000 §14.1) call pnSpace.rewindOutboundForRebuild
to reuse the same PN — the second build's map insert overwrites
the prior entry, so retention reflects the final padded packet.

drainOutbound's two callsites updated to pass tokens through.

# ACK / loss dispatch

Already wired in commit A. QuicConnection.onTokensAcked routes
Crypto tokens to LevelState.cryptoSend.markAcked at the matching
level, releasing buffer memory as the contiguous low end is ACK'd.
QuicConnection.onTokensLost routes them to markLost, re-queueing
the bytes for retransmit at the same level.

# RESET_STREAM / STOP_SENDING / NEW_CONNECTION_ID

Same dispatcher-only completion. The pendingResetStream /
pendingStopSending / pendingNewConnectionId maps on QuicConnection
are populated by the loss dispatcher when those token types are
seen. :quic doesn't currently emit any of those frames (no
application code triggers stream reset, connection-ID rotation
isn't wired), so the writer never drains the maps yet —
scaffolding for future emit support. The exhaustive when in
onTokensLost / onTokensAcked is now complete: any future addition
of a new RecoveryToken variant trips the compile-time exhaustive
check, mirroring the test in RecoveryTokenTest.

# Tests added (3, all pass)

CryptoRetransmitTest:
  - handshakePacket_carriesCryptoToken_inSentPacket: ClientHello
    emission produces an Initial-level SentPacket with a Crypto
    token at offset 0 with the expected level.
  - cryptoData_lostAndRetransmittedAtSameLevel: simulate loss via
    direct dispatch, observe re-queue in cryptoSend, verify next
    drain produces a fresh Initial packet replaying the same
    offset (RFC 9000 §13.3 idempotent).
  - cryptoAck_releasesBufferAtSameLevel: ACK via onTokensAcked,
    cryptoSend's readableBytes drops to 0.

# Net result

Lost handshake bytes (ClientHello, EncryptedExtensions, Certificate,
Finished, NewSessionTicket) are now recovered automatically. The
prior 1-second-fixed-PTO placeholder in QuicConnectionDriver
(commit step 7 of the prior plan) becomes meaningfully more useful
— PTO now wakes the writer to retransmit ACTUAL data, not just
emit empty PINGs.

Full :quic test suite + nestsClient moq-lite + amethyst Android
compile all pass.

https://claude.ai/code/session_01PYYez8a6sjiakyjAxsfCEQ
2026-05-05 00:10:57 +00:00
..