|
| 1 | +# Protocol-Agnostic QUIC Transport Layer |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Remove all HTTP/3 protocol knowledge from the QUIC transport layer. Replace `QuicStreamKind` enum with opaque `long streamTypeValue` flowing through transport. The transport distinguishes only bidirectional (request) vs unidirectional (typed) streams. Protocol interpretation happens exclusively in `Http30ConnectionStage`. |
| 6 | + |
| 7 | +## Core Concepts |
| 8 | + |
| 9 | +- **Request streams**: bidirectional, identified by `streamTypeValue = -1` (sentinel) |
| 10 | +- **Typed streams**: unidirectional, identified by their wire byte value (opaque `long`) |
| 11 | +- **TypedStreamDescriptor**: configuration record passed to transport at construction — `(long StreamTypeValue, long SyntheticStreamId)` |
| 12 | +- Transport opens typed streams eagerly from configuration, without knowing what the values mean |
| 13 | + |
| 14 | +## New Transport Type |
| 15 | + |
| 16 | +```csharp |
| 17 | +internal readonly record struct TypedStreamDescriptor(long StreamTypeValue, long SyntheticStreamId); |
| 18 | +``` |
| 19 | + |
| 20 | +Http3 layer provides at construction: |
| 21 | +```csharp |
| 22 | +[new(0x00, -2), new(0x02, -3)] // Control, QpackEncoder — transport doesn't know names |
| 23 | +``` |
| 24 | + |
| 25 | +## Typed Stream State |
| 26 | + |
| 27 | +Replaces the six hardcoded fields (`_controlHandle`, `_encoderHandle`, `_pendingControlItems`, `_pendingEncoderItems`, `_controlStreamId`, `_encoderStreamId`): |
| 28 | + |
| 29 | +```csharp |
| 30 | +private sealed class TypedStreamState |
| 31 | +{ |
| 32 | + public ConnectionHandle? Handle; |
| 33 | + public readonly Queue<NetworkBuffer> PendingItems = new(); |
| 34 | + public long StreamId; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +Stored in `Dictionary<long, TypedStreamState> _typedStreams` keyed by `streamTypeValue`. |
| 39 | + |
| 40 | +## File-by-File Changes |
| 41 | + |
| 42 | +### Delete |
| 43 | + |
| 44 | +- `QuicStreamKind.cs` — enum and `QuicStreamKindMapper` removed entirely |
| 45 | + |
| 46 | +### QuicConnectionHandle |
| 47 | + |
| 48 | +- `OpenStreamAsLeaseAsync(bool bidirectional)` — no stream type knowledge, just direction |
| 49 | +- `InboundStream(ConnectionLease, long StreamTypeValue, long StreamId)` — raw wire value |
| 50 | +- `AcceptInboundStreamAsLeaseAsync` — reads wire byte, returns as `long`, no interpretation, accepts all streams |
| 51 | +- Remove `MapStreamKind` — replace with `bidirectional ? Bidirectional+GetStream : WriteOnly+GetUnidirectional` |
| 52 | + |
| 53 | +### IQuicTransportEvent |
| 54 | + |
| 55 | +- `TypedLeaseAcquired(ConnectionLease, long StreamTypeValue, long StreamId)` |
| 56 | +- `InboundStreamReady` carries `InboundStream` which now has `long StreamTypeValue` |
| 57 | + |
| 58 | +### QuicPumpManager |
| 59 | + |
| 60 | +- `StartInboundPump(handle, long streamTypeValue, key, gen, streamId)` |
| 61 | +- `PumpAsync`: sets `h3Buf.StreamTypeValue = streamTypeValue` instead of `ApplyToBuffer` |
| 62 | +- Close signal: only for request streams (`streamTypeValue < 0`) |
| 63 | + |
| 64 | +### QuicStreamRouter |
| 65 | + |
| 66 | +- `RouteTaggedItem(buffer, long streamTypeValue, Dictionary<long, TypedStreamState> typedStreams)` — looks up by value, falls through to request routing for unknown/request type |
| 67 | +- Remove `QuicStreamKind` from all method signatures |
| 68 | + |
| 69 | +### QuicTransportStateMachine |
| 70 | + |
| 71 | +- Constructor receives `TypedStreamDescriptor[]`, initializes `_typedStreams` dictionary |
| 72 | +- Remove constants `ControlStreamSyntheticId`, `QpackEncoderStreamSyntheticId`, `QpackDecoderStreamSyntheticId` |
| 73 | +- `OnRequestLeaseAcquired` — iterates descriptors to open typed streams |
| 74 | +- `OnTypedLeaseAcquired(lease, long streamTypeValue, long streamId)` — looks up in `_typedStreams` |
| 75 | +- `OnInboundStreamReady` — maps `streamTypeValue` to synthetic ID via descriptors (or real stream ID for unconfigured types) |
| 76 | +- `HandlePush` — reads `StreamTypeValue` from buffer for routing instead of `Http3StreamType` |
| 77 | + |
| 78 | +### Http3NetworkBuffer (Internal/Messages.cs) |
| 79 | + |
| 80 | +- Add `public long StreamTypeValue { get; set; } = -1;` |
| 81 | +- `Http3StreamType StreamType` stays as plain settable property (no auto-conversion) |
| 82 | +- Transport only touches `StreamTypeValue`; protocol layer uses both |
| 83 | + |
| 84 | +### Http30ConnectionStage (Protocol Layer) |
| 85 | + |
| 86 | +- **Inbound**: maps `StreamTypeValue` to `Http3StreamType` in `HandleTaggedStreamData` |
| 87 | +- **Outbound**: sets `StreamTypeValue` on buffers (0x00 for Control, 0x02 for Encoder, 0x03 for Decoder) |
| 88 | +- This is the single place where wire values get protocol meaning |
| 89 | + |
| 90 | +### QpackStreamHandler |
| 91 | + |
| 92 | +- Sets `StreamTypeValue` on outbound buffers (instead of / alongside `StreamType`) |
| 93 | + |
| 94 | +## What Stays the Same |
| 95 | + |
| 96 | +- `Http3StreamType` enum stays (protocol-internal concern) |
| 97 | +- `Http3NetworkBuffer.StreamType` stays (used by protocol layer) |
| 98 | +- Synthetic stream IDs stay (configured instead of hardcoded) |
| 99 | +- All buffering/flush logic stays (same patterns, keyed by `long` instead of enum) |
| 100 | + |
| 101 | +## Test Impact |
| 102 | + |
| 103 | +- Transport specs (`QuicPumpManagerSpec`, `QuicStreamRouterSpec`, `QuicStreamRouterEnhancedSpec`, `QuicTransportStateMachineSpec`, `QuicTransportStateMachineLifecycleSpec`, `QuicConnectionHandleSpec`, `QuicConnectionManagerSpec`) — update to use `long` values instead of `QuicStreamKind` |
| 104 | +- Protocol specs using `Http3StreamType` — unchanged |
0 commit comments