open-standards/wai/extensions/loc-carriage.md
Transaction Science cbf58297e2 LOC carriage: WAI envelopes on LOC objects, a writer and reader in joule-moq, the corpus live-conformance/loc
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]>
2026-10-22 10:43:34 -04:00

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 of draft-ietf-moq-loc-04 (20 July 2026), the packaging draft-ietf-moq-msf-01 §4.1 names, over draft-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 the WAI_RECEIPT property 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 the loc module of joule-moq (joule-code/joule-code-rs/crates/joule-moq); the corpus is live-conformance/loc/, and its second implementation is live-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 prefix 0x000001) whose NAL units each have a forbidden_zero_bit of 0, of which exactly one is a VCL NAL unit (types 1 to 5) whose slice header begins with a first_mb_in_slice of 0;
  • wai.video.hevc: an Annex B byte stream whose NAL units each have a forbidden_zero_bit of 0 and a nuh_layer_id of 0, of which exactly one is a VCL NAL unit (types 0 to 31) whose first_slice_segment_in_pic_flag is 1;
  • wai.audio.aac: at least 7 bytes, the 12-bit sync word, layer 0, and a frame_length equal 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, whose content_hash is 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:

  1. at most one Immutable Properties, which parses and holds none of its own: loc_property_invalid;
  2. each LOC property of §2.2 at most once, across both property lists, with its rule: loc_property_invalid;
  3. WAI_MANIFEST and WAI_RECEIPT only inside the Immutable Properties, each at most once: loc_property_invalid; no WAI_MANIFEST: loc_manifest_absent;
  4. the manifest is a JSON object whose wai is of major version 1, whose model_requirement.capability is a string, and whose security.encryption is absent, null or "none": loc_manifest_invalid; that capability is mapped (§3.1): loc_capability_unmapped; and it is C: loc_capability_mismatch;
  5. the payload is one chunk (§3.2): loc_payload_form;
  6. the envelope is WAI1, the manifest's length as a u32 little-endian value, the manifest, the payload's length the same way, and the payload (SPEC §2);
  7. where WAI_RECEIPT is present: its value reads and holds to moq-streaming-format §5's rules on the property alone (property_invalid), and its content_hash is 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.