|
| 1 | +# MOQ Lite 04 protocol layer |
| 2 | + |
| 3 | +Status: done |
| 4 | + |
| 5 | +## Problem Statement |
| 6 | + |
| 7 | +`moqx` has a protocol-neutral QUIC transport foundation and now has initial MOQ |
| 8 | +Lite draft-04 message structs plus generic `MOQX.Codec` contracts. The next |
| 9 | +work is to turn those building blocks into a real MOQ Lite protocol layer. |
| 10 | + |
| 11 | +The transport PRD intentionally keeps "Full MOQ Lite implementation" out of |
| 12 | +scope. This PRD tracks the upper-layer work: MOQ Lite wire codecs, stream |
| 13 | +framing, and the protocol state machine that runs above `MOQX.Transport`. |
| 14 | + |
| 15 | +Relevant draft facts for this scope: |
| 16 | + |
| 17 | +- MOQ Lite draft-04 uses ALPN `moq-lite-04` for bare QUIC. |
| 18 | +- The session is active immediately after connection establishment; there is no |
| 19 | + CLIENT_SETUP/SERVER_SETUP exchange. |
| 20 | +- Bidirectional transaction streams are used for Announce, Subscribe, Fetch, |
| 21 | + Probe, and Goaway. |
| 22 | +- Publisher-created unidirectional Group Streams start with GROUP and then |
| 23 | + carry FRAME messages. |
| 24 | +- Unknown stream types are reset for extension probing rather than treated as a |
| 25 | + connection-level protocol failure. |
| 26 | +- Most messages carry a message length field, but that framing field is not |
| 27 | + part of the semantic message struct. |
| 28 | + |
| 29 | +## Solution |
| 30 | + |
| 31 | +Build `MOQX.MOQLite04` as its own protocol variant on top of `MOQX.Transport`. |
| 32 | + |
| 33 | +The implementation should proceed in narrow slices: |
| 34 | + |
| 35 | +1. Define semantic message structs and stream type lookup helpers. |
| 36 | +2. Add shared binary helpers in `MOQX.Codec` for the integer/string/bytes |
| 37 | + primitives needed by MOQ Lite and future draft-14 work. |
| 38 | +3. Implement MOQ Lite payload encoders and decoders for every message struct. |
| 39 | +4. Add a MOQ Lite stream codec that handles stream type prefixes, message |
| 40 | + length fields, buffering, and payload decoder selection. |
| 41 | +5. Add a MOQ Lite session/stream state machine that enforces stream roles, |
| 42 | + message ordering, graceful finish, abort sending, abort receiving, and |
| 43 | + connection close behavior using `MOQX.Transport`. |
| 44 | +6. Add a URI-first `MOQX.MOQLite04.connect/2` facade that establishes a |
| 45 | + transport connection and starts a role-neutral MOQ Lite client. |
| 46 | +7. Add a client runner that feeds normalized transport events into Session and |
| 47 | + applies returned transport actions through `MOQX.Transport`. |
| 48 | +8. Add subscriber-style client operations, with role implied by each operation |
| 49 | + rather than by a whole-connection mode. |
| 50 | +9. Add publisher-style client operations and a client-level |
| 51 | + subscribe/group/frame smoke flow over `MOQX.Transport.Support`. |
| 52 | + |
| 53 | +## User Stories |
| 54 | + |
| 55 | +1. As a MOQ Lite client implementer, I want typed message structs so protocol |
| 56 | + code can be written against Elixir values rather than ad hoc maps or raw |
| 57 | + binaries. |
| 58 | +2. As a codec implementer, I want shared `MOQX.Codec` helpers so draft-14 and |
| 59 | + MOQ Lite do not duplicate variable-length integer, string, and byte payload |
| 60 | + parsing. |
| 61 | +3. As a protocol implementer, I want payload encoders and decoders to be |
| 62 | + independent of stream buffering so they can be tested with simple binaries. |
| 63 | +4. As a session implementer, I want a stream codec that owns message lengths |
| 64 | + and incomplete buffers so transport events can be converted into complete |
| 65 | + messages deterministically. |
| 66 | +5. As a MOQ Lite endpoint, I want transaction stream state to reject invalid |
| 67 | + message order, such as SUBSCRIBE_DROP before the first SUBSCRIBE_OK. |
| 68 | +6. As a subscriber, I want Fetch Streams to return FRAME messages directly on |
| 69 | + the same bidirectional stream without a GROUP header. |
| 70 | +7. As a publisher, I want Group Streams to start with GROUP and then emit |
| 71 | + ordered FRAME payloads until Finish Sending or Abort Sending. |
| 72 | +8. As a future draft-14 implementer, I want this work to avoid a common |
| 73 | + protocol behaviour that would force draft-14 into MOQ Lite's stream model. |
| 74 | + |
| 75 | +## Implementation Decisions |
| 76 | + |
| 77 | +- `MOQX.Transport` remains the only transport boundary used by the protocol |
| 78 | + layer. |
| 79 | +- Protocol code must not match raw `quicer` messages. |
| 80 | +- `MOQX.Codec` is generic. It must not contain MOQ Lite stream roles, message |
| 81 | + atom tables, or session state. |
| 82 | +- `MOQX.MOQLite04` owns MOQ Lite draft-04 message structs, stream type lookup, |
| 83 | + stream codec, and session state. |
| 84 | +- The upper protocol state machine is called a Session. Connection remains |
| 85 | + transport vocabulary for `MOQX.Transport.Connection`. |
| 86 | +- Session APIs are pure reducers. `handle_transport/2` consumes normalized |
| 87 | + `MOQX.Transport` events and `handle_command/2` consumes local application |
| 88 | + intent. |
| 89 | +- Session reducers return protocol events and transport actions as data. They |
| 90 | + do not call `MOQX.Transport`, `quicer`, or process APIs directly. |
| 91 | +- Transport stream identity is preserved. Stream-derived protocol events remain |
| 92 | + tagged with a stream identity or ref, and each transport stream keeps its own |
| 93 | + `MOQX.MOQLite04.StreamCodec` state. |
| 94 | +- `MOQX.MOQLite04.Error` is the protocol error boundary. Session code should |
| 95 | + use structured errors and convert to integer transport application error |
| 96 | + codes only when producing transport actions. |
| 97 | +- `StreamType` remains a typespec plus lookup helpers, not a struct. |
| 98 | +- Message structs contain semantic payload fields only. Message length is a |
| 99 | + stream/message framing concern. |
| 100 | +- Decoder selection is stream-state dependent. A single decode-anything |
| 101 | + function should not guess message type without context. |
| 102 | +- The first implementation targets native QUIC and ALPN `moq-lite-04`; |
| 103 | + WebTransport remains out of scope. |
| 104 | +- Session APIs must receive explicit options or structs. Do not use |
| 105 | + `Application` env as a test seam or mutable global configuration. |
| 106 | +- The future `MOQX.MOQLite04.connect/2` boundary should accept a URI |
| 107 | + string or `URI.t()` directly. The module name already fixes the MOQ Lite |
| 108 | + draft-04 variant, so a separate `MOQX.Endpoint` wrapper would duplicate |
| 109 | + protocol/profile information at this layer. |
| 110 | +- Transport backend selection and backend options belong in explicit client |
| 111 | + options, such as `transport: {MOQX.Transport.Quicer, opts}`, not in endpoint |
| 112 | + data. |
| 113 | + |
| 114 | +## Testing Decisions |
| 115 | + |
| 116 | +Default tests should stay fast and deterministic. |
| 117 | + |
| 118 | +Initial coverage should include: |
| 119 | + |
| 120 | +- `MOQX.Codec` primitive round-trip and boundary tests; |
| 121 | +- payload encoder/decoder round-trip tests for every MOQ Lite message struct; |
| 122 | +- invalid enum, invalid length, incomplete buffer, and trailing-bytes errors; |
| 123 | +- stream codec tests for stream type prefixes and message length stripping; |
| 124 | +- pure state-machine tests for Announce, Subscribe, Fetch, Probe, Goaway, and |
| 125 | + Group stream transitions; |
| 126 | +- support-transport tests showing the state machine consumes normalized |
| 127 | + `MOQX.Transport` events rather than raw `quicer` events. |
| 128 | + |
| 129 | +Real QUIC or interop tests should stay tagged `:integration` and remain |
| 130 | +explicitly invoked. |
| 131 | + |
| 132 | +## Out of Scope |
| 133 | + |
| 134 | +- MOQT draft-14 protocol implementation. |
| 135 | +- WebTransport-over-HTTP/3 support. |
| 136 | +- Media payload parsing or codec/container awareness. |
| 137 | +- Production scheduling, caching, and relay fanout policy beyond the minimal |
| 138 | + protocol state needed to exchange messages. |
| 139 | +- External MOQ Lite relay interoperability as a default unit-test requirement. |
| 140 | + |
| 141 | +## Progress |
| 142 | + |
| 143 | +Initial building block commit: |
| 144 | + |
| 145 | +- `3dc2a14 Add moq-lite message model and codec contracts` |
| 146 | + |
| 147 | +Delivered so far: |
| 148 | + |
| 149 | +- `MOQX.Codec` |
| 150 | +- `MOQX.Codec.Encoder` |
| 151 | +- `MOQX.Codec.Decoder` |
| 152 | +- `MOQX.Codec` varint, string, and byte payload helpers |
| 153 | +- `MOQX.MOQLite04` stream type lookup helpers |
| 154 | +- `MOQX.MOQLite04` subscribe response discriminator lookup helpers |
| 155 | +- `MOQX.MOQLite04` message structs for Announce, Subscribe, Fetch, Probe, |
| 156 | + Goaway, Group, and Frame messages |
| 157 | +- `MOQX.MOQLite04` payload encoders and decoders for all message structs |
| 158 | +- `MOQX.MOQLite04.Error` structured protocol errors and transport application |
| 159 | + code mapping |
| 160 | +- `MOQX.MOQLite04.StreamCodec` incremental receive and send framing for |
| 161 | + opener and responder stream sides |
| 162 | +- `MOQX.MOQLite04.Session` pure reducer for normalized transport input, |
| 163 | + local protocol commands, per-stream codec state, protocol events, and |
| 164 | + transport action output |
| 165 | +- `MOQX.MOQLite04.connect/2` URI-first client facade over explicit transport |
| 166 | + options, native QUIC ALPN `moq-lite-04`, and a role-neutral |
| 167 | + `MOQX.MOQLite04.Client` value |
| 168 | +- `MOQX.MOQLite04.command/2` and `MOQX.MOQLite04.recv/2` client runner APIs |
| 169 | + that thread the connected client value, keep `Session` pure, and apply |
| 170 | + returned transport actions through `MOQX.Transport` |
| 171 | +- Role-neutral subscriber operations on `MOQX.MOQLite04` for |
| 172 | + AnnounceInterest, Subscribe, SubscribeUpdate, Fetch, Probe, repeated Probe, |
| 173 | + and Goaway transaction streams |
| 174 | +- Role-neutral publisher operations on `MOQX.MOQLite04` for accepting |
| 175 | + peer-opened streams, sending Announce, SubscribeOk, and SubscribeDrop |
| 176 | + responses, and publishing Group plus Frame messages on publisher-created |
| 177 | + unidirectional streams |
| 178 | +- A client-level support-transport smoke flow that subscribes, accepts the |
| 179 | + subscription, sends SubscribeOk, publishes a Group, and delivers the original |
| 180 | + Frame payload through public client APIs |
| 181 | +- Session tests for Announce, Subscribe, Fetch, Probe, Goaway, Group, |
| 182 | + stream lifecycle, support transport normalized events, and the |
| 183 | + reference-style subscribe/group/frame smoke flow |
| 184 | + |
| 185 | +Follow-up integration work: |
| 186 | + |
| 187 | +- issue 09: real QUIC MOQ Lite 04 integration tests over |
| 188 | + `MOQX.Transport.Quicer` |
| 189 | + |
| 190 | +## References |
| 191 | + |
| 192 | +- <https://datatracker.ietf.org/doc/html/draft-lcurley-moq-lite-04> |
| 193 | +- <https://datatracker.ietf.org/doc/draft-ietf-moq-transport/14/> |
| 194 | +- `docs/adr/0001-transport-boundary-support-transport-and-benchmark-harness.md` |
| 195 | +- `docs/adr/0002-native-quic-first-webtransport-out-of-scope.md` |
| 196 | +- `docs/adr/0003-validated-endpoints-above-raw-transport.md` |
| 197 | +- `docs/adr/0006-protocol-variants-own-session-state-codec-stays-generic.md` |
| 198 | +- `docs/adr/0007-protocol-sessions-are-pure-reducers.md` |
0 commit comments