Skip to main content

WAI Extension: Live HTTP Egress

Mirrored from the canonical text at commit c5e7133e ().

Status: Draft. Registers no capability and no record. It is how a room’s CMAF tracks, which a room delivers over MoQ (live-rooms §6), are served over HTTP while the room is live: an egress subscribes to an owner’s tracks and their receipts tracks as any subscriber does, holds each object once its bytes verify against its group’s signed receipts (§3), or, while the group is still growing, against a running receipt the owner signed for that object (§3.5), and packages what it holds when it is asked: low-latency HLS, one part per object, with preload hints, blocking playlist reload and playlist delta updates (§5), and low-latency DASH, a dynamic MPD whose segments are sent as they grow (§6). Nothing is written ahead of a request. A track under Common Encryption is served as it is, by an egress that holds no key: its playlists and its MPD state the protection the catalog states, and name a new header where the key changes (§4.5). A room’s breaks are carried through in the forms live-ads gives them (§7). Every part is served beside the publisher’s receipts for it, so a client verifies what it was sent as a subscriber of the room does (§8); and every playlist and MPD beside a statement, signed under a key the owner delegates to the egress, so a client acts on none that was changed, is of another track, or is older than one it holds (§8.5). A trace of what an egress received and what it answered can be checked by anyone: a part listed before its object was held, a withheld object served, and a blocking reload answered early are each refused with a reason (§10, §11). §9 says what latency is here: a figure measured on a host, never a property of the format. WAI defines no transport. The reference implementation is the live_egress module of wai-rs (feature live_egress) and the gateway joule-moq-http (joule-code/joule-code-rs/crates/joule-moq-http); the corpus is live-conformance/http-egress/, and its second implementation is live-conformance/http_egress_verify.py; the statements’ corpus is live-conformance/egress-statements/, and its second implementation live-conformance/egress_statements_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: the WAI texts this one cites; draft-pantos-hls-rfc8216bis-22 (the HLS second edition: §4.4.3.7, §4.4.3.8, §4.4.4.5, §4.4.4.7, §4.4.4.9, §4.4.5.2, §4.4.5.3, §6.2.2, §6.2.5, §6.2.6, §6.3.7 and §6.3.8); draft-ietf-moq-cmsf-01 (CMSF, whole) and draft-ietf-moq-msf-01 (MSF, §5); and the DASH Industry Forum’s change request Low-latency Modes for DASH of 27 March 2020 (clauses 9.X.4 and 9.X.5). For §4.5: draft-pantos-hls-rfc8216bis-22 again (§4.4.3.3, §4.4.4.3, §4.4.4.4, §4.4.4.5, §4.4.5.2, §5, §6.2.3 and §6.2.5.1), CMSF §4, and the DASH Industry Forum’s Interoperability Points, Part 6: Content Protection, version 5.0.0 of January 2022 (clauses 5, 7.2, 8 and 9.2). ISO/IEC 23009-1, ISO/IEC 23000-19 and ISO/IEC 23001-7 were not read: what this text writes of an MPD and reads of a CMAF file is what those texts and CMSF state of them.

1. Scope

In scope: what an egress holds of a track and what it withholds (§3); the resources it serves (§4); the HLS playlists (§5) and the MPD (§6) it packages from what it holds; the breaks (§7); the receipts beside each part (§8), and the statements beside each playlist and MPD (§8.5); what a latency figure is (§9); and the trace and its check (§10).

Out of scope: a transport; a codec; how a publisher makes CMAF; tracks of WAI envelopes that state no time for their objects, which an egress does not serve (§4.4), and envelopes as objects of their own, which are http-delivery §11.4’s; a key, a licence and any content protection system’s exchange, none of which an egress holds or makes (§4.5 states a protected track’s protection from its catalog and reads no sample); and who may fetch from an egress, which is the deployment’s (live-rooms §10.3 names one way).

Relationship to moq-streaming-format: a track here is of packaging cmaf (CMSF §3.5.1) in an MSF catalog, with a receipts track as §4.1 there gives any media track. Relationship to http-delivery: the playlists here are classical ones, of fragmented MP4, that any HLS or DASH client reads; receipt files and the receipt index have the forms §8.1 and §8.2 there give them.

2. Terms

3. What an egress holds

3.1 Reading CMAF

A box is a 32-bit size and a type, or a size of 1 and a 64-bit size after the type; a size of 0, one below its header’s length, or one past the end of what holds it does not read.

A track’s header is the initDataList entry its catalog entry’s initRef names (CMSF §3.1). It reads when: its first box is ftyp; it has exactly one moov and no moof or mdat; the moov has exactly one trak, whose tkhd gives the track ID and whose one mdia has one mdhd, giving the timescale, and one hdlr; and the moov’s one mvex has one trex, of that track ID, giving the default sample duration and default sample flags. The timescale and the track ID are above 0. A track whose header does not read serves nothing.

An object reads as CMAF chunks (CMSF §3.3) when its boxes are, in order, any of styp, prft and emsg, and one or more moof each directly followed by an mdat, and no other box; each moof has one mfhd and one traf; the traf has one tfhd, of the header’s track ID, one tfdt and one or more trun. A sample’s duration is its own in the trun, else the tfhd’s default, else the trex’s; its flags are the trun’s first-sample flags for the first sample, else its own, else the tfhd’s default, else the trex’s. The object’s start is its first tfdt’s decode time, its duration the sum of its samples’ durations, which is above 0, and each later moof’s decode time is where the samples before it ended. It is independent when its first sample’s flags do not set sample_is_non_sync_sample (bit 16).

3.2 Holding

An egress keeps, per track, the receipts and objects of the groups that are not closed, in the order they arrived. The first receipts to arrive for a group, and the first bytes to arrive for an object, stand: a second arrival is ignored, as is anything of a group at or below the highest group closed, and an object already held or withheld.

A group’s receipts are the one object of that group on the receipts track (moq-streaming-format §4.1). They are read when: they are in the binary form of jwp-receipts §2.7; the group receipt is in the labelled payload; its track_alias is the catalog’s wai_receipt_alias, its group_id the MOQT group, and its signer_pubkey the owner’s key; it lists leaf_count object receipts, whose object_ids are 0, 1, … in order; and its signature verifies (jwp-receipts §3 steps 1 and 2). Receipts that are not read are refused.

Only the lowest group that is not closed is worked on. When its receipts are read they are taken up on the track’s chain: where the egress took up a group before and this group’s parent_receipt_hash is not that group’s link hash, the chain is anchored again here, which the egress counts and reports. An egress joins a room that is running and misses what it misses; it never waits for a parent.

Then, in object order from the first object not yet held, while the object’s bytes and its receipt are both there, the object is held if every check passes, and withheld with the first reason that fails:

  1. the content hash of the bytes, jwp-receipts §2.1’s, is the receipt’s content_hash (content_hash_mismatch);
  2. the receipt’s proof reaches the group’s signed root at its position, jwp-receipts §3 step 5 (inclusion_proof_failed);
  3. the bytes read as CMAF chunks of the track (cmaf_malformed);
  4. the group’s first object is independent (group_not_sap), and starts no earlier than the track’s last closed segment ended; each later object starts where the one before it ended (cmaf_discontinuous);
  5. the object lasts at most the part target, in milliseconds (part_over_target);
  6. from the group’s first object’s start to this object’s end is less than the target duration and half a second, in milliseconds (segment_over_target).

Once an object of a group is withheld, no later object of that group is held: the group is cut there.

Until a group’s receipts arrive, an object of it is held on the group’s run (§3.5) instead: in object order, while the object’s bytes are there and the run covers its position, the object is held if its content hash is the one the run states for that position (content_hash_mismatch) and checks 3 to 6 pass, and withheld, the group cut, otherwise. Check 2 has no part in it: a running receipt’s place in the group’s tree was checked when it was taken into the run. Once the group’s receipts have arrived, read or refused, nothing more of the group is held on its run.

3.3 Why an object is withheld

reasonthe object
receipt_refusedis of a group whose receipts were refused
receipt_missingis of a group that closed before its receipts arrived, or has no receipt in them
object_missingfollowed an object of its group that never arrived
after_withheldfollowed an object of its group that was withheld
content_hash_mismatchdoes not hash to what its receipt states
inclusion_proof_failedhas a receipt whose proof does not reach the signed root
cmaf_malformeddoes not read as CMAF chunks of the track
group_not_sapstarts a group and does not start with a sync sample
cmaf_discontinuousdoes not start where its track left off
part_over_targetlasts longer than the part target
segment_over_targettakes its group past the target duration
track_not_timedis of an envelope track that states no time for it (§4.4)
track_not_independentis of an envelope track whose objects do not each decode alone (§4.4)
envelope_malformedis not one whole WAI1 envelope of media that decodes alone (§4.4)
envelope_undeclaredis an envelope the catalog or the track’s header does not describe (§4.4)
protection_unstatedis of a track whose header states protection its catalog does not, or of a group before the track’s first key period (§4.5)

A withheld object’s bytes are never served and never listed, in a part, in a segment or by range.

3.4 Closing a group

The lowest group that is not closed is over when either holds:

It is then closed. Each of its objects that arrived and is not held is withheld: for refused receipts receipt_refused; for none, or no receipt for that object, receipt_missing; in a cut group after_withheld; otherwise object_missing. Where it has held parts they become a segment, named by the group’s number; the segment is whole when the group was not cut, no object of it was left over, its receipts were read, and every object they list was held. A group whose parts were held on its run and whose receipts never came, or were refused, leaves a segment that is not whole. A group with no held part leaves no segment. Closing goes on to the next group while that one is over too.

A track’s media sequence numbers count its segments in order, the first being the number of the first group the egress held a part of. When a group’s first part is held and it starts after the last closed segment ended, the interval between is filled with gap segments, each of at most the target duration, the last one shorter: each takes a media sequence number and offers no bytes. So what an egress missed, and what it withheld, is a stated gap on the timeline and never a silent splice.

An egress keeps twice the window’s closed segments of each track, with their parts and their group receipts, and drops the oldest past that.

3.5 Running receipts

A group’s receipts are signed over all its objects (jwp-receipts §2.2), so a publisher that signs a group when it ends can publish them only then. Such a publisher MAY sign, for each object as it sends it, a running receipt: its statement of that object’s place in the group so far. An egress that takes running receipts holds an object once its running receipt has arrived (§3.2), and the group’s receipts, when they come, close the run and must agree with it. Group and object receipts, their digests and their binary form are unchanged.

What it is. A running receipt is not a receipt kind of jwp-receipts and has no signature format of its own. It is the payload of a meta event (live-events §4.2), signed by the owner as every event of the owner’s is (live-events §3.2), anchored (live-events §3.1) to the object it is about, and carried on the owner’s events track as any event is (live-events §8.1), where its timeline record is indexed by that object’s Location. The payload is 141 bytes; integers are little-endian, as an event’s are:

offsetsizefieldmeaning
04magicWRR1 (ASCII)
41version1 in this revision
58track_aliasu64: the media track’s wai_receipt_alias
1332content_hashthe anchored object’s content hash (jwp-receipts §2.1)
4532leafthat object’s leaf in the group’s tree (jwp-receipts §2.2), in the form its object receipt will take, legacy or labelled
7732priorthe root (jwp-receipts §2.2) over the leaves of the group’s objects before it; 32 zero bytes, the empty group’s root, exactly for object 0
10932rootthe root over the leaves of the group’s objects through it

A publisher signs one for each object, in object order, the anchor’s group and object the object’s MOQT Group ID and Object ID, and MUST NOT sign two that differ for one object.

A covering statement. A running receipt taken into its group’s run is a covering statement of jwp-receipts §4.1: the registry admits the class running-receipt, its form WRR1, as covering objects (covering_statements in registry/capabilities.json). It covers the object at its position, whose content hash it states, on the owner’s word. This section is what §4.1 there asks of a class’s text: a statement of the class is verified as Reading one and The run say, it covers what The run says, and a group’s receipts that contradict it are handled as Closing says, and at a relay as At a relay’s door says. An egress verifies the receipts it serves, so it is a relay of jwp-receipts §4 (§4.5 there), and a part it holds on its run (§3.2) is held on a covering statement.

Reading one. An egress that takes running receipts knows its room’s id and, for each track, the owner’s key, the track’s wai_receipt_alias and the track’s digest as an anchor names it (live-events §3.1). It reads each record alone: the event’s chain, its rate and the room’s event policy are the room verifier’s (live-events §6.2, §6.3), a subscriber of one owner does not hold them, and nothing here reports on them. A record is refused, and changes nothing, at the first of these that fails:

  1. the record reads as an event (live-events §3.3), of kind meta, with an anchor (running_malformed);
  2. its room is the egress’s room (running_wrong_room);
  3. its signature verifies (live-events §3.2) (running_bad_signature);
  4. its principal is the owner’s key (running_not_owner);
  5. its payload is 141 bytes, begins with WRR1, has version 1, and has a prior of 32 zero bytes exactly when the anchor’s object is 0 (running_malformed);
  6. the anchor’s track is the track’s digest and track_alias its wai_receipt_alias (running_other_track);
  7. the anchor’s group and object are the Location its timeline record was indexed at (running_location_mismatch).

The run. A group’s run is the running receipts taken for it, in position order; it covers positions 0 to its length less one, and its root is its last receipt’s root, or 32 zero bytes while it is empty. A running receipt that passed the checks above, of a group that is not closed, whose receipts have not arrived and whose run is not broken, is, with n the run’s length and i the anchor’s object:

Each of the four refusals breaks the run: no later running receipt of that group is taken, the parts already held stay held, and the rest of the group waits for the group’s receipts, as every object does where a publisher signs no running receipts. A running receipt of a closed group, of a group whose receipts have arrived, or of a group whose run is broken, is ignored. An egress that joins inside a group takes no run for it, since the run starts at object 0.

Closing. The group’s receipts, when they arrive and are read (§3.2), close the run, and MUST agree with it: for every position the run covers, the group’s receipts hold an object receipt, whose content_hash is the run’s and whose leaf (jwp-receipts §2.2) is the run’s leaf. Receipts that do not are refused (running_contradicted), as receipts that are not read are: the owner signed two accounts of one group. The parts held on the run stay held, since each was held to a digest the owner signed; the objects not yet held are withheld receipt_refused when the group closes; and the egress counts the group and reports it. Receipts that agree are taken up on the track’s chain as §3.2 says, and each part held on the run has its object receipt from then on (§8.1).

reasonthe running receipt, or the group’s receipts
running_malformeddoes not read as a meta event with an anchor and a WRR1 payload
running_wrong_roomis of another room
running_bad_signaturehas a signature that does not verify
running_not_owneris signed by a key that is not the owner’s
running_other_tracknames another track or another receipt alias
running_location_mismatchis anchored to another object than its record was indexed at
running_restatedstates again, differently, an object the run holds
running_skips_objectis past the run’s next position
running_prior_mismatchdoes not continue the run’s root
running_root_mismatchstates a root that is not over the run’s leaves and its own
running_contradicted(the group’s receipts) do not agree with the run

Cadence. A running receipt is an event, and counts against its principal’s event policy (live-events §5, §6.3) with every other event of the owner’s. The default policy admits 50 events in a second and a payload of 1024 bytes: an owner of a video and an audio track whose parts last 200 ms sends ten running receipts a second, and an owner whose tracks together send more than 50 objects a second needs a policy that says so.

At a relay’s door. A relay that reads the receipts it forwards at a door (jwp-receipts §4.1) MAY read running receipts as an egress does, for a room’s media track whose owner is a key its door holds the track’s group receipts to and whose wai_receipt_alias the owner’s catalog states. It reads the owner’s events track, holds each record to the checks of Reading one and each group’s run to its rules (a group it began reading inside takes no run, as for an egress that joins inside one), and fans out an object its group’s run covers once the object’s content hash is the one the run states: the run’s receipt at the object’s position is a covering statement of it (jwp-receipts §4.1), signed by the owner, a key the door holds the group’s receipts to. An object no run covers, one past what a broken run took, and one that carries its receipt as a property wait for the group’s receipts, the one covering statement left to them. The group’s receipts close the run as Closing says, and this is how a relay handles receipts that contradict it, as jwp-receipts §4.1 has a class’s text say: they are refused (running_contradicted) and dropped with their group as receipts whose signature does not verify are, so that no subscriber is given two accounts of one group; the objects already fanned out stay fanned out, each covered by a statement the owner signed; and the rest of the group is refused. Every other duty of jwp-receipts §4.1 holds whichever statement covers an object: the receipts the door forwards are unaltered, each group receipt is verified when it arrives, and an object no covering statement covers is not fanned out. A relay’s tally of signed figures counts an object passed on its run under no figure, the figure being in a leaf only the group’s receipts open.

4. Resources

4.1 Names

Under an owner’s base URL:

resourcewhat it is
master.m3u8the multivariant playlist (§5.6)
manifest.mpdthe MPD (§6.1)
<track>/media.m3u8the track’s media playlist (§5.1), with the delivery directives of §5.4 and §5.5
<track>/init.mp4the track’s header, byte for byte; a protected track has none (§4.5)
<track>/init.<i>.mp4a protected track’s header of the key period whose index is i, byte for byte (§4.5)
<track>/p/<g>.<o>.m4sthe held object o of group g: a part
<track>/p/<g>.<o>.m4s.receiptsthat part’s receipt index (§8.1), once its group’s receipts were taken up
<track>/p/<g>.<o>.m4s.runningthe run that part was held on (§8.4), where it was held on one
<track>/p/rcpt/<h>.jsonthe group receipt whose link hash is h (§8.2)
<track>/s/<g>.m4sthe segment of group g: its held parts, concatenated (§6.3)
<track>/s/<g>.m4s.partsthat segment’s parts list, once it is closed (§8.3)
wai.m3u8, wai.mpdthe multivariant playlist and the MPD of the envelope tracks (§4.4)

<g>, <o> and <i> are decimal with no leading zero. A name that is none of these, a part that is not held, a segment that is neither closed nor open, a group receipt the egress does not keep, and a playlist of a track with no held part, are answered 404. Two requests wait instead of being answered 404: one for the part a preload hint names (§5.3), and one for the segment of the open group or of the group after the highest group closed (§6.3).

4.2 Parameters

An egress states four parameters, which do not change while it serves a room: the target duration in whole seconds (1 to 60), the part target in milliseconds (at most the target duration), the window, the number of closed segments a playlist lists (3 to 4096), and the latency target in milliseconds. A publisher whose groups and objects do not fit them has those objects withheld (§3.2, checks 5 and 6).

4.3 The clock

When an egress first holds a part, of any track, it fixes its anchor: that instant on its own clock, less ms of the part’s end. The anchor is the wall-clock time of tick 0 of every track, so an owner’s tracks MUST share one decode timeline, starting together. The anchor is the egress’s statement of when it had the media, not the publisher’s of when it was made: an HLS program date-time and a DASH availability time are both read from it.

4.4 Envelope tracks

A room’s track of packaging "wai" carries one whole WAI1 envelope an object (moq-streaming-format §3), and its receipts are over the envelopes. An egress MAY serve such a track as Mode F (http-delivery §11.7, Appendix B): each held envelope is the one sample of one CMAF chunk, byte for byte. Mode F’s sample entries are not registered, so an egress serves envelope tracks only where its operator turns that on, for test material, as http-delivery §11.7 requires.

What the track must state. Packaging needs, for each object, a decode time, a duration and whether it decodes alone. An envelope states none of them and the catalog states them in one way only: time-aligned groups. A track is served when its catalog entry

Every object that arrives on a track that is not served is withheld with that reason, its receipts are not read, and every resource under the track is answered 404.

The timeline. A served track’s timescale is objects_per_720s. Object o of group g starts at tick (g + o) × 720 and lasts 720 ticks, exactly; tick 0 is start_ms, which the egress reads for its presence alone (§4.3). The formula steps a group by one object, so it fits a track of one object a group; a second object of a group is held where §3.2’s checks pass, and the next group’s object then starts before the track left off and is withheld (cmaf_discontinuous). An object whose g + o is 2^32 − 1 or more is withheld track_not_timed: its fragment has no sequence number.

Holding. §3.2 holds for an envelope track with its checks 3 and 4 replaced; checks 1 and 2 are over the object’s bytes, the envelope, as its receipt is:

  1. the bytes are exactly one WAI1 envelope of WAI MAJOR 1 that is not a layer object (http-delivery §10.4) and that http-delivery §4.2 gives an element (envelope_malformed); every capability of that element is one the catalog entry’s wai_capabilities lists; and its handler (vide for a manifest media of video, soun for audio, meta otherwise) and its element are the header’s, and its manifest MINOR at most the header’s (envelope_undeclared);
  2. it starts no earlier than the track’s last closed segment ended, and each later object of a group starts where the one before it ended (cmaf_discontinuous). Every object is independent.

The header is fixed by the first envelope that passes check 3 with no header to hold it to: the catalog states neither the samples’ class nor their highest MINOR. It is Appendix B’s header: ftyp (cmfc; cmfc, iso6), and a moov of an mvhd, one trak of track ID 1 and an mvex, at the track’s timescale, the handler that envelope’s, one wai1 sample entry whose waiC states MAJOR 1, that envelope’s MINOR, and its element as both the union and the floor; a visual entry states the catalog’s width and height, and an audio entry no channel count. <track>/init.mp4 is that header, and 404 before it is fixed.

A part of an envelope track is one moof and one mdat: an mfhd whose sequence number is g + o + 1; a traf of a tfhd of track 1 with default-base-is-moof, a tfdt of version 1 at the object’s start, and a trun (flags 0x701) of one sample with the data offset, the duration 720, the envelope’s length and the flags 0x02000000; and an mdat whose payload is the envelope. The bytes are those wai-pack’s Mode F writer gives a segment of one sample. Parts, segments, the media playlist, the preload hint, blocking reload, delta updates, gaps and the grid are §3.4, §5 and §6’s, unchanged: on a track of one object a group, a segment is one part.

What a client hashes. The receipts are over the envelope, not the chunk. For a part of an envelope track, <part>.receipts (§8.1) has as object the content hash of the part’s mdat payload, and a client checks that payload, the sample, against it; in <segment>.parts (§8.3), n is the length of the part, the chunk, and object the content hash of its mdat payload.

Where they are listed. master.m3u8 (§5.6) and manifest.mpd (§6.1) are of the owner’s CMAF tracks alone, each served once every CMAF track has a held part or is on its grid, and each the bytes it would be with no envelope track beside them: a classical player is never offered a sample it cannot decode, and a CMAF track beside an envelope track plays as it did. The served envelope tracks are listed in two files of their own:

What a player needs. No browser and no classical player decodes these samples: the sample entry wai1 names no codec a media pipeline has, so a page cannot give such a track to Media Source Extensions or to a native HLS or DASH player. A player of an envelope track reads wai.m3u8 or wai.mpd, supports the element by http-delivery §4.2 and never asks the user agent’s decoders about it (http-delivery §4.3), reads the header’s waiC, takes each part’s mdat payload as one envelope, checks it against the receipts beside the part (§8), and hands it to a WAI sink (browser-sink, native-sink), which dispatches it by its manifest and presents it at the time its fragment states. A classical player given master.m3u8 or manifest.mpd finds no envelope track there; one given wai.m3u8 finds only codecs it does not know.

4.5 Protected tracks

A room’s CMAF track may be under Common Encryption (moq-streaming-format §4.9): the samples of each object are encrypted, the catalog states the protection as CMSF §4 has it, and the track’s key changes with its key period, each stated by a catalog of its own. An egress serves such a track with no key. It holds each object as §3.2 holds any, against receipts that are over the protected bytes; it reads nothing of a sample, of a senc or of a pssh; and it writes, from the catalog alone, what a player needs to ask for a key: which scheme, which KID, which systems, and which header, for every segment it lists. It names systems by their system IDs, as the catalog does, and takes part in no system’s licence exchange.

What a header states. A header states protection when its one sample entry (the first entry of the stsd in the stbl in the minf of the mdia) is encv or enca. It then gives a scheme and a KID when that entry, after its own fields (78 bytes of a visual entry, 28 of an audio one), holds exactly one sinf, with one schm, whose scheme type is the scheme, and one schi holding one tenc, whose default KID (the 16 bytes after its version, flags and four more bytes) is the KID. A header with no such path, or another sample entry, is clear.

A key period. What a catalog states of a track’s key period is: the period’s index and from_group, the track’s wai_key_period (0 and 0 where a protected track’s catalog carries none); the entries, the contentProtections entries the track’s contentProtectionRefIDs name, in that order; and the period’s header, which the track’s initRef names. It reads when all of these hold:

  1. the header gives a scheme and a KID, and the scheme is cenc or cbcs;
  2. the header reads (§3.1) with the track ID, timescale, handler and trex defaults of the track’s first header;
  3. there is at least one entry, and every entry is an object whose scheme is the header’s, whose defaultKID is an array of UUID strings that holds the header’s KID, and whose drmSystem is an object with a systemID that is a UUID string;
  4. where a drmSystem has a pssh, it is a non-empty string of Base64 in the standard alphabet, padded; where it has a laURL, that is an object whose url, and whose type where it has one, are strings of printable ASCII with no space, ", < or >.

An egress carries a pssh and a licence URI through as the catalog gives them and reads neither; it does not hold a pssh to the KID.

A track’s first key period is the one its catalog states when the egress reads it. A track whose header states protection and whose catalog names no entry for it, or states a key period that does not read, is not served: every object that arrives on it is withheld protection_unstated, its receipts are not read, and every resource under it is answered 404. So an egress never offers protected bytes under a playlist that calls them clear. An object of a group before the first key period’s from_group is withheld protection_unstated too, and its group’s receipts are not read. A track whose header is clear and whose catalog names no entry for it is a clear track, served as §3 to §6 say.

Later key periods. A catalog published later states the track’s next key period (moq-streaming-format §4.9 has it published before the period’s first group). The egress takes it, and from then on a group is of the last key period taken whose from_group is at most its number. It is refused, and changes nothing, at the first of these:

reasonthe key period
key_period_not_protectedis of a track that has no first key period: a clear track, an envelope track, or one that is not served
key_period_malformeddoes not read
key_period_out_of_orderhas an index or a from_group that is not above the last taken period’s, or another scheme
key_period_latehas a from_group at or below a group the egress has closed or holds a part of

A key period whose index, first group, entries and header are those of one already taken is the same catalog again and is ignored. An egress counts what it refused and reports it. A refused period’s groups stay of the period before: where the publisher did change the key, its players find segments their key does not open, and the report says why. An egress cannot see that from the bytes.

The headers. <track>/init.<i>.mp4 is the header of the key period whose index is i, for every period taken, and 404 before. A protected track has no <track>/init.mp4: it is answered 404, so a client that ignores the protection finds no header to start with.

In a media playlist (§5.1) of a protected track:

A key line is #EXT-X-KEY:METHOD=<m>,URI="<u>",KEYFORMAT="urn:uuid:<system ID>",KEYFORMATVERSIONS="1": m is SAMPLE-AES for cbcs and SAMPLE-AES-CTR for cenc; the system ID is in lowercase; and u is data:text/plain;base64, and the entry’s pssh where it has one, else its licence URI where it has one, else urn:uuid: and the KID as a UUID. The HLS second edition defines the identity key format alone, and CMSF maps no entry to a key line: this mapping is this text’s. No IV is written: under cbcs the constant IV is in the header’s tenc. A client that knows none of the key formats ignores every line, as the HLS second edition has it do, and does not play the track.

In the MPD (§6.1), where a track of the owner is protected:

A clear track beside a protected one has no descriptor and no key line, and its segments are numbered through the Periods as a protected track’s are.

What is not here. An egress does not check that an object is encrypted, that it is under the key period’s key, or that a pssh names the KID: it has no key, and moq-streaming-format §4.9’s verification, which has, is the publisher’s and a verifier’s. The multivariant playlist carries no session key. No EXT-X-PRELOAD-HINT names a header. Output protection and any system’s licence format are that system’s.

5. Low-latency HLS

5.1 The media playlist

Lines end in LF. A duration is written in seconds with three decimals, and a date as YYYY-MM-DDTHH:MM:SS.mmmZ, the anchor plus ms of a start. With T the target duration, P the part target and W the window:

  1. #EXTM3U; #EXT-X-VERSION:9; #EXT-X-TARGETDURATION:<T>; #EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=<3·P>,CAN-SKIP-UNTIL=<6·T>; #EXT-X-PART-INF:PART-TARGET=<P>; #EXT-X-MEDIA-SEQUENCE:<n>, n the number of the first segment listed; #EXT-X-MAP:URI="init.mp4". (A protected track’s playlist differs here and before each key period’s first segment, as §4.5 says.)
  2. In a delta update that skips segments (§5.5), #EXT-X-SKIP:SKIPPED-SEGMENTS=<k>.
  3. The room’s date ranges (§7), each on a line.
  4. The last W closed segments, less those skipped, each as: #EXT-X-PROGRAM-DATE-TIME:<its start>; then for a segment, its parts where they are still listed (below), #EXTINF:<duration>, and s/<g>.m4s; for a gap segment, #EXT-X-GAP, #EXTINF:<duration>, and gap/<start in ticks>.m4s, at which nothing is served.
  5. The open group, where there is one: #EXT-X-PROGRAM-DATE-TIME:<its start> and its parts.
  6. #EXT-X-PRELOAD-HINT:TYPE=PART,URI="p/<g>.<o>.m4s" (§5.3).

A part is #EXT-X-PART:DURATION=<d>[,INDEPENDENT=YES],URI="p/<g>.<o>.m4s", INDEPENDENT for an independent object (§3.1). A closed segment’s parts stay listed while its end is less than three target durations, in milliseconds, before the end of the last held part of the open group, or of the last closed segment when no group is open.

A live playlist has no EXT-X-PLAYLIST-TYPE and, in this revision, no EXT-X-ENDLIST: an egress does not turn a room into an on-demand presentation.

5.2 Parts

A part is one object, and so one or more CMAF chunks that start where the part before ended. A publisher that wants parts near the part target cuts its objects so; an egress never re-cuts media, since the receipts are over the publisher’s objects. The HLS second edition asks that a part other than a segment’s last be at least 85% of the part target: an egress does not withhold a shorter one, and a publisher SHOULD meet it.

5.3 The preload hint

The hint names the next part of the open group; with no open group, part 0 of the group after the highest group closed. (A track with no held part has no playlist, so no hint, and no request for a part of it waits.) A request for the part the hint names is held until that object is held, then answered with its bytes, all at once: no byte of a part is sent before the part is held. Where the hinted object is then withheld, or its group closes without it, the request is answered 404, as the HLS second edition has a server answer for a hinted resource it will not publish.

5.4 Blocking playlist reload

A request for a media playlist with _HLS_msn=<M>, and optionally _HLS_part=<N>, is answered when the playlist holds what it names. With L the number of the last closed segment:

A request is refused at once with 400, by the state when it arrives, when _HLS_part comes without _HLS_msn; when either is not a decimal integer; when M > L + 2 (L being F − 1 before any segment has closed, and F 0 before any part is held); or when M = L + 1 and N is at least the open group’s parts plus the advance part limit, which is ⌈3000 / P⌉ for a part target under a second and 3 otherwise. A request that has waited three target durations MAY be answered 503. A refusal comes before the 404 of a track with no held part. An egress MUST NOT answer a blocking request with a playlist before the playlist holds what the request names (blocking_answered_early).

5.5 Delta updates

A request with _HLS_skip=YES or _HLS_skip=v2 is answered with a delta update: of the closed segments the playlist would list, the leading ones whose end is at least the skip boundary, six target durations in milliseconds, before the end of the last closed segment are left out, with their tags, and EXT-X-SKIP states how many. Where none is that old the playlist is the whole one. EXT-X-MEDIA-SEQUENCE is unchanged: it is the number of the first segment the whole playlist lists. Date ranges are never skipped (the playlist does not state CAN-SKIP-DATERANGES), so v2 is answered as YES.

5.6 The multivariant playlist

Served once every track of the owner has a held part: #EXTM3U, #EXT-X-VERSION:9, #EXT-X-INDEPENDENT-SEGMENTS; with video and audio, one #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="<track>",DEFAULT=YES,AUTOSELECT=YES,URI="<track>/media.m3u8" per audio track, then per video track #EXT-X-STREAM-INF:BANDWIDTH=<b>,CODECS="<c>",RESOLUTION=<w>x<h>,FRAME-RATE=<f>,AUDIO="audio" and <track>/media.m3u8, b the video’s bit rate plus the first audio track’s and c the video’s codec, a comma and that audio track’s; with video alone, the same without AUDIO and with the video’s own values; with audio alone, per track #EXT-X-STREAM-INF:BANDWIDTH=<b>,CODECS="<c>" and its playlist. Codecs, bit rates, dimensions and frame rate are the catalog’s (MSF §5), which the publisher states and nothing signs.

6. Low-latency DASH

6.1 The MPD

The MPD addresses segments by number, the group’s, at a fixed duration, so it is served only while every track of the owner is on a grid (§6.2), and is answered 404 otherwise. It is one document for every viewer, and it changes only when the room’s breaks do (§7), or a protected track’s key period is taken (§4.5, which gives such an owner’s MPD a Period for each key period and what follows is said of each):

live-conformance/http-egress/ holds the bytes; the corpus’s traces are their definition where this list and they differ in white space.

6.2 The grid

A track is on a grid of duration D, in ticks, from the first whole segment it closes: D is that segment’s duration. It stays on the grid while every segment it closes, before and after, starts at its group’s number times D and lasts at most D, and every whole one lasts D. A track that leaves the grid does not return, and the MPD is then answered 404: its template would name bytes at the wrong times. HLS needs no grid and goes on.

A segment that is not whole is served under its number with the parts that were held; a DASH client finds less media in it than the template states, and none where a group left no segment.

6.3 Segments as they grow

A request for the segment of the open group is answered 200 with a body sent in pieces (HTTP/1.1 chunked transfer coding, or its equivalent): at once the parts held so far, then each part as it is held, whole, and the body ends when the group closes. A request for the segment of the group after the highest group closed, before that group has a held part, waits the same way; a track with no held part and no closed group has no such group, and a request for a segment of it is answered 404. An egress MUST NOT send a byte of a segment that is not a held part’s (served_before_held), and MUST NOT end the body while the group is open (segment_answered_open). A closed segment is answered with its parts concatenated.

So a segment is available from the moment its first part is held, which is what availabilityTimeOffset states: on a grid whose segments last D and whose first part lasts p, the offset is D − p.

7. Breaks

A room’s breaks are live-ads’s. An egress that holds the room’s events and its ad policy writes, into every media playlist, the tags live-ads §5 gives the schedule, in the order live-ads §5.4 states, each on a line of its own after the header; and into the MPD’s Period, before its adaptation sets, the event streams of live-ads §6.3. It writes them byte for byte and adds none: the asset list a break names, the decision and the receipts are live-ads’s, at the decider. An egress that does not hold them writes none, and its playlists are the program’s alone.

8. Receipts beside the media

8.1 A part’s receipt index

<part>.receipts is a receipt index in the form of http-delivery §8.2: object is the part’s content hash (jwp-receipts §2.1), delivery holds one item, the WAI_RECEIPT property (moq-streaming-format §5) built from the object’s receipt and its group’s link hash, and the object receipt itself, and claims is empty. A client checks the part as §8.2 there checks a delivery item, over the bytes it was sent. A receipt that states a figure above 0 with no class in a labelled leaf has no valid property, and such a part’s index is answered 404.

8.2 Group receipts

p/rcpt/<h>.json is the group receipt in the form of http-delivery §8.1, for every group whose receipts the egress took up and still keeps. A group’s parent_receipt_hash names its parent’s file; a client that joins anchors its chain at the first group it verifies (jwp-receipts §3 step 3).

8.3 A segment’s parts

<segment>.parts is, in RFC 8785’s form, {"kind":"wai-egress-segment","group":<g>,"parts":[{"uri":"../p/<g>.<o>.m4s","n":<bytes>,"object":"<content hash>"}…]}: the parts whose bytes, in order, are the segment’s. A client that fetched a segment splits it at these lengths and checks each piece as §8.1’s part. Nothing signs the list: a piece that does not hash to a part whose receipts verify is not verified, whatever the list says.

8.4 A part’s run

<part>.running is served for a held part whose position its group’s run covers, for as long as the part is kept: in RFC 8785’s form, {"kind":"wai-egress-running","group":<g>,"object":<o>,"records":["<record>"…]}, the event records of the run’s receipts for positions 0 to o, each in lowercase hexadecimal. A client that knows the room, the owner’s key, and the track’s anchor digest and receipt alias checks each record as §3.5 reads one, at the Locations (g, 0) to (g, o), takes them in order into a run as §3.5 does, and requires its part’s bytes to hash to the last record’s content_hash. That is the owner’s signed word that these bytes are object o of group g. While a group is open a part held on its run has no receipt index (<part>.receipts is answered 404): the group receipt it would name does not exist yet. Once the group’s receipts are taken up, the index is §8.1’s, and a client checks the part against either. A part of a group whose receipts never came, or contradicted its run, keeps its run alone.

8.5 Statements beside playlists and MPDs

Every media playlist, multivariant playlist and MPD an egress answers 200 (§4.4, §5, §6) is served with a statement (http-delivery §8.10) in the response’s WAI-Statement header. It binds the bytes of that response, under the owner’s key, to what they are: the resource, its tracks and their aliases, when it was answered, and for a media playlist where it stands. A client holds a playlist to it before acting on it, as it holds each part to its receipts (§8.1), and refuses one changed on its way, one of another track, one older than one it holds, and a delta update that does not follow from what it holds. The group receipts, the runs and every other file of §8 are unchanged, and so is every byte of a playlist and an MPD: a statement is beside them.

The delegation. An egress holds no key of the owner’s (§13). The owner names a statement key for it in a meta event of its own (live-events §4.2), with no anchor, signed as every event of the owner’s is (live-events §3.2). Its payload, integers little-endian as an event’s are:

offsetsizefieldmeaning
04magicWMD1 (ASCII)
41version1 in this revision
532keythe statement key
378not_before_msthe earliest a statement under it is issued, in milliseconds since 1970
458not_after_msthe latest, at or after not_before_ms
531nhow many aliases follow, 1 to 64
548·naliaseseach track’s wai_receipt_alias that statements under it may state, strictly ascending

The owner carries it on its events track (live-events §8.1), where an egress that reads the track finds the one that names its key, or hands it to the egress otherwise; the egress carries it in every statement, so a client needs nothing more than the room’s id and the owner’s key, which it holds already to read a run (§8.4). Under a delegation, the statement key states playlists and nothing else: it signs no receipt and no event, and a receipt verifies only under the owner’s key.

What a statement states. For a request for name (the path under the owner, with no query) answered 200, the egress signs a statement whose sha256 is that of the body, issued_ms its clock (§4.3) when it answered, expires_ms that plus six target durations, delegation the record, package null, and the rest as the body states them:

A playlist is read for these line by line, split at LF, a CR anywhere not reading: its first line #EXTM3U; EXT-X-MEDIA-SEQUENCE exactly once; EXT-X-DISCONTINUITY-SEQUENCE and EXT-X-SKIP at most once each, the latter before any segment. Each line that is not empty and does not begin with # is a segment’s URI, and needs an EXTINF after the segment before it. The segments are numbered from EXT-X-MEDIA-SEQUENCE plus what EXT-X-SKIP leaves out, and a segment’s entry is its number, its EXTINF duration (the text before the comma), its URI and the last EXT-X-PROGRAM-DATE-TIME value since the segment before it (empty where there is none), joined by single spaces. A multivariant playlist’s first line is #EXTM3U. A body that does not read so has no statement, and is refused by its client.

So what a statement states is read from the bytes it signs, as a client reads them again, and an egress pays one signature a response. A trace (§10) records no header: the statements are held by the egress’s clients, not by the trace’s check.

What a client holds. A client of an egress keeps the room’s id and the owner’s key; each track’s alias, as the page gives it or as the first statement it accepts that names the track states it; for each resource, the issue time and position of the last statement it accepted; and for each track the entries of the segments the playlists it accepted list, at least the last 8192. Before acting on a playlist or an MPD answered 200, it checks the statement beside it as http-delivery §8.10 does, with an egress’s shape (package null, a time, a delegation, and an alias on every track) and its step 5 in full, and then the rest, refusing at the first that fails:

  1. the delegation reads: its record reads as an event (live-events §3.3) of kind meta with no anchor, of the room, its signature verifies (live-events §3.2), its principal is the owner’s key, and its payload reads as above (delegation_invalid);
  2. the statement’s signer is the key the delegation names, each of its aliases is one the delegation lists, and its issued_ms is within the delegation’s interval (statement_not_delegated); and its signature verifies (statement_signature);
  3. each track’s alias is the one the client holds for the track, where it holds one (statement_other_track);
  4. the client’s clock is at most expires_ms (statement_expired);
  5. the body reads as its name’s form, and the statement’s form, tracks, position and the count its skip states are what the body states (statement_inconsistent);
  6. where the client accepted a statement for the same name, this one was issued no earlier, and, for a media playlist, its msn, its dsn and its next are none below that one’s, next compared number first (statement_replayed);
  7. for a delta update, the client holds the entries of every segment it leaves out, and their SHA-256 is skip’s entries (delta_not_following);
  8. no segment the playlist lists has another entry than the one the client holds for its number (statement_rebound): a closed segment’s duration, URI and date do not change;
  9. for a blocking reload (§5.4) asked with _HLS_msn=M, next is at least [M, N + 1] with _HLS_part=N, and at least [M + 1, 0] without (statement_short).

A statement that passes is accepted and held: its time and position for its name, each alias it gave, and the entries its playlist lists. One refused changes nothing, and the client acts on nothing of its body.

reasonthe playlist or MPD, by its statement
statement_not_servedis served with no statement
statement_malformedis served with one that does not read, or is not an egress’s
statement_other_resourceis served with a statement of another resource
statement_mismatchis not the bytes its statement is over
delegation_invalidis stated under a delegation that is not the owner’s in this room
statement_not_delegatedis stated by a key, for a track or at a time the delegation does not cover
statement_signaturehas a statement whose signature does not verify
statement_other_trackis stated under another alias than its track’s
statement_expiredhas a statement that has expired
statement_inconsistenthas a statement that states other than its bytes do
statement_replayedis stated as older than one the client accepted
delta_not_followingis a delta update that leaves out other segments than the client holds
statement_reboundlists a segment the client holds otherwise
statement_shortanswers a blocking reload without what it named

What it binds. An accepted statement binds, under the owner’s key through its delegation: these bytes; that they are this resource of these tracks under these aliases; when they were answered; and, for a playlist, what it lists and where it stands. A client that acts only on what it accepted never acts on a playlist changed on its way, of another track, or older than one it acted on, and the delta updates it accepts are, segment for segment, the whole playlists the egress would have answered. It binds no more than the egress put in: what it lists and when are the egress’s, held to its arrivals by its trace (§10); its dates are its clock’s (§4.3); and its codecs, rates and headers are the catalog’s (§13).

9. Latency

Nothing in this text makes a latency true. A figure is a measurement, and states: the host; the path (loopback, a network); what was timed, from which instant to which; and the parameters of §4.2. Three intervals are defined so that figures can be compared:

Receipts bound it. A part is held only once its group’s receipts have arrived (§3.2), and a group’s receipts are signed over all its objects (jwp-receipts §2.2). A publisher that knows a group’s objects before it sends the first (content made ahead, or an encoder that looks a group ahead) publishes the receipts first, as moq-streaming-format §4.1 asks, and each part is held as it arrives. A publisher that signs a group when it ends publishes the receipts last, and every part of the group is then held at the group’s end: the group’s duration is added to its first part’s latency. A measurement states which of the two the publisher did. A publisher that signs at a group’s end and also signs running receipts (§3.5) has each part held when its running receipt arrives: the wait is then for one signature and its carriage on the events track, not for the group. A measurement states whether running receipts were used.

The ServiceDescription target and PART-HOLD-BACK are what an egress asks of a player, not what a viewer gets.

10. The trace

10.1 Form

A trace is one JSON object: kind "wai-egress-trace", version 1, the four parameters (target_duration_s, part_target_ms, window, latency_target_ms), tracks, and entries. A track is name, publisher (the owner’s key), receipt_alias, init (the header), codecs, bandwidth, width, height, frame_rate and sample_rate; a track’s name is video, audio or screen, / and a decimal layer, and no two are alike. Bytes and keys are lowercase hexadecimal. An envelope track (§4.4) has five members more: packaging, "wai"; capabilities, the catalog’s wai_capabilities; tempo, null or objects_per_720s and start_ms; frame_stream, a boolean; and layer; and its init and codecs are empty. The trace of an egress that takes running receipts (§3.5) also has room, the room’s id, and on every track anchor, the track’s digest as an anchor names it; a trace has both or neither. A protected track (§4.5) has one member more, protection: its first key period, an object of exactly index, from_group and entries, the catalog’s entries as JSON; its init is that period’s header. An envelope track has none.

An entry is t, the egress’s clock in milliseconds since 1970, which never goes back, and exactly one event:

eventmemberswhat happened
receiptstrack, group, bytesthe group’s receipts arrived
objecttrack, group, object, bytesan object arrived
endtrack, groupthe publisher ended the group
runningtrack, group, object, bytesan event record arrived on the owner’s events track as a running receipt of track, its timeline record indexed at that Location; only in a trace with a room
key_periodtrack, index, from_group, entries, inita later catalog stated a key period of track (§4.5): its entries, and its header
adshls (date-range lines), dash (event streams)the room’s breaks, as §7 writes them from here on
requestid, targeta request arrived; target is the path under the owner, with its query
emitid, bytesthat many more bytes of the request’s body were sent, before its end
responseid, status, bodythe request was answered: its status and its whole body

10.2 Checking

A checker replays the arrivals through §3 and requires each answer to be the one this text gives for the state at the answer’s entry. It refuses the trace at the first entry that fails, with the first of these that applies:

  1. the trace or the entry does not read as §10.1 (trace_malformed);
  2. an emit or a response for an id no open request has (response_unasked);
  3. an emit after which more bytes were sent for the request than the state has to send: the whole body where the state answers, a growing segment’s held parts where it waits, nothing otherwise (served_before_held);
  4. for a response:
    1. where the request was one §5.4 refuses when it arrived, any status but 400 (status_mismatch);
    2. a media playlist answered 200 that lists a part that was withheld (withheld_listed), a part that is not held, or a segment that is not closed (listed_not_held);
    3. where the state waits and the request has waited three target durations, 503 with an empty body passes;
    4. a media playlist request that the state still waits on (blocking_answered_early);
    5. a part or segment answered 200: a withheld part (withheld_served); a part the state waits on, or whose object has arrived and is neither held nor withheld: where its group’s receipts have not arrived and its run holds a receipt and does not cover the part (served_uncovered), and otherwise (served_before_held); a segment the state waits on (segment_answered_open);
    6. a status that is not the state’s, 200 or 404 (status_mismatch);
    7. a body that is not the state’s: of a playlist (playlist_mismatch), of the MPD (manifest_mismatch), of a receipt index, a group receipt, a parts list or a run (sidecar_mismatch), of anything else (bytes_mismatch); but, for a part of an envelope track whose last box is not an mdat holding exactly the held envelope, sample_not_alone where that payload begins with the envelope and holds more, and sample_mismatch otherwise; and, for a media playlist and for manifest.mpd, the reason of rule 5 where one applies.
  5. A media playlist or an MPD whose body is not the state’s is read for what it states of protection (§4.5), and refused with the first of these that applies. For a media playlist, read line by line: the key lines in force at a line are those of the last unbroken run of #EXT-X-KEY: lines before it, and the header in force the last #EXT-X-MAP: line before it. A clear track’s playlist with a key line whose method is not NONE: clear_stated_protected. A protected track’s, at the first part (#EXT-X-PART: with a URI="p/<g>.) or segment (s/<g>.m4s) of a group g that has a key period, in line order: no key line in force with a method other than NONE (protection_omitted); key lines in force that are not exactly that period’s (protection_not_catalogs); a header in force that is not that period’s EXT-X-MAP line (key_period_header). For the MPD, where the state has one: for each Period the state lists, in order, a served Period of that id is looked for (the text from <Period at a line’s start), and where there is none and a track is protected, key_period_header; then for each track in order, in the served AdaptationSet of that Period whose SegmentTemplate’s media is the track’s, with its lines that begin with <ContentProtection: for a clear track, any such line (clear_stated_protected); for a protected one, none (protection_omitted), lines that are not exactly the key period’s descriptors (protection_not_catalogs), an initialization that is not the key period’s header (key_period_header).

A trace that passes shows that, for what it records, the egress listed and served only what it held, when it held it. It shows nothing of what the trace leaves out: a trace is the egress’s own account, and a client’s protection is §8, not the trace.

11. Reasons

reasonthe trace
trace_malformeddoes not read
response_unaskedanswers a request that was not made
status_mismatchgives a status the state does not
listed_not_heldlists a part or a segment before the egress held it
withheld_listedlists a withheld object
blocking_answered_earlyanswers a blocking reload before the playlist held what it named
playlist_mismatchgives playlist bytes the state does not
manifest_mismatchgives MPD bytes the state does not
withheld_servedserves a withheld object
served_before_heldsends bytes the egress did not yet hold
served_uncoveredserves a part its group’s run does not cover
segment_answered_openends a segment while its group was open
bytes_mismatchgives media bytes that are not the held object’s
sidecar_mismatchgives receipts, a parts list or a run the state does not
sample_mismatchgives, for a part of an envelope track, a sample that is not the held envelope
sample_not_alonegives, for a part of an envelope track, an mdat that holds the envelope and more
protection_omittedgives a playlist or an MPD that lists a protected track’s media and does not state its protection
protection_not_catalogsstates a scheme, a KID, a system, a pssh or a licence URI that is not the catalog’s for that key period
key_period_headerlists a key period’s media under a header that is not that period’s
clear_stated_protectedstates protection for a clear track

12. Conformance

An egress conforms when a trace of what it received and answered passes §10.2, whatever it was sent. A checker conforms when it reaches the outcome live-conformance/http-egress/vectors.json states for every case: ok, or the reason; and, for a case with no base, the objects it states were withheld, each with its reason, how often a chain was anchored again, and, for a trace with a room, each running receipt refused (running_refused: its track, its group, the position its record was indexed at and the reason, in the tracks’ order and then the order they were refused in; a group whose receipts contradicted its run is among them, at the run’s length); and, for a trace in which a track states protection, each key period refused (key_periods_refused: its track, its index and the reason, in the tracks’ order and then the order they were refused in). A case with base is that trace with its edits applied in order: set replaces a member of an entry’s event; move takes the entry at from out and puts it at to, with the time of the entry then at to; insert puts an entry at at; remove drops one.

The corpus’s media is synthetic: headers and chunks with the boxes §3.1 reads and a few payload bytes, signed with a fixed test key. wai_live_egress_vectors gen writes it and verify regenerates it; http_egress_verify.py is written from this text, shares no code with them, and also requires every mutated copy of the corpus to fail and each of its own checks, dropped in turn, to be caught.

A client conforms to §8.5 when it reaches the outcome live-conformance/egress-statements/vectors.json states for every case: ok, or the reason of §8.5’s table. A session there is what a client of the reference egress was answered, each playlist and MPD with its statement; a case runs a session whole, or that many steps of it and then one step that plants a fault. wai_live_egress_vectors statements gen writes it and statements verify regenerates it; egress_statements_verify.py is written from §8.5 and http-delivery §8.10, shares no code with them, and also requires every mutated copy of the corpus to fail and each of its own checks, dropped in turn, to be caught.

The gateway’s own tests run it against a relay with planted faults, check every part and segment it serves against its receipts over HTTP, check its trace, and state the latency they measured on the host they ran on (§9).

13. Security and privacy considerations