Files
amethyst/quic
Vitor Pamplona 0920aba2b8 fix(quic): issue spare source CIDs to peer post-handshake-confirmed
RFC 9000 §5.1.1 + §19.15: a client SHOULD advertise spare source
CIDs to the peer via NEW_CONNECTION_ID frames so the peer has
DCIDs available for path migration / NAT rebind. Strict server
stacks (quic-go, picoquic, msquic, mvfst) refuse to validate a new
client path until they have a spare DCID — server log shows
"skipping validation of new path … since no connection ID is
available". Quinn migrates on src-port-change alone, which is why
rebind-port / rebind-addr passed against quinn pre-fix and failed
against the others.

Adds:
  - IssuedSourceConnectionIdEntry (data class) — sequence + CID +
    stateless-reset token tuple.
  - QuicConnection.issuedSourceConnectionIds (LinkedHashMap) — pool
    of currently-active issued SCIDs, seeded with seq=0 (the
    initial CID, no token because the stateless_reset_token TP is
    server-only per RFC 9000 §18.2).
  - QuicConnection.issueOwnConnectionIdsLocked(count) — drives the
    initial issuance once handshake is confirmed; appends recovery
    tokens to pendingOwnNewConnectionIdEmits for the writer to emit.
  - QuicConnection.issueOwnReplacementSourceCidLocked() — top-up
    after each peer RETIRE_CONNECTION_ID so the pool stays at
    full capacity for repeated migrations.
  - QuicConnectionWriter — once handshakeConfirmed flips, calls
    issueOwnConnectionIdsLocked with `peer.activeConnectionIdLimit
    - 1` (cap 7); drains pendingOwnNewConnectionIdEmits as fresh
    NEW_CONNECTION_ID frames; the existing pendingNewConnectionId
    map continues to handle loss-recovery retransmits.
  - applyPeerRetireConnectionIdLocked — three cases: in-pool seq
    is freed and replaced; below-next-seq missing seq is benign
    retransmit; above-next-seq is PROTOCOL_VIOLATION (peer
    retiring something we never advertised).

Verified against the live interop runner:
  - quic-go rebind-port: 2/2 (was 0/N — Task 2). Now passes in ~64s.
  - quic-go rebind-addr: 2/2 (was 0/N). Now passes in ~125-138s.
  - picoquic rebind-port: 2/2.
  - picoquic rebind-addr: 1/2 (flaky — separate investigation;
    one run the connection migrates correctly, the other times
    out at 110s; not blocking the rebind-port / quic-go win).
  - picoquic connectionmigration: 0/2 still failing (the runner's
    "active migration" testcase exercises a more sophisticated
    migration shape — TBD; tracked separately).

Tests: IssuedSourceConnectionIdTest (5 cases pinning the
post-handshake emission, peer-RETIRE replacement, retransmit
tolerance, and protocol-violation closure for above-next-seq retires).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:40:36 -04:00
..