Commit Graph

9 Commits

Author SHA1 Message Date
Claude da3e77d33e test(quic): RFC 9001 §A.2 full client Initial decrypt interop vector
Add the canonical RFC 9001 Appendix A.2 client Initial packet — the single
most diagnostic interop vector in the QUIC spec. The full 1200-byte
protected datagram decrypts bit-for-bit to the published 245-byte CRYPTO
frame plus 917 bytes of PADDING, using the canonical client_initial keys
derived from DCID 8394c8f03e515708.

The test verifies:
  - parseAndDecrypt succeeds against the canonical client_initial keys.
  - Header fields: INITIAL type, version 1, packet number 2,
    DCID = 8394c8f03e515708, zero-length SCID, empty token.
  - Plaintext payload size = 1162 bytes (1182 length field - 4 PN - 16 tag).
  - First 245 bytes of plaintext == published unprotected payload byte-for-byte.
  - Remaining 917 bytes are all PADDING (0x00).
  - Frame decoder picks the leading CRYPTO frame at offset 0.
  - First byte of CRYPTO body is TLS ClientHello (0x01).

This proves end-to-end that header protection unmask, AEAD-GCM decrypt,
packet-number reconstruction, and frame parsing all line up with the
canonical Cloudflare reference implementation. Combined with the §A.5
ChaCha20 vector and §A.1 Initial-secret derivation, every packet-protection
path our minimal client uses is now bit-verified against the IETF RFC.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 21:33:04 +00:00
Claude 90f9cca37d test(quic): add RFC 9204 QPACK §B.1 + RFC 9001 §A.1 server-HP vectors
Add the QPACK + Header Protection vectors from the IETF RFCs that are
most diagnostic for cross-implementation interop.

- RFC 9204 Appendix B.1 — `:path = /index.html` literal-with-name-reference
  encode + decode. Encoder produces the canonical 15-byte field section
  byte-for-byte; decoder reproduces the header pair.
- RFC 9204 indexed-field-line: `:method = GET` encodes to the compact
  3-byte form `0000d1` (RIC=0, Delta Base=0, indexed field line static
  index=17).
- Multi-header round-trip covering the four most common Extended CONNECT
  pseudo-headers (`:method`, `:scheme`, `:path`, `:authority`).
- RFC 9001 §A.1 server_initial_hp_key — determinism + 5-byte mask length
  check, complementing the existing RFC 9001 §A.1 derivation tests.

Combined with the existing RFC 7541 Huffman corpus and RFC 9001 §A.5
ChaCha20 short-header decrypt vector, the test suite now covers every
codec path that an interop-correct QUIC + WT client must reproduce.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 21:27:12 +00:00
Claude 0cfbdf5589 test(quic): RFC 7541 Huffman + RFC 9001 §A.5 ChaCha20 interop vectors
Add canonical interop fixtures from the IETF RFCs:

- RFC 7541 Appendix C — 8 HPACK / QPACK Huffman decode vectors covering
  short tokens ("www.example.com", "no-cache", "custom-key", "custom-value",
  "302", "private"), the long date-string ("Mon, 21 Oct 2013 20:13:21 GMT"),
  and the URL ("https://www.example.com"). Every byte of the 256-symbol
  Huffman table is exercised across the corpus.
- RFC 9001 §A.5 — ChaCha20-Poly1305 short-header decrypt. The protected
  packet 4cfe4189655e5cd55c41f69080575d7999c25a5bfb decodes byte-for-byte
  to a single PING frame (0x01) with packet number 654_360_564 using the
  RFC's published key, iv, and hp_key.
- AEAD nonce derivation against the same vector — verifies our iv XOR
  direction matches the canonical e0459b3474bdd0e46d417eb0.

These two suites are the single most diagnostic cross-implementation
checks for the QPACK Huffman path and the ChaCha20 packet protection
path. They complement the existing RFC 9000 §A.1 varint, RFC 9001 §A.1
Initial-secret, and RFC 8448 §3 TLS-derived-secret vectors.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 21:22:49 +00:00
Claude 46742636c7 feat(quic): Phase L — wire QuicWebTransportFactory into nestsClient
Replace the Kwik stub in :nestsClient with a pure-Kotlin QUIC + WebTransport
adapter built on top of :quic.

- QuicWebTransportSessionState (in :quic) bundles the QuicConnection +
  QuicConnectionDriver + the CONNECT bidi stream id and exposes
  open-bidi-stream / open-uni-stream / send-datagram / poll-incoming-* /
  close primitives. Stream-type prefix bytes (0x41 / 0x54 + quarter session id)
  are pushed onto each new stream automatically. close() emits a
  WT_CLOSE_SESSION capsule before tearing down the QUIC connection.
- QuicWebTransportFactory (in :nestsClient/jvmAndroid, replacing
  KwikWebTransportFactory) drives the full open sequence:
    1. UDP connect + QuicConnection.start
    2. wait until handshake completes (Status.CONNECTED)
    3. open H3 control uni-stream with stream-type 0x00 + the
       SETTINGS frame (ENABLE_CONNECT_PROTOCOL=1, H3_DATAGRAM=1,
       ENABLE_WEBTRANSPORT=1)
    4. open the Extended CONNECT bidi: HEADERS frame carrying
       :method=CONNECT, :protocol=webtransport, :scheme=https,
       :authority, :path, optional Authorization: Bearer
    5. wrap the connection + driver + connect stream id in a
       WebTransportSession adapter that the existing nestsClient MoQ +
       audio pipeline already targets.
- The KwikWebTransportFactory stub + its test are removed; nothing else in
  :amethyst, :commons, or the audio pipeline changes — the moment connect()
  returns a session, the rest of PR #2494's stack runs end-to-end.
- spotless / ktlint compliance: file rename to match the QuicWebTransportSession
  class name, comment-style cleanup in TlsConstants and LongHeaderPacket.

Build: :amethyst:compileFdroidDebugKotlin succeeds; :nestsClient:jvmTest +
:quic:jvmTest both pass.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 21:03:54 +00:00
Claude cceb5bfe96 feat(quic): Phase I-K — HTTP/3, QPACK, WebTransport framing
Layer the WebTransport-over-HTTP/3 stack on top of QUIC:

- HTTP/3 frame and stream-type identifiers (RFC 9114) plus the WebTransport
  draft additions (stream type 0x41 for client-bidi, 0x54 for client-uni).
- HTTP/3 Settings frame codec advertising the three settings nests requires:
  ENABLE_CONNECT_PROTOCOL=1, H3_DATAGRAM=1, ENABLE_WEBTRANSPORT=1.
- QPACK static table (RFC 9204 Appendix A — all 99 entries) plus pre-built
  name→index and (name,value)→index maps for encoder lookup.
- QPACK prefixed-integer codec (RFC 7541 §5.1).
- QPACK literal-only encoder: indexed-static, literal-with-static-name-ref,
  and literal-with-literal-name field-line shapes — no dynamic table inserts
  on the encoder side, so we always emit Required Insert Count = 0 and
  Delta Base = 0.
- QPACK decoder supporting indexed-static + literal-with-static-name-ref +
  literal-with-literal-name. Throws on dynamic-table references (we
  advertise QPACK_MAX_TABLE_CAPACITY=0).
- QPACK Huffman decoder (RFC 7541 Appendix B); the encoder always emits
  Huffman=0 literal strings.
- WebTransport capsule encoder (WT_CLOSE_SESSION = 0x2843).
- WebTransport datagram framing — quarter-stream-id varint prefix per
  RFC 9297 + draft-ietf-webtrans-http3.
- WebTransport stream type prefixes for client-bidi (0x41) and client-uni
  (0x54), each followed by the quarter session id.
- ExtendedConnect builder for the `:method=CONNECT, :protocol=webtransport`
  request headers and HEADERS frame body.
- QuicConnectionDriver wraps a UdpSocket + QuicConnection in coroutines
  for the read/send loops.

Round-trip tests: QPACK encode → decode preserves header lists for all
three field-line shapes plus the WebTransport extended CONNECT request.
WT datagram framing round-trips with both zero and non-zero session ids.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 20:56:35 +00:00
Claude 52ab84acfe feat(quic): Phase D-H — QuicConnection orchestrator
Wire together TLS, packet codec, and stream buffers into a single client
QuicConnection that drives the handshake and 1-RTT exchange.

- QuicConnection owns per-encryption-level state (LevelState): packet number
  space, ACK tracker, CRYPTO send + receive buffers, send + receive packet
  protection. Initial keys are installed at construction from a random DCID.
- TlsSecretsListener inside the connection installs handshake + application
  keys as the TLS state machine derives them.
- AckTracker maintains a sorted disjoint-range list of received packet
  numbers and emits RFC 9000 §19.3-shaped ACK frames newest-first.
- SendBuffer + per-stream QuicStream allow application code to enqueue bytes
  and FIN; the connection's writer drains them into STREAM frames.
- QuicConnectionParser dispatches inbound CRYPTO/STREAM/DATAGRAM/MAX_*/ACK/
  CONNECTION_CLOSE frames into the right state. Coalesced packets in a single
  datagram are demultiplexed.
- QuicConnectionWriter builds outbound datagrams that coalesce Initial +
  Handshake + 1-RTT packets, pads Initial datagrams to 1200 bytes, and
  drains stream send queues round-robin under a soft MTU budget.
- TransportParameters codec covers all RFC 9000 §18.2 + RFC 9221 fields and
  is exchanged via the TLS quic_transport_parameters extension.
- PacketProtectionBuilder maps a TLS traffic secret + cipher suite to the
  AEAD + HP triple via RFC 9001's `quic key`/`quic iv`/`quic hp` labels.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 20:50:26 +00:00
Claude ac53853057 feat(quic): Phase C — long-header packets, frames, short-header packets
End-to-end packet codec for QUIC v1 packets:

- Long-header packet builder + parser (RFC 9000 §17.2) with packet-number
  encoding, header protection (AES-ECB sample mask), and AEAD-GCM payload
  protection. Initial packets carry the optional token field.
- Short-header (1-RTT) packet builder + parser with implicit DCID length.
- Stream reassembly buffer that coalesces out-of-order, overlapping chunks
  into a contiguous prefix; consumed bytes are dropped, future overlaps
  are deduplicated.
- Stream-id helpers (RFC 9000 §2.1) — client/server, bidi/uni discrimination.
- Frame codec for the minimal subset MoQ exercises: PADDING, PING, ACK,
  ACK_ECN, CRYPTO, STREAM (all OFF/LEN/FIN flag combos), MAX_DATA,
  MAX_STREAM_DATA, MAX_STREAMS, NEW_CONNECTION_ID, CONNECTION_CLOSE
  (transport + app), HANDSHAKE_DONE, DATAGRAM (RFC 9221).

Round-trip test against RFC 9001 Appendix A.1's canonical client DCID
encrypts an Initial packet with the canonical protection material, then
decrypts it from the wire bit-for-bit. A wrong-key decrypt returns null
(silent drop per RFC 9001 §5.5). ReceiveBuffer reorders, deduplicates,
coalesces, and drops already-consumed prefixes correctly.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 17:39:06 +00:00
Claude 692b034566 feat(quic): Phase B — TLS 1.3 client on Quartz primitives
Implement a TLS 1.3 client state machine that drives the QUIC handshake using
only Quartz's existing crypto. No BouncyCastle dependency.

- HKDF-Expand and HKDF-Expand-Label upstreamed to Quartz's Hkdf class with
  RFC 5869 + RFC 8448 test vectors covering them.
- :quic crypto stack: AEAD (AES-128-GCM via Quartz's AESGCM, ChaCha20-Poly1305
  via Quartz's pure-Kotlin impl), header protection (AES-ECB via JCA single
  block + ChaCha20 keystream), QUIC Initial-secret derivation matching
  RFC 9001 Appendix A.1 bit-for-bit.
- TLS 1.3 transcript hash, key schedule (early/handshake/master + per-direction
  client/server traffic secrets), Finished MAC.
- ClientHello + extension encoders carrying SNI, supported_versions=[TLS 1.3],
  supported_groups=[X25519], signature_algorithms covering ECDSA/RSA-PSS/Ed25519,
  X25519 key_share, psk_dhe_ke, ALPN=[h3], and the QUIC transport_parameters
  extension.
- ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished
  parsers. The state machine handles the certificate path and the PSK-style
  no-cert path; certificate validation is wired through a CertificateValidator
  SPI (real impl lands in Phase L).
- Transport parameters codec covering all RFC 9000 §18.2 + RFC 9221 fields.
- QuicWriter/QuicReader buffer helpers shared across the rest of the stack.

Round-trip test: a minimal in-process TLS server built from the same primitives
drives a full ClientHello → ServerHello → EE → Finished → client Finished
exchange. Both sides reach handshake-complete and agree bit-for-bit on the
handshake & application traffic secrets. ALPN + transport parameters round-trip
through EncryptedExtensions cleanly.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 17:33:54 +00:00
Claude 2d541c6fd4 feat(quic): Phase A — module foundations
Create the new :quic Gradle module (KMP, api(project(":quartz"))) and migrate
the QUIC varint codec out of :nestsClient where it was incidentally living.
Add the connection-ID, packet-number-space, and UDP socket primitives that
the rest of the QUIC client will build on.

Layer-by-layer plan in docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md.

- New :quic module wired into settings.gradle, with commonMain + jvmAndroid
  source sets mirroring :quartz's structure.
- Varint moves from com.vitorpamplona.nestsclient.moq to com.vitorpamplona.quic;
  MoqBuffer/MoqCodec updated to import the new path.
- ConnectionId enforces the 0..20 byte length range and ships a randomizer
  backed by Quartz's RandomInstance.
- PacketNumberSpaceState tracks per-space outbound allocation + largest-received
  tracking, and implements the RFC 9000 §A.3 truncated-PN decode formula plus
  the §17.1 minimum encode-length picker.
- UdpSocket is an expect class with a connected DatagramChannel actual on
  jvmAndroid using Dispatchers.IO (no Selector — one socket per connection).

All 12 tests pass on jvmTest. RFC 9000 §A.1 varint vectors and §A.3 truncated-PN
vector match bit-for-bit.

https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
2026-04-25 17:19:02 +00:00