extensions/loc-carriage.md maps an envelope whose payload is one WebCodecs chunk (an Opus packet, an AVC or HEVC access unit in Annex B, an ADTS frame) onto one object of a track of packaging loc (draft-ietf-moq-loc-04): the payload as the LOC Payload, the manifest as the Immutable Property WAI_MANIFEST (0x3A61), the receipt commitment as WAI_RECEIPT. A reader rebuilds the envelope byte for byte and checks the receipt against it, refusing with the extension's codes. joule-moq's loc module writes and reads; its example writes the corpus and tests/loc_vectors.rs checks it; loc_verify.py is the second implementation. WAI_MANIFEST is registered as a wire identifier; moq-streaming-format §6 points to the carriage. Co-Authored-By: Claude Opus 5.5 <[email protected]>
10 KiB
WAI Extension: LOC Carriage
Status: Draft, tracking its drafts as moq-streaming-format does. Maps a WAI envelope and its receipt onto one object of a track of packaging
loc: the Low Overhead Media Container ofdraft-ietf-moq-loc-04(20 July 2026), the packagingdraft-ietf-moq-msf-01§4.1 names, overdraft-ietf-moq-transport-22. A WAI envelope whose capability's payload is one chunk of a codec the WebCodecs Codec Registry registers travels as one LOC object: its payload is the LOC Payload, unchanged; its manifest is an Immutable Property,WAI_MANIFEST; its receipt commitment is theWAI_RECEIPTproperty of moq-streaming-format §5. A subscriber that reads LOC and nothing else hands the payload to its decoder as a chunk; a subscriber that reads WAI rebuilds the envelope byte for byte and checks its receipt against it. No new transport. The reference implementation is thelocmodule ofjoule-moq(joule-code/joule-code-rs/crates/joule-moq); the corpus islive-conformance/loc/, and its second implementation islive-conformance/loc_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.A section number of LOC, MSF or MOQT here is one of those revisions. Where a later revision renumbers or renames what this text cites, the citation is what changes (moq-streaming-format §6).
1. Scope
§1.1 Packaging wai (moq-streaming-format §3) carries whole envelopes, and a subscriber that
does not read WAI cannot use its objects. Packaging loc carries a codec's chunks, which any
LOC subscriber can decode. This extension lets one track be both: its objects are LOC objects,
and a WAI subscriber reads each as the envelope it came from.
§1.2 It maps the capabilities of §3 and no others. A capability whose payload is not one chunk
of a registered WebCodecs codec travels as packaging wai.
§1.3 It changes no envelope and no receipt: the envelope a subscriber rebuilds is the one the publisher wrote, byte for byte, and the receipt commits to it exactly as jwp-receipts §2.1 says.
2. LOC, as this extension reads it
§2.1 A LOC object's MOQ Object Properties hold the LOC Public Properties, and its payload holds
the LOC Private Properties followed by the LOC Payload, the "internal data" of a WebCodecs
EncodedAudioChunk or EncodedVideoChunk (LOC §2, §2.2). LOC defines Private Properties only
with Secure Objects (LOC §3.1.3): an object this extension writes carries none, and a sealed
object (confidentiality §4) is opened first and then read as here.
§2.2 The LOC properties (LOC §2.3, §6.1), each at most once on an object:
| type | name | value | rule |
|---|---|---|---|
0x10 |
Timestamp | varint | |
0x08 |
Timescale | varint | not 0 |
0x09 |
Video Frame Marking | bytes | 1 to 4 bytes (RFC 9626) |
0x0C |
Audio Level | varint | at most 255 (RFC 6464 §3) |
0x0D |
Video Config | bytes | not empty |
0x0F |
Audio Config | bytes | not empty |
§2.3 MSF §4.1 places one chunk in each object and a group of pictures in one group; MSF §5.2
names the track's codec by the WebCodecs codec string.
3. Mapped capabilities
§3.1 The capabilities this extension maps, each with the WebCodecs codec whose chunk its payload is:
| capability | WebCodecs codec | the payload, one chunk |
|---|---|---|
wai.audio.opus.packet |
opus, format opus |
one Opus packet (RFC 6716 §3) |
wai.video.avc |
avc1 or avc3, Annex B (no description) |
one access unit as an Annex B byte stream |
wai.video.hevc |
hev1 or hvc1, Annex B (no description) |
one access unit as an Annex B byte stream, of the base layer only |
wai.audio.aac |
mp4a.40, format adts |
one ADTS frame |
Not mapped: wai.video.vp8 and wai.video.vp9 (their payload is an IVF stream, a chunk is one
frame), wai.video.av1 (its payload carries a framing header), wai.audio.flac (a FLAC stream
with its STREAMINFO, where a chunk is one frame), wai.video.vvc (the WebCodecs registry does
not register VVC), wai.video.mvhevc (a WebCodecs HEVC access unit holds exactly one base-layer
picture), and every capability with no WebCodecs codec.
§3.2 One chunk. A writer and a reader hold the payload to the form of its capability, and
refuse another with loc_payload_form:
wai.audio.opus.packet: at least one byte;wai.video.avc: an Annex B byte stream (after at least two zero bytes, the prefix0x000001) whose NAL units each have aforbidden_zero_bitof 0, of which exactly one is a VCL NAL unit (types 1 to 5) whose slice header begins with afirst_mb_in_sliceof 0;wai.video.hevc: an Annex B byte stream whose NAL units each have aforbidden_zero_bitof 0 and anuh_layer_idof 0, of which exactly one is a VCL NAL unit (types 0 to 31) whosefirst_slice_segment_in_pic_flagis 1;wai.audio.aac: at least 7 bytes, the 12-bit sync word, layer 0, and aframe_lengthequal to the payload's length.
4. Writing an object
§4.1 A publisher writes an envelope (SPEC §2) of a mapped capability as one object:
- its payload is the envelope's payload, with nothing before or after it;
- its Immutable Properties (MOQT §10.7) hold
WAI_MANIFEST, the envelope's manifest bytes exactly as the envelope holds them; the LOC properties the publisher states (LOC §3.1.2 asks for them to be immutable); and, where the publisher states a receipt,WAI_RECEIPT, its value laid out as moq-streaming-format §5 lays it out, whosecontent_hashis the envelope's (jwp-receipts §2.1).
WAI_MANIFEST has type 0x3A61, from the range MOQT §8.4 leaves to an application's own use
(odd, so its value is bytes). Like WAI_RECEIPT's 0x3A41, a subscriber reads it only on a
track that §4.2 declares. A manifest longer than 65 535 bytes, the largest value MOQT §8.3
allows, is not written (loc_manifest_invalid), and neither is an envelope that is not a WAI1
envelope.
§4.2 The track's catalog entry declares packaging "loc" and codec (MSF §5.2), and the
fields of moq-streaming-format §4: wai_capabilities, holding exactly the mapped capability,
and wai_receipts where its objects carry WAI_RECEIPT. A subscriber that reads WAI recognises
such a track by wai_capabilities on a loc track.
§4.3 The LOC Timestamp and Timescale are the publisher's, and nothing in the envelope fixes them; the receipt commits to the envelope, so it does not cover them. A subscriber that needs a signed time for an object reads it from a claim (jwp-receipts §2.4.3).
5. Reading an object
§5.1 A subscriber reading an object of such a track, whose catalog names the capability C,
checks, in this order:
- at most one Immutable Properties, which parses and holds none of its own:
loc_property_invalid; - each LOC property of §2.2 at most once, across both property lists, with its rule:
loc_property_invalid; WAI_MANIFESTandWAI_RECEIPTonly inside the Immutable Properties, each at most once:loc_property_invalid; noWAI_MANIFEST:loc_manifest_absent;- the manifest is a JSON object whose
waiis of major version 1, whosemodel_requirement.capabilityis a string, and whosesecurity.encryptionis absent,nullor"none":loc_manifest_invalid; that capability is mapped (§3.1):loc_capability_unmapped; and it isC:loc_capability_mismatch; - the payload is one chunk (§3.2):
loc_payload_form; - the envelope is
WAI1, the manifest's length as au32little-endian value, the manifest, the payload's length the same way, and the payload (SPEC §2); - where
WAI_RECEIPTis present: its value reads and holds to moq-streaming-format §5's rules on the property alone (property_invalid), and itscontent_hashis the envelope's (jwp-receipts §2.1):loc_receipt_mismatch.
§5.2 The envelope a reader rebuilds is then read as any envelope is (SPEC §2 to §4), and its receipt verified as jwp-receipts §3 says, against the group receipt the property names. This extension checks only what §5.1 lists.
6. Refusals
| code | meaning |
|---|---|
loc_property_invalid |
the properties do not parse, a LOC property breaks its rule or is given twice, or WAI_MANIFEST or WAI_RECEIPT is outside the Immutable Properties or given twice |
loc_manifest_absent |
no WAI_MANIFEST |
loc_manifest_invalid |
the manifest is not a plaintext WAI1 manifest of major version 1 naming a capability, or (writing) the envelope is not a WAI1 envelope or its manifest is longer than 65 535 bytes |
loc_capability_unmapped |
the manifest's capability is not one of §3.1 |
loc_capability_mismatch |
the manifest's capability is not the track's |
loc_payload_form |
the payload is not one chunk of its codec (§3.2) |
loc_receipt_mismatch |
WAI_RECEIPT's content_hash is not the rebuilt envelope's |
property_invalid |
WAI_RECEIPT itself is refused (moq-streaming-format §5) |
7. What this extension does not do
§7.1 It does not change LOC's payload, define a LOC property beyond WAI_MANIFEST, or restate
MOQT's encoding of properties. It does not map a WAI2 envelope: a multi-rendition envelope is
several payloads, and each rendition a publisher sends over LOC is its own track.
8. Conformance
§8.1 live-conformance/loc/vectors.json holds objects of
every mapped capability, each with the envelope it carries, its MOQ Object Properties as MOQT
-22 encodes them, its payload, its LOC properties and its receipt property; envelopes the writer
refuses; and objects the reader refuses, each with its code, every code of §6 given. Its HEVC
objects carry access units of format-probe-conformance/ext/hevc/base_64x64.265; its Opus
object a 20 ms packet of silence; its AVC and AAC objects fixed bytes in the form of §3.2, which
are not decodable.
§8.2 joule-moq's tests/loc_vectors.rs regenerates the corpus byte for byte and requires
every outcome; loc_verify.py, written from this text and sharing no code with joule-moq,
decodes every object's properties, rebuilds every envelope, computes every content hash with
BLAKE3 itself, requires every outcome, and requires every mutated copy of the corpus to fail.