# joule-moq MoqT (Media over QUIC Transport) for CommunicationOS broadcast media, with a JWP receipt extension: every object carries verifiable provenance and its energy figure. ## The draft it implements `draft-ietf-moq-transport-22` (1 October 2026), and no other. The draft is chosen before the first MOQT byte, by the ALPN `moqt-22` on native QUIC or the `WT-Available-Protocols` value `moqt-22` on WebTransport (§6.2); `SETUP` carries no version. `MOQT_DRAFT_VERSION` is the conventional code `0xff000016`. Drafts do not interoperate with each other. Deployed relays speak earlier drafts, which differ from -22 in their varint, their control streams and their messages, so this crate does not talk to them. The codec of -22 is a module of its own (`src/draft22/`), selected through `draft::Draft`: a later draft, or an earlier one added for interoperation, is a sibling module and a `Draft` variant, and the draft-neutral parts (tracks, objects, receipts, the session's state) stay as they are. ## What is implemented The codec (`draft22`), every decoder bounded (it reads no byte that is not there and allocates nothing from a length before the bytes behind it exist) and every refusal a typed `WireError` carrying the draft's consequence: close the session with a code, reject the request, or a malformed track. | -22 | module | |---|---| | §8.1 variable-length integer: 1 to 9 bytes, leading ones, longer forms accepted | `draft22::varint` | | §8.2 Location, §8.7 Track Namespace (at most 32 fields, none empty, 4,096 bytes for a Full Track Name), §8.8 rendering | `draft22::names` | | §8.3 Key-Value-Pairs (delta types, values up to 2^16-1 bytes), §8.4 and §10 Properties with Immutable Properties, Prior Group and Object ID Gaps and Mandatory Track Properties, §9.1 Setup Options | `draft22::kvp` | | §8.9 Authorization Token structure | `draft22::token` | | §9 every control message of Table 5, framed by a 16-bit Length | `draft22::control` | | §9.20 every Message Parameter, the list each message allows, the rules on repetition; §8.6 Range Filters; §9.20.9 Location Filters; FILL_PARAMETERS | `draft22::params` | | §6.3 and §6.4.2 control and request streams, Request IDs by end, §9.1.7 MAX_REQUEST_UPDATES credit, the order of messages on each request stream | `draft22::request` | | §11 subgroup streams, object datagrams, fetch streams with End of Range, padding; a whole-stream decoder and an incremental one | `draft22::data` | | §12 and §16.11 session, request, PUBLISH_DONE and stream reset codes; §13 grease | `draft22::codes` | On it: - `session`: the draft by ALPN, SETUP options, Request IDs, the receipt extension declared by a Setup Option. - `transport_quic` (feature `quic`): native QUIC on quinn. A pair of unidirectional control streams, each opened by SETUP; a bidirectional stream per request; subgroup, fetch and padding streams; object datagrams. - `mock`: an in-memory fabric and relay whose objects, receipts and requests cross the -22 codec. - `carriage`: how this crate's objects and receipts ride -22 objects (below). ### Where -22 leaves an encoding open The codec takes one reading of each, and the vector checker takes the same: - FILL_TIMEOUT (0x0A) and RENDEZVOUS_TIMEOUT (0x04) state no encoding; both are durations in milliseconds and are read as varints, as the draft encodes its other durations. - FILL_PARAMETERS is length-prefixed and holds "a sequence of Parameters": read up to its length, with no count before them, type deltas starting from 0. - End of Range (§11.4.1.2) says "the Group ID and Object ID fields are present"; the only such fields of a fetch object are the Group ID Delta and Object ID Delta, which bits 0x08 and 0x04 of 0x8C, 0x10C and 0x20C mark. They are read as an object's are: absolute on the first entry, and after it a Group ID that moves one or more groups in the request's Group Order, with an absolute Object ID. - An unknown message, stream or datagram type closes the session; -22 names no code, and this codec gives PROTOCOL_VIOLATION. ## Carriage of this crate's objects and receipts Object Properties in the range -22 sets aside for an application's own use (§8.4), inside the object's Immutable Properties, since a relay must not change them: | type | name | value | |---|---|---| | `0x3A40` | `MRL_LAYER` | varint: the object's layer, when the header states layers | | `0x3A41` | `WAI_RECEIPT` | bytes: WAI `extensions/moq-streaming-format.md` §5's property, laid out below | | `0x3A42` | `MRL_TOTAL_LAYERS` | varint, when non-zero | | `0x3A43` | `JWP_OBJECT_RECEIPT` | bytes: the object receipt standing alone, in the binary form of WAI `extensions/jwp-receipts.md` §2.7.2 | | `0x3A44` | `WAI_ENVELOPE` | varint `1`, when the payload is a WAI envelope | A layer cap on a SUBSCRIBE is an OBJECT_PROPERTY_FILTER on `MRL_LAYER` (`messages::mrl_cap_filter`), which needs the peer's MAX_FILTER_RANGES above 0. An object's delivery timeout rides as its OBJECT_DELIVERY_TIMEOUT property (§10.2). The `WAI_RECEIPT` value: `content_hash` (32 bytes), `joules_micro` (a -22 varint), `acquisition` (a varint length, then UTF-8), `acquisition_evidence` (a varint length, then bytes), `group_receipt` (32 bytes), and nothing after. A group's receipts ride its receipts track (moq-streaming-format §4.1): group *g* carries one object, the group receipt and the object receipts of group *g* of the media track in the binary form of WAI jwp-receipts §2.7.3: `WJR1`, the group receipt's canonical bytes (its signing payload in the form signed, then its signature, and for a legacy group its parent flag and parent), a u32 count, and each object receipt's body. `ReceiptBundle::read` gives the group's link hash computed over the bytes received; `read_receipts`, `GroupReceipt::from_binary` and `ObjectReceipt::from_binary` refuse what does not read with the §3 step jwp-receipts §2.7.4 names. `tests/wai_jwp_corpus.rs` reads and writes every binary vector of WAI's corpus. **Room grants** (WAI `extensions/live-rooms.md` §10.5, `grant_proof`): a grant is public, so it is presented with a `WRP1` proof of its principal's key over a challenge the verifier chose. Over MOQT the relay's SETUP carries the challenge (`GRANT_CHALLENGE`, `0x3A47`) and the client's the grant as an AUTHORIZATION TOKEN beside the proof (`GRANT_PROOF`, `0x3A49`), with the context `SETUP //`. `check_proof` is §10.5.4's checks, given the grant's digest and principal by whatever read the grant; over HTTP the same record rides the `WAI-Room-Proof` header and the challenge `WAI-Room-Challenge`. The receipt extension is declared by the Setup Option `0x3A46`, a varint holding the extension's version. -22 has no unregistered range for Setup Options and asks for a provisional registration; until one is made the code point is this crate's choice, and a peer that does not know it ignores it (§9.1). ## Not yet implemented - WebTransport (MOQT over HTTP/3, §6.2.1), a relay with priority fan-out and per-object receipts, and the authorization token cache (§8.9) are in `joule-moq-relay`, built on this codec; this crate's own session keeps no token cache. The browser client is `joule-moq-web`, over this crate built without its default feature `runtime` (the codec, the carriage and the receipts, for `wasm32-unknown-unknown`). - Evaluating filters at a publisher (§3.3), and holding a peer to its MAX_FILTER_RANGES: the codec reads and writes every filter and counts their ranges. - GOAWAY, delivery-timeout enforcement on real streams and §5.1's scheduling are `joule-moq-relay`'s; this crate's QUIC transport has none of them, and its mock relay applies delivery timeouts on a caller-driven clock. - `draft-ietf-moq-loc` and `draft-ietf-moq-msf` semantics (catalogs, LOC properties): their properties ride as any property does. ## The vector corpus `tests/vectors/draft-22/vectors.json` holds -22 vectors: varints, every control message type, control, subgroup, fetch and padding streams, datagrams, and the `WAI_RECEIPT` value; and negative vectors, each with the refusal it must meet. `tests/draft22_vectors.rs` generates the file deterministically from typed values and holds the crate to it; `JOULE_MOQ_WRITE_VECTORS=1` rewrites it. `tests/vectors/draft-22/moqt22_check.py` decodes the corpus independently, from the draft's text, standard library only: ```sh python3 -I tests/vectors/draft-22/moqt22_check.py ``` Each vector has a `name`, a `kind`, its bytes as `hex`, `canonical` (whether this crate's encoder makes exactly those bytes) and `expect`; a `uni-stream` vector also has `context.group_order` (1 Ascending, 2 Descending), the Group Order of the request a fetch stream answers. | kind | bytes | reading | |---|---|---| | `varint` | one varint | `{"value", "length"}` | | `control` | one framed message | the message (below) | | `uni-stream` | a whole stream to its FIN | a list of events (below) | | `datagram` | one datagram | `{"datagram": {…}}` or `{"padding": n}` | | `wai-receipt` | the `WAI_RECEIPT` value | its five fields | The JSON view: byte strings are lowercase hex; a Track Namespace is a list of its fields in hex; a Location is `[group, object]`; a Key-Value-Pair is `{"type", "value"}` for an even type and `{"type", "bytes"}` for an odd one, with Immutable Properties as raw bytes. A message is an object with `"message"` (its name in Table 5) and its fields by their names in the draft, lowercased with underscores. A parameter is `{"type": NAME}` with `"value"` (a varint or a uint8), `"token"`, `"location"`, `"filter"` (a Location Filter's `filter_type` and the fields it has; a Range Filter's `set_id`, `property_type` for the property filters, and `ranges` as `[start, end or null]`; `null` for Length 0), `"parameters"` (FILL_PARAMETERS) or `"namespace"`. Stream events are `{"header": "subgroup" | "fetch" | "control" | "padding", …}`, `{"control": message}`, `{"object": …}` (subgroup), `{"fetch_object": …}`, `{"end_of_range": {"kind", "location"}}` and `{"padding": n}`. A refusal is `{"error": kind, "consequence": c}`, with `c` one of `session:`, `request:` or `malformed-track` (and `property_invalid` for the `WAI_RECEIPT` value, which a sink discards). The kinds are `WireError::kind`: `truncated`, `trailing-bytes`, `unknown-message-type`, `unknown-stream-type`, `invalid-flags`, `unknown-parameter`, `duplicate`, `parameter-not-allowed`, `duplicate-filter`, `invalid-filter`, `type-overflow`, `length-exceeds-limit`, `too-many-namespace-fields`, `empty-namespace-field`, `invalid-value`, `overflow`, `properties-on-non-normal`, `unknown-object-status`, `no-prior-object`, `empty-datagram-properties`, `malformed-token`, `unexpected-message`, `malformed-track`. ## Tests - `cargo test -p joule-moq --features quic --all-targets` runs the codec's unit tests, the corpus (`tests/draft22_vectors.rs`), generated round trips and hostile input (`tests/draft22_roundtrip.rs`), the QUIC transport over loopback, the mock and relay, the staged MoQ trace and WAI's JWP corpus. - The independent decoder above runs over the corpus in CI (`.github/workflows/joule-code-conformance.yml`).