WAI Extension: HLS Windows and Clips
Mirrored from the canonical text at commit 0d3f0d7c ().
Status: Draft. Registers the record
wai.delivery.hls_clip: what a holder of a classical HLS presentation kept of it, signed as it kept it, and the clips it cut from what it kept. A window is the recent span of one media playlist of fragmented MPEG-4 segments that a holder keeps for a player to seek back into; each time what it lists changes, a segment admitted, a segment evicted or the stream ended, the holder signs a window revision (wai-hls-window, §5): the segments it adds, each by its SHA-256 and length with its duration, its date and its header, how many it evicts, the state that results, and a link to the revision before it. A clip is a span of the window turned into an on-demand playlist; its clip record (wai-hls-clip, §6) states every segment and header the clip presents and the revision it was cut from. Both playlists are written from the records by one rendering (§4), so a verifier renders the playlist from the record and compares the bytes. A viewer of the live window holds the chain of revisions and each segment it lists at signed (§7.2); a clip verifier holds the clip’s files to its record and the record to the window it was cut from (§7.3); and what a window or a clip lists is the listing a holder’s storage snapshots state (receipt-ledger §4.13), the same objects by the same hashes (§9). No new container, no new playlist tag and no transport. The reference implementation is thehls_clipmodule ofwai-rs(featurehls_clip); the corpus islive-conformance/hls-clips/, and its second implementation islive-conformance/hls_clips_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision: the WAI texts this one cites; RFC 8216 whole (August 2017, read 2026-11-11, SHA-256
954910a5ad73506cfdb5abe11571f6b859d49d1f82191ffe616595a40687e58eof the text the RFC Editor serves), its §3.3, §4.1, §4.3.1.2, §4.3.2.1, §4.3.2.3, §4.3.2.5, §4.3.2.6, §4.3.3.1 to §4.3.3.5, §4.3.5.1, §6.2.1, §6.2.2 and §7 in particular; and its draft successor,draft-pantos-hls-rfc8216bis-22(1 May 2026, the latest revision in the IETF archive on 2026-11-11, SHA-2565e8ce0e44266757dc0ab44061f3030fb3cf6de32fb217555aebff3507912b6f2), §4.4.1.2, §4.4.2.1, §4.4.3.1 to §4.4.3.5 and §4.4.4.1 to §4.4.4.6, to hold that every tag the rendering writes keeps its meaning there. Every tag written is RFC 8216’s; the draft’s only change to them that a rendering meets is that a target duration is at least 1 (its §4.4.3.1), which §5.2 and §6.2 require. A section number of the draft in this text is that revision’s.
1. Scope
In scope: how an object of a window or a clip is named and what a set of them lists (§3); the rendering of a window’s live playlist and a clip’s on-demand playlist (§4); the window revision, its rules and the state a chain of revisions gives (§5); the clip record and its rules (§6); the verification of a window’s chain, of a live window by a viewer, and of a clip (§7); where each is served (§8); and what a holder’s storage snapshots of a window and a clip state (§9).
Out of scope: how a holder ingests, packages or keeps the presentation; which segments a window keeps and for how long, which is its holder’s policy (a 24-hour age and a byte budget are typical, as live-recording §8.2 bounds a relay’s); the multivariant playlist that names several renditions’ windows or clips, each of which is a window or a clip of its own here; byte ranges, low-latency parts, encrypted segments and I-frame playlists, which a rendering never writes; and what is in an object’s bytes, which a record names and never reads. A room’s recording and a clip of a take are live-recording’s, a WAI playlist and its receipt are http-delivery’s (§8.6 there), and a room’s tracks served over HTTP with statements beside their playlists are live-http-egress’s.
Relationship to the core spec: wai.delivery.hls_clip is a record (SPEC §5).
It names no payload format and is not dispatchable.
2. Terms
- Holder: the Ed25519 key that keeps a window and its clips and signs their records, as receipt-ledger §2 has a holder keep objects for a buyer. A verifier holds the holder’s key from outside the records (the page that names the window, a buyer’s contract), never from them.
- Init: a Media Initialization Section (RFC 8216 §3), the header a segment is read with. Segment: a fragmented MPEG-4 Media Segment (RFC 8216 §3.3). Each is an object.
- Instant: milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted.
- Media sequence number, discontinuity sequence number: RFC 8216 §4.3.3.2 and §6.2.1’s.
- Identity of a record: the SHA-256 (FIPS 180-4) of its bytes, its RFC 8785 form (§5.1, §6.1).
- Hexadecimal is lowercase. An integer is unsigned and at most 2^53 − 1. A u8 is one byte, and a u64 eight bytes, little-endian.
3. Objects
3.1 Names
An object is named by its content hash, the SHA-256 of its bytes, and its length. A segment’s entry is a JSON object with exactly these members:
| member | value |
|---|---|
sha256 | its content hash |
bytes | its length, at least 1 |
duration_ms | its duration in milliseconds, at least 1: what EXTINF states (RFC 8216 §4.3.2.1) |
date_ms | the instant of its first sample: what EXT-X-PROGRAM-DATE-TIME states (RFC 8216 §4.3.2.6), at most 253 402 300 799 999 (9999-12-31T23:59:59.999Z) |
discontinuity | true when there is a discontinuity between it and the segment before it (RFC 8216 §4.3.2.3) |
init | the content hash of its init |
An init is stated as {"bytes": <length>, "sha256": <content hash>}. A playlist names an init as
init/<content hash>.mp4 and a segment as seg/<content hash>.m4s, each resolved against the
playlist’s URI (RFC 8216 §4.1), so an object’s name is its bytes’. No two objects one window
revision lists, and no two a clip lists, have one content hash: a segment carries its own sequence
number and decode time, so two segments with the same bytes are one segment listed twice, and an
init is never a segment.
3.2 The listing
What a window revision or a clip lists is its segments and the inits they are read with. Its
listing is that of receipt-ledger §4.13 over those objects: objects,
how many there are; bytes, their lengths summed; and listing, the SHA-256 over the 19 bytes
wai:ledger-listing and 0x01 followed by each object, in strictly ascending order of content
hash, as its content hash (32 bytes) and its length (u64). A playlist and a record are the holder’s
statements of the objects and are not listed, as a package’s receipt is not one of its objects.
4. The rendering
A playlist is rendered from: a target duration t; a media sequence number m; a discontinuity
sequence number d; whether it is on demand; whether it ends; whether its segments are
independent; and its segments, at least one, in order. Each line ends in LF (0x0A), integers are
written in decimal without leading zeros, a duration is written as seconds with three fractional
digits (2.002 for 2002 ms), and a date as YYYY-MM-DDThh:mm:ss.sssZ: the proleptic Gregorian
date and UTC time of its instant, the year in four digits and every field zero-padded (RFC 3339
§5.6, as RFC 8216 §4.3.2.6 asks of ISO 8601). The lines are:
#EXTM3U;#EXT-X-VERSION:6, the version RFC 8216 §7 requires ofEXT-X-MAPwithoutEXT-X-I-FRAMES-ONLYand above which a server writes none (§6.2.1 there);#EXT-X-TARGETDURATION:<t>;#EXT-X-MEDIA-SEQUENCE:<m>;#EXT-X-DISCONTINUITY-SEQUENCE:<d>when d is not 0;#EXT-X-PLAYLIST-TYPE:VODwhen it is on demand; and#EXT-X-INDEPENDENT-SEGMENTSwhen its segments are independent.- Each segment, in order:
#EXT-X-DISCONTINUITYwhen its entry states one and it is not the first;#EXT-X-MAP:URI="init/<init>.mp4"when it is the first or its init is not the one of the segment before it;#EXT-X-PROGRAM-DATE-TIME:<date>;#EXTINF:<duration>,; andseg/<sha256>.m4s. #EXT-X-ENDLISTwhen it ends.
So a segment’s discontinuity sequence number is d plus the EXT-X-DISCONTINUITY lines before
it (RFC 8216 §6.2.1), and the first segment’s is d: a discontinuity before the first segment is
written into d, never as a line (§5.3). Every segment has its date, so the mapping of dates to
segments is never ambiguous (RFC 8216 §6.2.1), and each has its header. A clip’s playlist is on
demand and ends, with m and d 0 (§6.1); a window’s is never on demand, and ends once the window
has ended (§5.3).
A playlist reads as a rendering when some inputs render to exactly its bytes. A reader splits
it at LF and reads, in order: the header lines of step 1, EXT-X-DISCONTINUITY-SEQUENCE and the
two that follow it being optional; then segments until #EXT-X-ENDLIST or the end, each its
optional discontinuity, its optional map (not optional on the first), its date, its duration and
its URI, every value of the form the rendering writes; and, where it holds that, the inputs those
lines give. It then renders them and requires its bytes. The inputs read back are the ones
rendered, but for the first segment’s discontinuity, which is in d, and every length, which a
playlist does not state: a reader of a playlist gives each length as unknown.
5. A window revision
5.1 Form
A window revision is one JSON object in RFC 8785’s form, at most 65 536 bytes, with exactly these members:
| member | value |
|---|---|
kind | "wai-hls-window" |
version | 1 |
window | the window’s name, of the holder’s choosing: 1 to 128 bytes of A–Z, a–z, 0–9, ., _, - and / |
revision | its number: 0 for the window’s first, and each later one the number after its parent’s |
parent | the identity of the revision before it, or null for revision 0 |
at_ms | the instant the holder made it, on its clock |
target_duration_s | the target duration, 1 to 86 400 (RFC 8216 §4.3.3.1) |
independent_segments | whether its playlist states EXT-X-INDEPENDENT-SEGMENTS (RFC 8216 §4.3.5.1) |
evicted | how many segments it removes from the start of what its parent listed |
inits | the inits it lists that what its parent listed after the eviction does not, at most 16 |
added | the entries of the segments it appends at the end, in order, at most 256 |
ended | whether the window’s stream has ended: its playlist then ends |
media_sequence | the media sequence number of the first segment it lists |
discontinuity_sequence | the discontinuity sequence number of the first segment it lists |
objects, bytes, listing | what it lists (§3.2) |
playlist_sha256 | the SHA-256 of the playlist it renders (§5.3) |
signer | the holder’s key |
sig | Ed25519 by signer over the 15 bytes wai:hls-window (ASCII) and 0x01, followed by the RFC 8785 form of every member but sig, verified as SPEC §7 Signatures requires |
A revision is served as exactly its RFC 8785 form, and its identity is the SHA-256 of those bytes.
A reader reads the form whole: I-JSON (RFC 7493), its one canonical form (a member named twice, at
any depth, white space, another order of members or another escape is not that form), every
member and no other, each of its form; otherwise hls_record_malformed.
5.2 Rules
A reader then refuses, the first that applies:
revisionis 0 andparentis notnull, or it is not 0 andparentisnull; revision 0 evicts any or adds none; or its inits are not distinct, each used by an added segment, in the order of the first added segment that uses each:hls_record_invalid;- two of its added segments, or an added segment and an init, have one content hash:
hls_object_repeated; - revision 0’s first added segment states a discontinuity:
hls_discontinuity_first; - an added segment’s duration, rounded to the nearest second, half a second up, is above the target
duration (RFC 8216 §4.3.3.1: ⌊(
duration_ms+ 500) / 1000⌋ >target_duration_s):hls_segment_too_long; - an added segment’s date is not after the one before it:
hls_time_not_increasing.
Then its signature: bad_key, bad_signature.
5.3 The state a chain gives
A window’s state after a revision is: the segments it lists, in order, each with its entry; the lengths of the inits they use; its media sequence number m and discontinuity sequence number d; whether it has ended; and its target duration and whether its segments are independent. Its playlist is the rendering (§4) of its target duration, m, d, not on demand, ending exactly when it has ended, its independence and its segments.
Revision 0’s state lists its added segments, with the media_sequence and
discontinuity_sequence it states: a window may begin in the middle of a stream and keep its
numbers. A later revision’s state is its parent’s with the first evicted segments removed and
the added ones appended. With T the parent’s listed segments followed by the added ones, T₀
the parent’s first, and k the number evicted:
- m is the parent’s m plus k;
- d is the parent’s d plus the number of T₁ to Tₖ that state a discontinuity: every discontinuity of a segment removed from the playlist’s start, and that of the new first, whose line is not written, is counted into d, so that no listed segment’s discontinuity sequence number changes (RFC 8216 §6.2.2). T₀’s own is already in the parent’s d.
The revision states that state’s media_sequence, discontinuity_sequence, objects, bytes,
listing and the SHA-256 of its playlist. Its inits are exactly those its added segments use that
the segments the parent lists after the eviction do not use, in the order of first use; an init
no listed segment uses any longer has left the listing, and is stated again by the revision that
lists it again. A revision changes the window: it adds a segment, evicts one or ends it. A window
that has ended adds nothing more and stays ended; it may still evict, as RFC 8216 §6.2.1 lets a
server remove segments from a playlist’s start. Every revision lists at least one segment and at
most 65 536.
RFC 8216 §6.2.2 asks a server not to leave a live playlist shorter than three target durations; that is the holder’s eviction policy, and a revision that does is not refused here. What a window’s playlist states of its segments never changes while it lists them: a revision appends and removes, and nothing else, as RFC 8216 §6.2.1 permits.
6. A clip record
6.1 Form
A clip record is one JSON object in RFC 8785’s form, at most 16 777 216 bytes, with exactly these members:
| member | value |
|---|---|
kind | "wai-hls-clip" |
version | 1 |
window | the name of the window it was cut from (§5.1) |
cut_from | the identity of the window revision whose listing it was cut from, or null for a clip of a window that signs no revisions |
first | the media sequence number, in that window, of its first segment |
from_ms, to_ms | the span it presents, from the instant from_ms (included) to to_ms (excluded) |
cut_at_ms | the instant the holder cut it, on its clock |
target_duration_s | the target duration, 1 to 86 400 |
independent_segments | whether its playlist states EXT-X-INDEPENDENT-SEGMENTS |
inits | the inits it lists, 1 to 256, each used, in the order of the first segment that uses it |
segments | the entries of its segments, in order, 1 to 65 536 |
playlist_sha256 | the SHA-256 of its playlist |
signer | the holder’s key |
sig | Ed25519 by signer over the 13 bytes wai:hls-clip (ASCII) and 0x01, followed by the RFC 8785 form of every member but sig, verified as SPEC §7 Signatures requires |
Its playlist is the rendering (§4) of its target duration, m 0 and d 0, on demand, ending, its
independence and its segments. Its media sequence starts at 0, so it is an on-demand presentation
of its own; first places it in the window. A record is served as exactly its RFC 8785 form, and
its identity is the SHA-256 of those bytes. It reads as a window revision does (§5.1), with its own
members (hls_record_malformed).
6.2 Rules
A reader then refuses, the first that applies:
from_msis not belowto_ms:hls_record_invalid;- a segment’s init is not one of its inits:
hls_init_unlisted; - its inits are not exactly the distinct inits its segments use, in the order of the first
segment that uses each:
hls_record_invalid; - two of its segments, or a segment and an init, have one content hash:
hls_object_repeated; - its first segment states a discontinuity:
hls_discontinuity_first(a clip’s first segment follows nothing in it); - a segment’s duration is above the target duration as §5.2 rule 4 rounds it:
hls_segment_too_long; - a segment’s date is not after the one before it:
hls_time_not_increasing; - its segments are not exactly the ones that present some of the span: the first’s date is at or
before
from_msand its end, its date plus its duration, after it; and the last’s date is beforeto_msand its end at or after it (hls_span_not_covered). So a clip carries no segment wholly outside its span, and leaves out none of it; playlist_sha256is not the SHA-256 of its playlist:hls_record_inconsistent.
Then its signature: bad_key, bad_signature.
A holder cuts a clip of the span [a, b) from a revision’s state by taking the listed segments
from the one whose date is at or before a and whose end is after it to the one whose date is
before b and whose end is at or after it, each entry as listed but for the first’s
discontinuity, which a clip does not state; the inits they use, in the order of first use, with
their lengths; first the first one’s media sequence number; and cut_from the revision’s
identity.
7. Verification
7.1 A window’s chain
A verifier given the holder’s key and a window’s revisions in order, from revision 0, and for any of them the playlist served with it, checks each in order and stops at the first refusal, which it reports with the revision’s position:
- it reads and holds to its rules (§5.1, §5.2);
- its
signeris the holder’s key (hls_signer_unknown); - its
revisionis its position, and, past the first, itsparentis the identity of the one before it (hls_revision_unchained); - past the first, its
window,target_duration_sandindependent_segmentsare the first’s (hls_window_changed), and itsat_msis not below its parent’s (hls_revision_time); - past the first, it adds no segment, and states
ended, once its parent has ended (hls_window_ended); and it adds, evicts or ends (hls_revision_empty); - it evicts no more segments than its parent lists, and lists at least one and at most 65 536
(
hls_record_inconsistent); - no added segment is an object the parent lists after the eviction, and no init it states is
a segment the parent lists then (
hls_object_repeated); each added segment’s init is one of its inits or one that the segments the parent lists then use (hls_init_unlisted); and none of its inits is one of the latter (hls_record_inconsistent); - its first added segment’s date is after that of the last segment the parent lists after the
eviction (
hls_time_not_increasing); - the state it gives (§5.3) has the
media_sequence,discontinuity_sequence,objects,bytes,listingandplaylist_sha256it states (hls_record_inconsistent); - the playlist served with it, where one is given, is its playlist (
hls_playlist_mismatch).
A chain that passes states, for each revision, its identity, its media sequence and discontinuity sequence numbers, how many segments it lists and its listing; and the window’s subject (§9).
7.2 A viewer of the live window
A viewer holds the holder’s key and a head: the latest revision it accepted, and the state it
holds for it. It is given, each time it reloads, the playlist and the revision served at
rev/<SHA-256 of the playlist>.json (§8), and fetches a parent by its identity. It checks:
- The first revision it is given is its anchor: it reads, holds to its rules and verifies
(§5.1, §5.2), and its signer is the holder’s key (
hls_signer_unknown); the playlist’s SHA-256 is itsplaylist_sha256(hls_playlist_mismatch); the playlist reads as a rendering (§4), not on demand (hls_playlist_malformed); and its target duration, independence, whether it ends, m and d are the revision’s (hls_record_inconsistent). The viewer holds the state the playlist reads as, every length unknown, and the revision as its head (anchored). - The head again (a reload with no new revision): the playlist is its playlist
(
hls_playlist_mismatch), and nothing changes (unchanged). - A later revision becomes the head only when it chains to the head. From it the viewer
follows
parent, fetching each parent fromrev/<parent>.json, until it reaches the head’s identity. Each fetched parent is served (hls_revision_parent_missing), its SHA-256 is the identity it was fetched by and its number one below its child’s (hls_revision_unchained), and it reads, holds to its rules, verifies and is the holder’s (§7.1 steps 1 and 2). A walk that reaches a revision at or below the head’s number, or with no parent, before the head does not chain (hls_revision_unchained), so a revision older than the head never does. Then, oldest first, each revision walked and the new one are held to §7.1 steps 4 to 8 against the state the viewer holds, and step 9 as far as a state of unknown lengths allows: itsmedia_sequence,discontinuity_sequenceandplaylist_sha256. The playlist is the new head’s (hls_playlist_mismatch). The viewer then holds the new state and head (accepted). - A head too far back. A viewer fetches at most 64 parents for one revision. When the walk
needs more, and the revision is ahead of the head (its m plus the segments it lists is
above the head’s), the viewer anchors again at it, as in step 1 (
reanchored): the revisions between were not walked, and no segment they alone listed is held at signed. One that is not ahead is refused (hls_revision_walk_exceeded). Before it anchors again, every segment the new state lists at a media sequence number the old one lists is the same content hash, or the revision is refused (hls_listing_rebound): a holder that signs two windows under one name is caught where they meet.
A revision refused leaves the head and the state as they were. A segment is held at signed (the word of http-delivery §11.6) when the head’s state lists it at its media sequence number and its bytes’ SHA-256 is its content hash; the viewer presents it at that number and no other. So a viewer of the live window holds each segment it plays to the holder’s signed word, and holds the same word for a clip cut later from a revision it accepted (§7.3 step 5).
7.3 A clip
A clip verifier is given the holder’s key; the clip record; the clip’s files, clip.m3u8 and each
init/<content hash>.mp4 and seg/<content hash>.m4s, each present or absent; and, where it holds
one, the window: the states of the revisions of a chain it verified (§7.1), or a viewer’s (§7.2).
It stops at the first refusal, which it reports with where:
- the record reads and holds to its rules (§6.1, §6.2), at
record; - its
signeris the holder’s key (hls_signer_unknown), atrecord; clip.m3u8is given (hls_file_missing) and is its playlist (hls_playlist_mismatch), atplaylist;- each init, then each segment, in order, is given (
hls_file_missing), and its bytes’ SHA-256 is its content hash and their length itsbytes; bytes whose SHA-256 is another object’s of the record arehls_object_swapped, and any othershls_object_altered; atinitsorsegmentsand its position; - with the window:
cut_fromnames a revision of it made at or beforecut_at_ms(hls_revision_unknown); and the clip’swindow,target_duration_sandindependent_segmentsare the window’s, and its segments are the ones that revision’s state lists from media sequence numberfirston, each with the same content hash, length, duration, date and init, the same discontinuity but for the first, and each init with the same length (hls_clip_not_in_window); atwindow.
A clip that passes is accepted, and the verifier states its identity, how many segments it presents and their duration, its listing (§3.2) and its subject (§9). A clip accepted without a window is held to its holder’s word alone: a segment the holder left out and signed for is found only against the window it was cut from, whose revisions the holder signed before the cut.
8. Where they are served
- A window: its playlist at a path of its holder’s choosing, the objects at
init/andseg/beside it (§3.1), and each revision, immutable, atrev/<identity>.jsonand atrev/<playlist_sha256>.json, both resolved against the playlist’s URI: the second name is how a viewer finds the revision of the playlist it was served, whatever revision was the latest when it asked; the first is how it fetches a parent. A revision once served is never changed. - A clip: a directory of its own, holding
clip.m3u8, its playlist;clip.json, its record; andinit/andseg/with the clip’s objects. The directory’s name is the holder’s.
9. Storage
A holder that bills a window’s or a clip’s storage under a tariff of version 8 of
receipt-ledger (§4.13 there) keeps it as one stored set. The objects that
ledger’s snapshots list are the ones a revision and a clip list (§3.2), named the same way, by
their content hashes and lengths, so a snapshot’s objects, bytes and listing are a
revision’s or a clip’s figures, which anyone holding its record recomputes; a segment a window and
a clip both list is one object with one content hash in both listings, kept once or twice as the
holder keeps it, and listed by each set. Nothing in receipt-ledger changes.
- The subject. A stored set’s subject is the digest of a name of its holder’s choosing
(receipt-ledger §4.13). A holder that keeps a window or a clip as a stored set names it by the
SHA-256 over the 16 bytes
wai:hls-subject(ASCII) and 0x01, followed by the record kind (u8) and the identity: for a window, kind 39 and the identity of its revision 0, so every snapshot of the window over its life is of one subject; for a clip, kind 40 and its record’s identity. - A window’s snapshots. The window’s listing changes at each revision that adds or evicts, and
the holder signs a snapshot then:
atthe revision’sat_msin microseconds (× 1000), and itsobjects,bytesandlistingthe revision’s. A buyer given the chain checks each snapshot against the revision of its instant, and each interval of a storage record has its segments by revision. - A clip’s snapshots state the clip’s listing for as long as it is kept.
Record kinds 39 (a window revision) and 40 (a clip record), each named by its identity (§5.1, §6.1), are registered in receipt-ledger §5.2 and cited here, in the subject.
10. Reasons
| reason | raised at |
|---|---|
hls_record_malformed | §5.1, §6.1: a record that does not read as its form |
hls_record_invalid | §5.2 rule 1, §6.2 rules 1 and 3 |
hls_object_repeated | §5.2 rule 2, §6.2 rule 4, §7.1 step 7 |
hls_discontinuity_first | §5.2 rule 3, §6.2 rule 5 |
hls_segment_too_long | §5.2 rule 4, §6.2 rule 6 |
hls_time_not_increasing | §5.2 rule 5, §6.2 rule 7, §7.1 step 8 |
hls_init_unlisted | §6.2 rule 2, §7.1 step 7 |
hls_span_not_covered | §6.2 rule 8 |
hls_record_inconsistent | §6.2 rule 9, §7.1 steps 6, 7 and 9, §7.2 steps 1 and 3 |
bad_key | §5.2, §6.2: a signer that is not a key SPEC §7 Signatures admits |
bad_signature | §5.2, §6.2: a signature that does not verify |
hls_signer_unknown | §7.1 step 2, §7.2 steps 1 and 3, §7.3 step 2 |
hls_revision_unchained | §7.1 step 3, §7.2 step 3 |
hls_window_changed | §7.1 step 4 |
hls_revision_time | §7.1 step 4 |
hls_window_ended | §7.1 step 5 |
hls_revision_empty | §7.1 step 5 |
hls_playlist_mismatch | §7.1 step 10, §7.2 steps 1 to 3, §7.3 step 3 |
hls_playlist_malformed | §7.2 step 1 |
hls_revision_parent_missing | §7.2 step 3 |
hls_revision_walk_exceeded | §7.2 step 4 |
hls_listing_rebound | §7.2 step 4 |
hls_file_missing | §7.3 steps 3 and 4 |
hls_object_altered | §7.3 step 4 |
hls_object_swapped | §7.3 step 4 |
hls_revision_unknown | §7.3 step 5 |
hls_clip_not_in_window | §7.3 step 5 |
11. Conformance
live-conformance/hls-clips/ holds the corpus, in one file,
vectors.json, written from fixed seeds by wai_hls_clip_vectors gen. Its objects are short
box-shaped stand-ins for CMAF headers and segments, each a ftyp and moov, or a styp, moof
and mdat, of generated bytes: a record names bytes and never reads them, so no decodable media
is needed, and none is a recording.
- Dates: instants and the dates the rendering writes for them, the first instant, a leap day, a century year that is not a leap year and the last instant.
- Windows: chains of revisions with the playlists served with them, each with the outcome §7.1 gives. A window of ten revisions that admits, evicts, changes its header with a discontinuity, splices a second discontinuity, evicts past both, ends and evicts after it ended; one that begins in the middle of a stream with its own numbers and independent segments; a long one, which lists 67 segments, ends, and then evicts one a revision for 66 revisions; and a second chain under the long one’s name and key, of six other segments and then one a revision. And each a refusal at its last revision, one for each reason §7.1 gives and each rule of §5.2: a parent link broken, a revision skipped, revision 0 with a parent, another holder’s revision, a signature that does not verify and one by a key of small order, a renamed window, a changed target duration, a revision made before its parent, a segment after the end and an end undone, a revision that changes nothing, one evicting more than is listed and one emptying the window, a segment listed twice, an init never stated and one stated again, a date going back, a wrong media sequence number, discontinuity sequence number, listing, byte count or playlist digest, a playlist edited, a segment over the target duration and one at its bound, a discontinuity on the first segment, an unused init, and records that do not read.
- Viewers: a viewer’s steps over those windows, each with its outcome (§7.2): anchored at revision 0 and at a later one, following each revision and skipping ahead with its parents served, a reload of the head, a parent withheld and then served, an older revision, a playlist edited, a playlist that is not a rendering, a walk of exactly 64 parents, one that is too long and not ahead, one that anchors again, and one that meets another chain under the same name.
- Clips: clips of the windows with their files, each with its outcome (§7.3): a whole window, a span, a span across a header change and a discontinuity, a span whose first segment was the window’s discontinuity, one segment, a clip of a window that signs no revisions and one with independent segments; and refusals: two segments’ files swapped, a segment’s file missing, a segment dropped from a record signed again, a byte changed in a segment and in a header, a playlist edited and one missing, a discontinuity on the first segment, a span padded, one cut short and one empty, two segments reordered, a segment not the window’s, a revision not in the window and one made after the cut, another window, another holder, a bad signature, an init never stated or never used, a segment listed twice, one over the target duration, a wrong playlist digest and a record that does not read.
A conforming verifier gives every vector the outcome, the reason, the place and the figures the
corpus states. wai_hls_clip_vectors verify and the test tests/hls_clip.rs check that the file
regenerates byte for byte, that wai-rs gives every vector its outcome, and that §10 lists
exactly the reference’s codes. live-conformance/hls_clips_verify.py is the second
implementation, written from this text: it shares no code with wai-rs, renders and reads
playlists, computes listings and dates itself, verifies signatures with WAI’s strict rule
(claim-conformance/_ed25519.py), gives every vector the same outcome, fails every mutated copy
of the corpus and catches each of its own checks dropped, with the standard library alone.
12. Security and privacy considerations
- What the records bind. A revision binds, under the holder’s key, the segments a window listed at that revision, in order, each by its content hash, with the dates, durations, discontinuities and headers its playlist states; the chain binds the order of revisions, so a revision cannot be left out, replayed or changed without its successors failing; and a clip record binds its segments, its playlist’s bytes and the revision it was cut from. A segment changed, dropped, swapped or reordered, or a playlist edited, fails a verifier that holds the record, and a clip whose holder signs a span the window never listed fails against the window.
- What rests on the holder’s word. Classical HLS carries no publisher’s signature, so the records are the holder’s word for what it received: that these were the stream’s segments, at those instants, is its statement, and a publisher holding its own segments recomputes every content hash and listing from them. The instants are its clock’s. Whether it kept what a revision lists is its word too, as receipt-ledger §12 has a holder’s storage be. A viewer that anchors at a playlist takes the holder’s word for the revisions before it; one that re-anchors past 64 revisions takes it for those between.
- One window, one chain. A holder that signs two chains under one window’s name equivocates;
a viewer that meets both where they list one media sequence number refuses the second
(
hls_listing_rebound), and a verifier of a chain sees one chain from its revision 0. - Privacy. A window’s name is the holder’s label and identifies no person; a record carries no account and no viewer. A clip carries only the segments of its span, and a revision only the segments of the window, which anyone allowed to play the window already fetches.