ea15a9afa1
Plumbs sent-packet retention into the writer. From now on every
Application packet emission stores a SentPacket in
`LevelState.sentPackets`, keyed by packet number, carrying:
- the packet number (from `pnSpace.allocateOutbound()`)
- the writer's `nowMillis` send time
- whether the packet is ack-eliciting (RFC 9000 §13.2.1)
- the encrypted on-wire size (or 0 if encrypt threw)
- a list of RecoveryTokens — one per retransmittable frame in the
packet (Ack token for ACK frames; MaxStreamsUni / MaxStreamsBidi
/ MaxData / MaxStreamData for the corresponding flow-control
extensions)
`appendFlowControlUpdates` now takes a parallel `tokens: MutableList`
and writes lock-step with `frames`. The writer's existing semantics
are unchanged — same frames go on the wire, same advertised-cap
bookkeeping. Step 2 only adds the retention; nothing reads
`sentPackets` yet (steps 3–6 do that).
Order of operations on packet emission:
1. Allocate packet number
2. runCatching the encrypt step
3. Record SentPacket regardless of encrypt outcome (sizeBytes=0 if
it threw — the bookkeeping survives so loss detection can later
declare the gap lost on the time threshold)
4. Re-throw the encrypt exception so the driver loop sees the same
error it did before this change
Tests added (3, all pass):
- writer_records_sent_packet_with_max_streams_uni_token: cross
half-window, drain, observe a SentPacket whose tokens contain
MaxStreamsUni with maxStreams matching advertisedMaxStreamsUni
- ack_only_outbound_records_sent_packet_with_ack_token_and_not_ack_eliciting:
pending ACK only, observe a SentPacket with single Ack token and
ackEliciting=false
- successive_drains_record_distinct_packet_numbers: two drains
record disjoint PN sets
Full :quic test suite passes (no regressions). nestsClient moq-lite
tests pass.
https://claude.ai/code/session_01PYYez8a6sjiakyjAxsfCEQ