live-http-egress §4.5 (new). An egress served a track under Common Encryption (moq-streaming-format §4.9) with playlists that did not say so. It now states the protection, with no key: it reads the catalog's entries for the track and the header's own sample entry, and reads no sample. - A key period is the catalog's entries for a track, its wai_key_period and its header. It reads when the header gives a scheme (cenc or cbcs) and a KID, is the track's first in all but its protection, and every entry states that scheme and KID with a system ID. A track whose header states protection its catalog does not is not served (protection_unstated), and neither is a group before the first key period. A later key period is taken, or refused with one of four reasons and reported. - HLS: at each key period's first segment, EXT-X-DISCONTINUITY where the header changes, an EXT-X-KEY for each entry (SAMPLE-AES for cbcs, SAMPLE-AES-CTR for cenc, the key format the entry's system ID) and an EXT-X-MAP naming that period's header, init.<index>.mp4; the playlist carries EXT-X-DISCONTINUITY-SEQUENCE. - DASH: a Period for each key period; in each, a protected track's set carries a ContentProtection of urn:mpeg:dash:mp4protection:2011 with the scheme and cenc:default_KID, and one for each entry's system ID with its pssh and licence URI; its SegmentTemplate names the period's header and numbers its segments from the Period's first. - The trace gains a track's protection and the key_period event; its check gains protection_omitted, protection_not_catalogs, key_period_header and clear_stated_protected. - Reference: wai-rs live_egress::protection. The corpus gains eight traces and nineteen fault cases, 114 in all; every earlier trace and case keeps its bytes. http_egress_verify.py, the second implementation, reaches all 114, fails 68 mutated copies and has each of its 110 checks caught when dropped. The corpus regenerates byte for byte on x86_64 and aarch64. - Planted faults, each refused with its own reason by both: a playlist and an MPD that omit the protection of a protected track, a KID that is not the catalog's, a key period's first segment without its new header, a clear track stated as protected. - joule-moq-http reads the protection from the owner's catalog and each later catalog's key period from the catalog track. tests/protected.rs sends a room under cbcs with three key periods through a relay: every part served verifies against the publisher's receipts, which are over the protected bytes, and decrypts under its own period's key alone, with keys the test holds and the gateway does not; the gateway's traces pass both checkers. - wai-pack's cmsf module gains Live, a track protected object by object, and entry. Not done, and stated: an egress does not check that an object is encrypted, having no key; the multivariant playlist carries no session key; a break after a key period's first boundary is carried in the playlists and not in the MPD; any system's licence exchange is that system's. Co-Authored-By: Claude Fable 5.2 <[email protected]>
63 KiB
WAI Extension: Live HTTP Egress
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). 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_egressmodule ofwai-rs(featurelive_egress) and the gatewayjoule-moq-http(joule-code/joule-code-rs/crates/joule-moq-http); the corpus islive-conformance/http-egress/, and its second implementation islive-conformance/http_egress_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) anddraft-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-22again (§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); 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
- Owner: the publisher of a set of tracks of a room, or a slot (live-rooms §6.1).
- Track: a CMAF track of an owner, named here
<kind>/<layer>from the last namespace field and the Track Name of live-rooms §6.1:video/0,audio/0,screen/0. - Group, object: MOQT's. A group is a segment and an object is a part.
- Running receipt, run: §3.5's. A running receipt is the owner's signed statement of one object of a group that is still growing; a group's run is its running receipts, in position order.
- Held: an object is held once §3.2 has accepted it. Withheld: it arrived and was refused, with a reason of §3.3.
- Closed: a group is closed once §3.4 says it is over. The open group of a track is its lowest group that is not closed, once it has a held part.
- Ticks: units of a track's timescale.
ms(x)is⌊1000·x / timescale⌋; the duration in milliseconds from tickato tickbisms(b) − ms(a), so durations along a track sum to its length exactly.
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:
- the content hash of the bytes, jwp-receipts §2.1's, is the receipt's
content_hash(content_hash_mismatch); - the receipt's proof reaches the group's signed root at its position, jwp-receipts §3
step 5 (
inclusion_proof_failed); - the bytes read as CMAF chunks of the track (
cmaf_malformed); - 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); - the object lasts at most the part target, in milliseconds (
part_over_target); - 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
| reason | the object |
|---|---|
receipt_refused |
is of a group whose receipts were refused |
receipt_missing |
is of a group that closed before its receipts arrived, or has no receipt in them |
object_missing |
followed an object of its group that never arrived |
after_withheld |
followed an object of its group that was withheld |
content_hash_mismatch |
does not hash to what its receipt states |
inclusion_proof_failed |
has a receipt whose proof does not reach the signed root |
cmaf_malformed |
does not read as CMAF chunks of the track |
group_not_sap |
starts a group and does not start with a sync sample |
cmaf_discontinuous |
does not start where its track left off |
part_over_target |
lasts longer than the part target |
segment_over_target |
takes its group past the target duration |
track_not_timed |
is of an envelope track that states no time for it (§4.4) |
track_not_independent |
is of an envelope track whose objects do not each decode alone (§4.4) |
envelope_malformed |
is not one whole WAI1 envelope of media that decodes alone (§4.4) |
envelope_undeclared |
is an envelope the catalog or the track's header does not describe (§4.4) |
protection_unstated |
is 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:
- a later group's receipts are read, and that group's object 0 has arrived, hashes to its
receipt's
content_hashand is included under the group's root: the publisher has moved on. So it has when a later group's receipts have not arrived, its run (§3.5) covers its object 0, and that object has arrived and hashes to what the run states; - the publisher ended the group (its stream finished) and the group's receipts have arrived, read or refused.
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:
| offset | size | field | meaning |
|---|---|---|---|
| 0 | 4 | magic |
WRR1 (ASCII) |
| 4 | 1 | version |
1 in this revision |
| 5 | 8 | track_alias |
u64: the media track's wai_receipt_alias |
| 13 | 32 | content_hash |
the anchored object's content hash (jwp-receipts §2.1) |
| 45 | 32 | leaf |
that object's leaf in the group's tree (jwp-receipts §2.2), in the form its object receipt will take, legacy or labelled |
| 77 | 32 | prior |
the 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 |
| 109 | 32 | root |
the 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.
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:
- the record reads as an event (live-events §3.3), of kind
meta, with an anchor (running_malformed); - its
roomis the egress's room (running_wrong_room); - its signature verifies (live-events §3.2) (
running_bad_signature); - its
principalis the owner's key (running_not_owner); - its payload is 141 bytes, begins with
WRR1, has version 1, and has apriorof 32 zero bytes exactly when the anchor'sobjectis 0 (running_malformed); - the anchor's
trackis the track's digest andtrack_aliasitswai_receipt_alias(running_other_track); - the anchor's
groupandobjectare 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:
- where i < n: ignored when its four digests are those of the run's receipt at i (a
group of an events track repeats records at its start, live-events §8.2); otherwise the
owner has stated one object twice (
running_restated); - where i > n: it skips an object (
running_skips_object); - where its
prioris not the run's root (running_prior_mismatch); - where its
rootis not the root over the run's leaves followed by its ownleaf(running_root_mismatch); - otherwise taken: it is the run's next.
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).
| reason | the running receipt, or the group's receipts |
|---|---|
running_malformed |
does not read as a meta event with an anchor and a WRR1 payload |
running_wrong_room |
is of another room |
running_bad_signature |
has a signature that does not verify |
running_not_owner |
is signed by a key that is not the owner's |
running_other_track |
names another track or another receipt alias |
running_location_mismatch |
is anchored to another object than its record was indexed at |
running_restated |
states again, differently, an object the run holds |
running_skips_object |
is past the run's next position |
running_prior_mismatch |
does not continue the run's root |
running_root_mismatch |
states 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.
4. Resources
4.1 Names
Under an owner's base URL:
| resource | what it is |
|---|---|
master.m3u8 |
the multivariant playlist (§5.6) |
manifest.mpd |
the MPD (§6.1) |
<track>/media.m3u8 |
the track's media playlist (§5.1), with the delivery directives of §5.4 and §5.5 |
<track>/init.mp4 |
the track's header, byte for byte; a protected track has none (§4.5) |
<track>/init.<i>.mp4 |
a protected track's header of the key period whose index is i, byte for byte (§4.5) |
<track>/p/<g>.<o>.m4s |
the held object o of group g: a part |
<track>/p/<g>.<o>.m4s.receipts |
that part's receipt index (§8.1), once its group's receipts were taken up |
<track>/p/<g>.<o>.m4s.running |
the run that part was held on (§8.4), where it was held on one |
<track>/p/rcpt/<h>.json |
the group receipt whose link hash is h (§8.2) |
<track>/s/<g>.m4s |
the segment of group g: its held parts, concatenated (§6.3) |
<track>/s/<g>.m4s.parts |
that segment's parts list, once it is closed (§8.3) |
wai.m3u8, wai.mpd |
the 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
- carries
wai_tempowithobjects_per_720s, from 1 to 2^32 − 1, andstart_ms(moq-streaming-format §4.4): otherwise it is not served,track_not_timed. An egress guesses no duration, from a frame rate or from when objects arrive; - declares no frame stream (
wai_frame_stream) and is no layer track (wai_staged.layerabove 0): otherwise it is not served,track_not_independent. A frame after a group's first and a layer do not decode alone, and Mode F has no sample that is not a sync sample.
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:
- the bytes are exactly one
WAI1envelope 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'swai_capabilitieslists; and its handler (videfor a manifestmediaofvideo,sounforaudio,metaotherwise) and its element are the header's, and its manifest MINOR at most the header's (envelope_undeclared); - 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:
wai.m3u8, once each has a held part:#EXTM3U,#EXT-X-VERSION:9,#EXT-X-INDEPENDENT-SEGMENTS, and per track#EXT-X-STREAM-INF:BANDWIDTH=<b>,CODECS="<e>",X-WAI-FLOOR="<e>", with,RESOLUTION=<w>x<h>for avidetrack, and<track>/media.m3u8;eis the header's element andb,wandhthe catalog's;wai.mpd, while each is on its grid: §6.1's MPD over those tracks, itspublishTimethe instant they were first all on their grids, eachAdaptationSetwith nocontentType,mimeTypevideo/mp4,audio/mp4orapplication/mp4by handler, anEssentialPropertyof schemetag:transaction.science,2026:wai/http-delivery, by which a client that does not know the scheme ignores the set, aSupplementalPropertyof schemetag:transaction.science,2026:wai/floorwhosevalueis the element, and aRepresentationwhosecodecsis the element, with itsbandwidth.
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:
- the header gives a scheme and a KID, and the scheme is
cencorcbcs; - the header reads (§3.1) with the track ID, timescale, handler and
trexdefaults of the track's first header; - there is at least one entry, and every entry is an object whose
schemeis the header's, whosedefaultKIDis an array of UUID strings that holds the header's KID, and whosedrmSystemis an object with asystemIDthat is a UUID string; - where a
drmSystemhas apssh, it is a non-empty string of Base64 in the standard alphabet, padded; where it has alaURL, that is an object whoseurl, and whosetypewhere 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:
| reason | the key period |
|---|---|
key_period_not_protected |
is of a track that has no first key period: a clear track, an envelope track, or one that is not served |
key_period_malformed |
does not read |
key_period_out_of_order |
has an index or a from_group that is not above the last taken period's, or another scheme |
key_period_late |
has 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:
- the header lines end with
#EXT-X-DISCONTINUITY-SEQUENCE:<n>in place of#EXT-X-MAP:URI="init.mp4",nthe number of segments before the first one the whole playlist lists, from the first the egress held, that began a key period: a segment begins a key period when the segment before it on the track is of another. A gap segment is of the key period of the segment before it; - before the
#EXT-X-PROGRAM-DATE-TIMEof a closed segment, and of the open group, that begins a key period:#EXT-X-DISCONTINUITY; and there, and before the first segment the playlist writes (the first after anEXT-X-SKIP, in a delta update), the period's key lines, one for each entry in order, and#EXT-X-MAP:URI="init.<i>.mp4".
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:
- the
MPDelement declaresxmlns:cenc="urn:mpeg:cenc:2013"andxmlns:dashif="https://dashif.org/CPS"after its own namespace, and itspublishTimeis the later of §6.1's instant and the instant a key period after a track's first was last taken; - there is a
Periodfor each boundary: 0, and each instant a key period after its track's first begins, which is itsfrom_grouptimes its track's grid duration. A Period'sidis its place among the boundaries, from 0, and itsstartthe boundary, asPT<seconds with three decimals>S(the first isPT0S). A boundary that is not a whole number of some track's segments from 0 leaves that track no first segment there, and the MPD is answered 404 from then on. A Period that ends at or before the start of the first segment every track still keeps is left out; the others keep theirid. The room's event streams (§7) are written in the Period whoseidis 0 and in no other: this revision carries a break after the first boundary in the playlists alone; - in each Period a track's
SegmentTemplatehas, asstartNumber, the number of the segment that starts at the boundary and, where that is above 0,presentationTimeOffset, that segment's start in ticks, after it; itsinitializationis<track>/init.<i>.mp4,ithe index of the key period that segment's group is of, or of the track's first where the Period starts before it; - a protected track's
AdaptationSetbegins, before itsProducerReferenceTime, with that key period's descriptors, each on a line:<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="<scheme>" cenc:default_KID="<the KID as a UUID>"/>, then for each entry in order<ContentProtection schemeIdUri="urn:uuid:<system ID>">holding<cenc:pssh>, the entry'spssh, where it has one, and<dashif:Laurl licenseType="<type>">, its licence URI, where it has one (licenseTypeonly where thelaURLhas atype), and written as an empty element where it has neither.&is written&.
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:
#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>,nthe 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.)- In a delta update that skips segments (§5.5),
#EXT-X-SKIP:SKIPPED-SEGMENTS=<k>. - The room's date ranges (§7), each on a line.
- The last
Wclosed 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>,ands/<g>.m4s; for a gap segment,#EXT-X-GAP,#EXTINF:<duration>,andgap/<start in ticks>.m4s, at which nothing is served. - The open group, where there is one:
#EXT-X-PROGRAM-DATE-TIME:<its start>and its parts. #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:
M ≤ Land noN: at once. WithN: at once when segmentMis no longer kept, or has more thanNparts; otherwise (the request is then for part 0 of the next segment) whenM < Lor the open group has a part.M = L + 1: only withN, once the open group has more thanNparts. WithoutNthe request waits for segmentMto close.- Before any segment has closed, with
Fthe number the first will have: at once whenM < F; withN, whenM = Fand the open group has more thanNparts.
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):
MPDoftypedynamic,profilesurn:mpeg:dash:profile:isoff-live:2011,http://www.dashif.org/guidelines/low-latency-live-v5,availabilityStartTimethe anchor,publishTimethe instant every track was first on its grid,minimumUpdatePeriodthe target duration,timeShiftBufferDepththe least over the tracks ofmsof the window's segments,minBufferTimethe part target;- a
ServiceDescriptionwhoseLatencytargetis the latency target, in milliseconds; - one
Period,id0, starting at 0, holding the room's event streams (§7) and oneAdaptationSetper track, with aProducerReferenceTimeof typeencoderwhosewallClockTimeis the anchor atpresentationTime0, aSegmentTemplatewith the track'stimescale,durationthe grid's,startNumber0,initialization<track>/init.mp4andmedia<track>/s/$Number$.m4s, and oneRepresentationwith the catalog's values; - on each
SegmentTemplate,availabilityTimeOffset, in seconds with three decimals:msof the grid's duration lessmsof the first part of the first whole segment, withavailabilityTimeComplete="false"; neither is written where that is 0; - a
UTCTimingof schemeurn:mpeg:dash:utc:http-iso:2014, valuetime: the egress's clock, as an ISO 8601 date, attimebeside the MPD.
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.
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:
- sent to listed: from the instant a publisher has handed a part's object to its transport to the instant an HTTP client that was waiting on a blocking reload (§5.4) has the last byte of the playlist that lists it;
- sent to served: from that instant to the instant a client that was waiting on the hinted part (§5.3) has its last byte;
- edge to presentation: by a player's own report, how far the position it presents is behind the end of the last part the playlist or the MPD offers.
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:
| event | members | what happened |
|---|---|---|
receipts |
track, group, bytes |
the group's receipts arrived |
object |
track, group, object, bytes |
an object arrived |
end |
track, group |
the publisher ended the group |
running |
track, group, object, bytes |
an 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_period |
track, index, from_group, entries, init |
a later catalog stated a key period of track (§4.5): its entries, and its header |
ads |
hls (date-range lines), dash (event streams) |
the room's breaks, as §7 writes them from here on |
request |
id, target |
a request arrived; target is the path under the owner, with its query |
emit |
id, bytes |
that many more bytes of the request's body were sent, before its end |
response |
id, status, body |
the 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:
- the trace or the entry does not read as §10.1 (
trace_malformed); - an
emitor aresponsefor anidno open request has (response_unasked); - an
emitafter 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); - for a
response:- where the request was one §5.4 refuses when it arrived, any status but 400
(
status_mismatch); - 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); - where the state waits and the request has waited three target durations, 503 with an empty body passes;
- a media playlist request that the state still waits on (
blocking_answered_early); - 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); - a status that is not the state's, 200 or 404 (
status_mismatch); - 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 anmdatholding exactly the held envelope,sample_not_alonewhere that payload begins with the envelope and holds more, andsample_mismatchotherwise; and, for a media playlist and formanifest.mpd, the reason of rule 5 where one applies.
- where the request was one §5.4 refuses when it arrived, any status but 400
(
- 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 notNONE:clear_stated_protected. A protected track's, at the first part (#EXT-X-PART:with aURI="p/<g>.) or segment (s/<g>.m4s) of a groupgthat has a key period, in line order: no key line in force with a method other thanNONE(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'sEXT-X-MAPline (key_period_header). For the MPD, where the state has one: for each Period the state lists, in order, a served Period of thatidis looked for (the text from<Periodat 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 servedAdaptationSetof that Period whoseSegmentTemplate'smediais 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), aninitializationthat 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
| reason | the trace |
|---|---|
trace_malformed |
does not read |
response_unasked |
answers a request that was not made |
status_mismatch |
gives a status the state does not |
listed_not_held |
lists a part or a segment before the egress held it |
withheld_listed |
lists a withheld object |
blocking_answered_early |
answers a blocking reload before the playlist held what it named |
playlist_mismatch |
gives playlist bytes the state does not |
manifest_mismatch |
gives MPD bytes the state does not |
withheld_served |
serves a withheld object |
served_before_held |
sends bytes the egress did not yet hold |
served_uncovered |
serves a part its group's run does not cover |
segment_answered_open |
ends a segment while its group was open |
bytes_mismatch |
gives media bytes that are not the held object's |
sidecar_mismatch |
gives receipts, a parts list or a run the state does not |
sample_mismatch |
gives, for a part of an envelope track, a sample that is not the held envelope |
sample_not_alone |
gives, for a part of an envelope track, an mdat that holds the envelope and more |
protection_omitted |
gives a playlist or an MPD that lists a protected track's media and does not state its protection |
protection_not_catalogs |
states a scheme, a KID, a system, a pssh or a licence URI that is not the catalog's for that key period |
key_period_header |
lists a key period's media under a header that is not that period's |
clear_stated_protected |
states 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.
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
- An egress is a subscriber, and no more trusted. It cannot make an object the publisher did not sign pass a client's check (§8); it can withhold, delay and reorder, which a gap, a late part or a failed check shows.
- The catalog is unsigned. Codecs, bit rates and the header come from the catalog. A header that is not the publisher's makes the media fail to decode; it does not make other media pass, since each part is checked against a signed receipt.
- The anchor is the egress's clock, and a wrong one moves every date and every availability time together. It is not evidence of when anything was made.
- A running receipt is the owner's word and no more. A part held on a run was held to a digest the owner signed for that object of that group, and a client checks the same (§8.4). An owner that signs a run and then other receipts for the group has signed both: the egress refuses the second, keeps the records, and reports it. A relay that withholds running receipts makes parts wait for their group's receipts; it cannot make a part pass. A running receipt replayed from the owner's earlier events states the object it always stated: its anchor names the track, the group and the object.
- A protected track's protection is the catalog's word. The catalog is unsigned, and an egress states what it states: a relay or an egress that names another KID, another licence URI or a clear header can make a player ask for the wrong key or for none, and the player then fails to decode. It cannot make protected media play without its key, and it cannot make other media pass a client's check of the receipts (§8), which are over the protected bytes. An egress holds no key and can release none; who is released a key is key-release's, at the key holder.
- Withheld bytes stay withheld. An egress keeps no path, by part, by segment or by range, that reaches an object's bytes before §3.2 has held it.
- Blocking requests hold connections. An egress bounds how many it holds and for how long (three target durations), and refuses at once those §5.4 names.
- Who fetches. A request to an egress shows it the viewer's address and what the viewer watches, as any HTTP delivery does. This text carries no viewer identity and asks for none.