docs(quic-plan): lock in new top-level :quic module (Option A)
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
This commit is contained in:
@@ -78,32 +78,94 @@ the TLS 1.3 client state machine ourselves using those primitives.
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
**Module placement:** a new top-level Gradle module `:quic` sibling to
|
||||||
nestsClient/src/jvmAndroid/kotlin/com/vitorpamplona/nestsclient/
|
`:quartz`, `:commons`, `:nestsClient`. It's a KMP module (commonMain + an
|
||||||
└── transport/
|
`androidMain` + `jvmMain` pair via a shared `jvmAndroid` source set, mirroring
|
||||||
├── quic/ ← new: pure-Kotlin QUIC client
|
Quartz's pattern) so it's reusable for future non-Nests consumers
|
||||||
│ ├── crypto/ ← BC adapter (TLS over CRYPTO frames)
|
(Nostr-over-QUIC relays, desktop audio, iOS later). `:quic` takes
|
||||||
│ ├── packet/ ← long/short-header encode/decode + protection
|
`api(project(":quartz"))` so the crypto primitives are available without
|
||||||
│ ├── frame/ ← STREAM, ACK, CRYPTO, MAX_*, CONNECTION_CLOSE …
|
re-exporting. `:nestsClient` drops its `transport/` subpackage and declares
|
||||||
│ ├── stream/ ← per-stream send/recv buffers, flow control
|
`implementation(project(":quic"))` instead.
|
||||||
│ ├── recovery/ ← loss detection, PTO, NewReno
|
|
||||||
│ ├── connection/ ← QuicConnection state machine
|
**settings.gradle delta:**
|
||||||
│ └── transport/ ← UDP socket + datagram pump
|
|
||||||
├── http3/ ← new: HTTP/3 client
|
```groovy
|
||||||
│ ├── frame/ ← DATA, HEADERS, SETTINGS, GOAWAY, MAX_PUSH_ID
|
include ':quartz'
|
||||||
│ ├── qpack/ ← static + dynamic tables, Huffman
|
include ':commons'
|
||||||
│ └── Http3Connection.kt ← request/response state machine
|
include ':ammolite'
|
||||||
└── webtransport/ ← new: WT layer
|
include ':quic' // NEW — pure-Kotlin QUIC + HTTP/3 + WebTransport
|
||||||
├── ExtendedConnect.kt ← :protocol = webtransport
|
include ':nestsClient' // gains `implementation project(':quic')`
|
||||||
├── WtStreamType.kt ← 0x41 (bidi) / 0x54 (uni) prefix bytes
|
include ':desktopApp'
|
||||||
├── WtCapsule.kt ← WT_SESSION_CLOSE = 0x2843, etc.
|
include ':cli'
|
||||||
└── KotlinWebTransportFactory.kt ← implements existing WebTransportFactory
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Naming note: the existing `KwikWebTransportFactory.kt` stub becomes
|
**Package layout under `:quic`:**
|
||||||
`KotlinWebTransportFactory.kt` once real. The current ViewModel wiring
|
|
||||||
(`AudioRoomConnectionViewModel`) imports the class directly, so the rename is
|
```
|
||||||
one change with no protocol impact.
|
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)
|
## 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.
|
plus an integration test against the next layer up.
|
||||||
|
|
||||||
### Phase A — Foundations (1 week)
|
### 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).
|
- Connection ID generation (cryptographically random, 8-byte source IDs).
|
||||||
- Packet number space tracking (Initial, Handshake, Application).
|
- Packet number space tracking (Initial, Handshake, Application).
|
||||||
- Largest-acked / next-pn tracking per space.
|
- 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)
|
### Phase B — TLS 1.3 client state machine (3 weeks)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user