| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .. | ||
| examples | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| README.md | ||
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(featurequic): 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 //<authority><path>. 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 isjoule-moq-web, over this crate built without its default featureruntime(the codec, the carriage and the receipts, forwasm32-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-locanddraft-ietf-moq-msfsemantics (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:
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:<code>, request:<code> 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-targetsruns 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).