From 2b44e9b06b5d7dabd3821d409771f56491686f96 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Apr 2026 13:20:33 +0000 Subject: [PATCH] docs(quic-plan): lock in new top-level :quic module (Option A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the Architecture section to reflect the decision to house the pure-Kotlin QUIC + HTTP/3 + WebTransport code in a new top-level Gradle module `:quic`, sibling to `:quartz`, `:commons`, `:nestsClient`. Key changes: - Module placement: new KMP module `:quic` with commonMain + jvmAndroid + jvmMain + androidMain source sets (mirrors Quartz). `:quic` takes `api(project(":quartz"))` so all crypto primitives are in-scope without re-export. - settings.gradle delta spelled out. - Package layout shifts from `nestsClient/src/jvmAndroid/...transport/quic/` to `:quic/src/commonMain/com/vitorpamplona/quic/`, with UDP socket-specific bits in `jvmAndroid/`. - Varint.kt migration called out: moves from `nestsClient/moq/Varint.kt` to `:quic` at `com.vitorpamplona.quic.Varint`. Mechanical, one commit. - Rename KwikWebTransportFactory stub → QuicWebTransportFactory (real), living in `:quic` but implementing `:nestsClient`'s existing `WebTransportFactory` interface. AudioRoomConnectionViewModel changes one ctor call. - Rationale section documenting why Option A beats putting it in `:nestsClient` (single responsibility / reusability / security boundary / test isolation / build graph / charter fit). - Phase A now explicitly includes the module-creation + Varint-migration step; test suite must stay green across the move. Timeline unchanged (17-19 weeks). Dependency story unchanged (no external libs; everything piggybacks on Quartz). https://claude.ai/code/session_013nVLALALKaHVgHm9u5Cg8D --- ...4-22-pure-kotlin-quic-webtransport-plan.md | 120 ++++++++++++++---- 1 file changed, 94 insertions(+), 26 deletions(-) diff --git a/docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md b/docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md index 7005b6f65..e2491643b 100644 --- a/docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md +++ b/docs/plans/2026-04-22-pure-kotlin-quic-webtransport-plan.md @@ -78,32 +78,94 @@ the TLS 1.3 client state machine ourselves using those primitives. ## Architecture -``` -nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/ -└── transport/ - ├── quic/ ← new: pure-Kotlin QUIC client - │ ├── crypto/ ← BC adapter (TLS over CRYPTO frames) - │ ├── packet/ ← long/short-header encode/decode + protection - │ ├── frame/ ← STREAM, ACK, CRYPTO, MAX_*, CONNECTION_CLOSE … - │ ├── stream/ ← per-stream send/recv buffers, flow control - │ ├── recovery/ ← loss detection, PTO, NewReno - │ ├── connection/ ← QuicConnection state machine - │ └── transport/ ← UDP socket + datagram pump - ├── http3/ ← new: HTTP/3 client - │ ├── frame/ ← DATA, HEADERS, SETTINGS, GOAWAY, MAX_PUSH_ID - │ ├── qpack/ ← static + dynamic tables, Huffman - │ └── Http3Connection.kt ← request/response state machine - └── webtransport/ ← new: WT layer - ├── ExtendedConnect.kt ← :protocol = webtransport - ├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni) prefix bytes - ├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843, etc. - └── KotlinWebTransportFactory.kt ← implements existing WebTransportFactory +**Module placement:** a new top-level Gradle module `:quic` sibling to +`:quartz`, `:commons`, `:nestsClient`. It's a KMP module (commonMain + an +`androidMain` + `jvmMain` pair via a shared `jvmAndroid` source set, mirroring +Quartz's pattern) so it's reusable for future non-Nests consumers +(Nostr-over-QUIC relays, desktop audio, iOS later). `:quic` takes +`api(project(":quartz"))` so the crypto primitives are available without +re-exporting. `:nestsClient` drops its `transport/` subpackage and declares +`implementation(project(":quic"))` instead. + +**settings.gradle delta:** + +```groovy +include ':quartz' +include ':commons' +include ':ammolite' +include ':quic' // NEW — pure-Kotlin QUIC + HTTP/3 + WebTransport +include ':nestsClient' // gains `implementation project(':quic')` +include ':desktopApp' +include ':cli' ``` -Naming note: the existing `KwikWebTransportFactory.kt` stub becomes -`KotlinWebTransportFactory.kt` once real. The current ViewModel wiring -(`AudioRoomConnectionViewModel`) imports the class directly, so the rename is -one change with no protocol impact. +**Package layout under `:quic`:** + +``` +quic/ +├── build.gradle.kts ← KMP module, api(project(":quartz")) +└── src/ + ├── commonMain/kotlin/com/vitorpamplona/quic/ + │ ├── Varint.kt ← migrated from nestsClient/moq/Varint.kt + │ ├── crypto/ ← HkdfExpandLabel + thin helpers + │ ├── tls/ ← TLS 1.3 client state machine + │ ├── packet/ ← long/short-header codec + protection + │ ├── frame/ ← CRYPTO, STREAM, ACK, MAX_*, CONNECTION_CLOSE… + │ ├── stream/ ← per-stream buffers + flow control + │ ├── recovery/ ← loss detection + PTO + NewReno + │ ├── connection/ ← QuicConnection state machine + │ ├── http3/ ← RFC 9114 client + │ ├── qpack/ ← RFC 9204 encoder/decoder + │ └── webtransport/ + │ ├── ExtendedConnect.kt + │ ├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni) + │ ├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843 + │ └── QuicWebTransportFactory.kt ← implements nestsClient's WebTransportFactory + ├── jvmAndroid/kotlin/com/vitorpamplona/quic/ + │ └── transport/ + │ └── UdpSocket.kt ← DatagramChannel + suspend wrapper + ├── commonTest/ + └── jvmTest/ ← RFC 8448 / RFC 9001 vector tests +``` + +**Migrations when `:quic` lands:** + +- `nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/Varint.kt` + moves to `:quic` at `com.vitorpamplona.quic.Varint`. The existing MoQ codec + (Phase 3c) imports the new path. Mechanical change — one commit. +- The `nestsClient/transport/` package (`WebTransportSession` + + `FakeWebTransport` + `KwikWebTransportFactory` stub) stays in `:nestsClient` + because that's the seam `:nestsClient`'s audio pipeline talks to. The real + implementation `QuicWebTransportFactory` lives in `:quic` and implements + `nestsClient.transport.WebTransportFactory` — `:nestsClient` swaps its + constructor call from `KwikWebTransportFactory()` to + `QuicWebTransportFactory()`, nothing else changes upstream. + +**Why a dedicated module (Option A):** + +- **Single responsibility.** `:quic` is a transport library; `:nestsClient` + is an audio-room client. They have nothing conceptually in common beyond + nests happens to run over WebTransport. +- **Reusability.** Future Nostr-over-QUIC relay work, any Kotlin project that + wants MoQ, desktop / iOS targets all pull `:quic` directly. Potentially + extractable to an open-source library later — pure-Kotlin QUIC barely + exists in the JVM ecosystem, so this would be genuinely valuable beyond + Amethyst. +- **Security boundary.** QUIC + TLS is the code you want smallest, isolated, + and auditable without reading audio-room code around it. A dedicated module + enforces the boundary. +- **Test isolation.** `:quic` tests run on `commonTest` / `jvmTest` — fast + feedback. They never touch `:nestsClient`'s fakes. +- **Build graph.** `:quic` has no Android-framework dependencies (no + `MediaCodec`, no `AudioTrack`), so it compiles clean as a pure KMP lib and + targets iOS / desktop trivially when those arrive. +- **Charter.** Quartz is labeled "Nostr protocol only, no UI" in the project's + CLAUDE.md. QUIC isn't Nostr; it doesn't fit Quartz's charter. + +Naming note: the existing `KwikWebTransportFactory.kt` stub in `:nestsClient` +is renamed to reference the real implementation `QuicWebTransportFactory` in +`:quic`. `AudioRoomConnectionViewModel` updates its constructor call in one +line, no protocol impact. ## Phase breakdown (one developer, full-time estimates) @@ -111,11 +173,17 @@ Each phase ends with green commonTest tests that exercise its layer in isolation plus an integration test against the next layer up. ### Phase A — Foundations (1 week) -- UDP socket abstraction (`DatagramChannel`, suspend wrapper, single-receive loop). +- Create new `:quic` module (KMP, `api(project(":quartz"))`), register in + `settings.gradle`, port `Varint.kt` from `:nestsClient` and update MoQ + imports, add `:nestsClient` → `implementation(project(":quic"))` wire. +- UDP socket abstraction (`DatagramChannel`, suspend wrapper, single-receive + loop) in `jvmAndroid`. - Connection ID generation (cryptographically random, 8-byte source IDs). - Packet number space tracking (Initial, Handshake, Application). - Largest-acked / next-pn tracking per space. -- **Tests:** varint already done; add CID + packet-number-space unit tests. +- **Tests:** varint tests migrate with the file; add CID + packet-number-space + unit tests. Full MoQ + nestsClient test suite stays green after the + Varint migration. ### Phase B — TLS 1.3 client state machine (3 weeks)