# WAI Extension: LOC Carriage > Status: Draft, tracking its drafts as [moq-streaming-format](moq-streaming-format.md) 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-05` (2 November 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/`](../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](confidentiality.md) §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 | | `0x11` | Codec String | bytes | UTF-8 (RFC 3629), not empty (LOC §2.3.1.3) | The Codec String is the WebCodecs codec string of the payload's codec, such as `avc1.42C00A` or `opus`. This extension checks its form alone: a reader does not compare it with the capability's codec (§3.1) or with the track's catalog `codec` (§2.3). §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), a Codec String among them where it states one, which the writer does not derive from the payload; 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`](../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.