extensions/audience-decision.md registers two route-record documents, each signed in its canonical form under its own domain. `wai.video.audience` (`wai-audience-decision`): a decider's signed decision of whether the sessions in one region may be shown a programme over a span, under the blackouts in force. Each blackout is named by the id a session class carries (http-delivery Appendix H `blackouts`) and lists its regions in rung-price §2's region codes, with the programme shown in its place. A session aggregates its own location before it leaves the session: to the one listed code that holds it, to outside, or to unresolved (no location, or a country of which only subdivisions are listed); the aggregation decides exactly what the location would. A decision whose evidence holds anything else (coordinates, a postal code, a session's identifier) or a region finer than the aggregation gives is refused `audience_location_not_aggregated`; one whose region the blackouts neither list nor hold, `audience_region_not_in_set`. The blackouts applied (every one, unresolved), the verdict (show, withhold, or alternate when all that apply name one programme) and that programme are recomputed; a session class of the programme carries the applied set as its `blackouts` and the region's country as its `geo`, and a verifier given both states the class's hash. `wai.delivery.territory` (`wai-territory`): a publisher's signed statement of where its content may be shown (everywhere, only in some regions, everywhere but some). Its latest statement stands alone; a lapsed or contradicted latest revives none. The territory (within, outside, undetermined, or unstated with its reason) is reported beside a server key's placement, which cache-authority §4.4 computes from its own inputs and no statement changes; a statement that names servers, nodes, origins, roles or may_sign is refused `territory_scopes_serving`. cache-authority §4.3 and §4.4 are unchanged; §4.4 gains a paragraph saying so. The reference is the `audience_decision` module of wai-rs (feature `audience_decision`); the corpus is live-conformance/audience/ (13 aggregations, 67 decisions, 17 classes, 39 statements, 32 territories, 10 placements), and audience_decision_verify.py is its second implementation: 178 cases agree, 21 of 21 mutated copies fail and 34 of 34 weakened verifiers are caught. Twelve faults planted in the reference each fail its tests: the evidence's extra-member check and its finer-region check, rule 6 (a region not the blackouts'), the serving-member check and `may_sign` among its members, a placement refused beside an outside territory, a decision's and a statement's signature verified without the domain, over the text as written and under the other record's domain, an unresolved session read as outside, and a lapsed statement letting an earlier one stand. Co-Authored-By: Claude Opus 5.5 <[email protected]>
62 KiB
WAI Extension: Channel Lineup and Guide
Status: Draft. Registers the record
wai.video.lineup: a linear channel's signed lineup,WLU1, which describes each programme asset of one channel schedule (its title, description, categories, rating, artwork by content hash, where it may be shown, and its rights holder) and where each programme of the guide begins, and which binds that schedule by its root. It defines how a channel as operators exchange it (an XMLTV schedule, Media RSS catalogues, a JSON form of both, and the SCTE-35 date ranges of the channel's HLS Media Playlist) is imported into awai.video.channelreceipt and a lineup; and the channel's guide, an XMLTV document or a now/next document for a player's guide row, rendered from a receipt and its lineup, naming both, so that anyone holding them recomputes the guide and compares. Thewai.video.channelreceipt keeps its signed bytes and travels in revenue-share's JSON carriage of it. No new transport. The reference implementation is thechannel_lineupmodule ofwai-rs(featurechannel_lineup); the corpus islive-conformance/channels/, and its second implementation islive-conformance/channel_lineup_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.This revision adds the cue tags of a Media Playlist of the channel (§7.4): a packager that writes the channel's segments, MPEG-2 transport stream segments among them, marks each break of the receipt's schedule with
EXT-X-CUE-OUTand the break's duration,EXT-X-CUE-OUT-CONTon each later segment of the break andEXT-X-CUE-INon its return, and, for a break whose cue carries its SCTE-35 section, with theEXT-X-DATERANGEtags of RFC 8216 §4.3.2.7.1; a verifier given the receipt, its cues and the playlist recomputes the tags and compares (§8.4). An import, a lineup and a guide are unchanged.Texts read for this revision, each at its source:
- the XMLTV DTD,
xmltv.dtd: its date and time format, and the elementstv,channel,display-name,programme,title,desc,category,icon,episode-num,ratingandvalue, with their attributes and the order the DTD gives a programme's children;- Media RSS Specification 1.5.1, December 2009: §4.1 (
media:group), §4.2 (media:content), §5 (where its optional elements appear), §5.2 (media:rating), §5.3 (media:title), §5.4 (media:description), §5.6 (media:thumbnails), §5.7 (media:category) and §5.13 (media:restriction); and the RSS 2.0 elements it extends (rss,channel,item,title,description,category,guid);draft-pantos-hls-rfc8216bis-22: §4.2 (attribute lists, decimal-floating-point and hexadecimal-sequence values), §4.4.5.1 (EXT-X-DATERANGE) and §4.4.5.1.1 (SCTE-35 in a date range);- ANSI/SCTE 35-1 2023r2: §9.6 (Table 5,
splice_info_section()) and §9.7.3 (Table 10,splice_insert());- RFC 8216 (HTTP Live Streaming): §4.2 (attribute lists), §4.3.2.1 (
EXTINF), §4.3.2.6 (EXT-X-PROGRAM-DATE-TIME), §4.3.2.7 (EXT-X-DATERANGE), §4.3.2.7.1 (SCTE-35 in a date range), §4.3.3.1, §4.3.3.2 and §4.3.3.4 (EXT-X-TARGETDURATION,EXT-X-MEDIA-SEQUENCE,EXT-X-ENDLIST), §6.2.1 and §6.3.1 (the server's and the client's general rules), §7 (protocol versions) and §8.10 (its example of a date range carrying SCTE-35);- RFC 8259 (JSON), RFC 8785 (the JSON Canonicalization Scheme), and XML 1.0 for the subset of §3.3.
A section number of one of these texts is that revision's.
1. Scope
In scope: the bounds and value forms an import reads (§3); the import of a channel's schedule,
catalogue and ad breaks into a schedule, its assets and its cues (§4); the JSON carriage a
wai.video.channel receipt travels in, which is revenue-share's (§5); the lineup, its rules, its signature and how it binds its
schedule (§6); the guide, in XMLTV and as a now/next document (§7), and the cue tags of a Media
Playlist of the channel (§7.4); verification of a receipt, a
lineup and a guide together (§8), and of a playlist's cue tags (§8.4); the operations that do
each, with a worked example (§9).
Out of scope: a transport; how feeds travel between operators; playout, which turns a schedule
into segments; the ad decision of a break, which live-ads and wai.video.ssai
make; the revenue an asset's rights holder is owed; how a packager cuts and encodes segments,
which §7.4 takes as given; interstitial date ranges, which stitched-presentation
reads; SCTE 224 audience and blackout rules (a lineup states where an asset may be shown, and
says nothing about when; a decision under blackouts is audience-decision's);
and the elements of the XMLTV DTD and of Media RSS that §4 does not name, which an import does not
read.
Relationship to the core spec: wai.video.lineup is a record (SPEC §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.
A guide is not a record: it is a rendering of two records that anyone recomputes, so it carries
no signature of its own.
Relationship to wai.video.channel (SPEC §5): an import seals a channel receipt with
ChannelReceipt::seal, whose signed bytes are unchanged here, and carries it in the JSON form
revenue-share §4.1 defines for it; a lineup binds the receipt's
schedule by its schedule_root. A receipt's program-as-run (aired) is what a verification
sets beside a guide (§8.3).
Relationship to live-events: each ad break an import reads becomes a cue
payload of live-events §4.3, unchanged, which a room's principal signs as a cue event when the
channel is delivered as a room. Relationship to cue-injection: its injection
record (§7 there) binds a cue to the SCTE 104 message it was translated from; a cue an import
yields comes from a playlist's SCTE-35 date range, and an import makes no injection record.
2. Terms
- Channel: a linear channel, named by a channel id.
- Schedule: the entries of a
wai.video.channelreceipt, gapless and non-overlapping, each an asset id, a start, a duration and a kind: program, ad or filler. - Programme: a span of the schedule the guide lists as one item: an episode or a film, with the ad breaks inside it. A programme's asset is the asset id of its program entries.
- Asset: what an asset id names; the lineup describes each programme asset (§6.2).
- Catalogue: the assets a channel's content owners describe, in Media RSS or the JSON form.
- Break: a span of a programme that ads fill, from a date range or the JSON form (§4.5).
- Operator: the party that runs the channel and signs its receipt and its lineup.
- Rights holder: the party an asset's lineup entry names as holding its rights.
- Feed: a document an import reads: an XMLTV document, a Media RSS document, the JSON form, or a Media Playlist.
- Guide: an XMLTV document (§7.2) or a now/next document (§7.3) rendered from a receipt and a lineup.
An instant is a count of milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted. An
instant or a duration an import reads, a lineup states or a guide writes is at most 2^53 − 1,
the integers JSON carries exactly (RFC 8785 §3.2.2.3). A span from a to b holds the instants from a, included, to b,
excluded. Integers of the lineup are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes.
Ed25519 is RFC 8032's, verified strictly (SPEC §7 Signatures): a key that fails
step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature. Hexadecimal
written by this text is lowercase.
3. Reading
3.1 Bounds
An import refuses import_oversized a feed or an artwork file of more than 16 777 216 bytes, and
a feed that holds more than 10 000 programmes of the channel (XMLTV, the JSON form), more than
10 000 items (Media RSS, all its documents together), more than 10 000 assets (the JSON form) or
more than 10 000 breaks (the JSON form and the playlist together). The size of each feed is
checked before it is read, and each count as soon as its document has been read as XML, JSON or
lines, before any of its elements is read further. An XML document is read to a depth of 64
elements at most (§3.3). An import therefore reads a bounded amount for any input.
3.2 Text values
A text is a sequence of Unicode scalar values in UTF-8 with no character below U+0020 other
than U+0009 (tab) and U+000A (line feed). A short text is a text of 1 to 255 bytes; a
long text a text of 0 to 65 535 bytes. A value an import reads that must be one of these and
is not is refused import_field_invalid. Text is taken as the feed writes it; nothing is
normalised.
3.3 The XML subset
An XMLTV or Media RSS document is read over the XML subset of
stitched-presentation §3.5, with its local names and its text of an
element, and with one addition: before the root element, where that subset admits a comment, the
document MAY hold one document type declaration with no internal subset, which is skipped.
Such a declaration is <!DOCTYPE, white space, a name (an ASCII letter or _, then ASCII
letters, digits, _, -, . and :), and then optionally white space and either SYSTEM,
white space and a literal, or PUBLIC, white space, a literal, white space and a literal;
then optional white space and >. A literal is ", bytes other than ", and "; or ',
bytes other than ', and '. White space is one or more of space, tab, CR and LF. A
declaration of any other form (one with an internal subset among them), or a second one, is
outside the subset. A document outside the subset, or whose root element is not the one §4.2 or
§4.3 names, is refused import_xml_malformed.
An element's first x child is its first child element of local name x. Elements are
named by local name only, so a Media RSS element and an RSS 2.0 element of one local name
(media:title and title, for one) are both title children; §4.3 says which one an import
takes.
3.4 Times and durations
An XMLTV time is 14 ASCII digits YYYYMMDDhhmmss, optionally followed by one space and a
numeric zone: + or - and four digits HHMM with HH at most 23 and MM at most 59. With no
zone the time is UTC, as the DTD says. The instant is the UTC instant of the date and time, less
the zone's offset. A value is refused:
import_timezone_unknownwhen its first 14 bytes are digits and what follows them is a space and anything other than a numeric zone (a zone named by letters, for one);import_time_malformedotherwise when it is not of that form, when its year is below 1970, its month not 1 to 12, its day not a day of that month in the Gregorian calendar, its hour above 23, its minute above 59 or its second above 59, or when its instant is before 1970-01-01T00:00:00Z.
An HLS date-time is YYYY-MM-DDThh:mm:ss, optionally . and one or more digits, then Z or
a zone +hh:mm or -hh:mm with hh at most 23 and mm at most 59, the fields as above. Its
fraction is read to the millisecond, and every digit after the third MUST be 0. A value of
this form without a zone is refused import_timezone_unknown; any other value that is not of
this form, or breaks a rule above, is refused import_time_malformed.
A duration is a decimal-floating-point value (draft-pantos-hls-rfc8216bis-22 §4.2): one or
more digits, optionally . and one or more digits, a number of seconds. It is read to the
millisecond, every fractional digit after the third MUST be 0, and the count of milliseconds
MUST be at most 2^53 − 1; otherwise it is refused import_time_malformed.
4. Import
4.1 Inputs
An import is given:
- channel: the channel id, a short text;
- feeds, one of these sets, each optionally with one Media Playlist:
- an XMLTV document, and zero or more Media RSS documents;
- one or more Media RSS documents, with no XMLTV document;
- one JSON form (§4.4), and no XMLTV or Media RSS document;
- for each Media RSS document, optionally its owner, the rights holder of its items: a short text;
- owner: optionally, the rights holder of every asset that no other input gives one: a short text;
- id_system: the XMLTV
episode-numsystem that names a programme's asset (§4.2); when not given,wai-asset-id, the system a guide writes (§7.2); - xmltv_channel: the XMLTV channel id whose programmes are read; when not given,
channel; - start: an instant, which a schedule made of Media RSS items alone requires (§4.6);
- artwork: a set of files, each named by a file name (§4.6);
- window: optionally, the receipt's window, a span from
window_starttowindow_end(§4.9); - the operator's Ed25519 key and the receipt's
signer_id(a short text), and optionally the receipt hash of the window before this one, itsparent_receipt_hash.
An input set that is none of the sets above, a Media RSS owner given for some of the Media RSS
documents and not for the others, a value above that is not of its form, or a schedule of Media
RSS items alone with no start, is refused import_inputs_invalid.
4.2 An XMLTV schedule
The document's root element is tv. Its channel name is the text of the first
display-name child of the first channel child of tv whose id attribute is
xmltv_channel, when that text is not empty; it MUST then be a short text. The import's
programmes are the programme children of tv whose channel attribute is
xmltv_channel, in document order; when there are none, the import is refused import_empty.
Each one is read, in document order, by these steps, and refused at the first that fails:
- Its
startattribute is an XMLTV time (§3.4); an absentstartisimport_time_malformed. - It has a
stopattribute: otherwiseimport_no_stop. That attribute is an XMLTV time (§3.4), and its instant is after the start's: otherwiseimport_time_malformed. - Its asset. It has an
episode-numchild whosesystemattribute isid_system(an absentsystemisonscreen, the DTD's default), and the text of the first such child is a short text: otherwiseimport_asset_unknown. That text is the programme's asset id. - Its description, each value as §3.2 says (
import_field_invalid): its title, the text of its firsttitlechild, a long text of 1 byte or more; its description, the text of its firstdescchild, or empty when it has none, a long text; its categories, the text of eachcategorychild, in order, each a short text, at most 16 of them; its artwork, thesrcattribute of eachiconchild, in order, each a long text of 1 byte or more, at most 8 of them (aniconwith nosrcis refused); and its rating, when it has aratingchild: the first one'ssystemattribute, or empty when it has none, which when not empty is a short text; and the text of that child's firstvaluechild, a short text (aratingwith novalue, or an empty one, is refused).
Every instant an XMLTV document gives is a whole second.
4.3 A Media RSS catalogue
The document's root element is rss, with a channel child; the first channel child is
read. Its channel name is the text of that channel's first title child, when not empty; it
MUST then be a short text. Its items are that channel's item children, in document
order. Each one is read, in document order, by these steps, and refused at the first that fails:
- Its asset id is the text of its first
guidchild (RSS 2.0'sguid), a short text: otherwiseimport_asset_unknown. An asset id that an item read before it has, in this document or in an earlier one, is refusedimport_asset_duplicate. - Its content is its first
contentchild (media:content, §4.2 there), or, when it has none, the firstcontentchild of its firstgroupchild (media:group, §4.1 there). Its content URI is the content'surlattribute, a long text, empty when absent or when it has no content. Its duration is the content'sdurationattribute read as a duration (§3.4;import_time_malformed), which MUST be at least 1 millisecond (import_time_malformed); with no such attribute it is unknown. - Its description, each value as §3.2 says (
import_field_invalid): its title, the text of its firsttitlechild, a long text of 1 byte or more; its description, the text of its firstdescriptionchild, or empty, a long text; its categories, the text of eachcategorychild, each a short text, at most 16; its artwork, theurlattribute of eachthumbnailchild (media:thumbnail, §5.6 there), each a long text of 1 byte or more, at most 8 (athumbnailwith nourlis refused); its rating, when it has aratingchild (media:rating, §5.2 there): the first one'sschemeattribute, orurn:simplewhen absent (§5.2's default), which when not empty is a short text; and its text, a short text. - Where it may be shown, from its first
restrictionchild whosetypeattribute iscountry(media:restriction, §5.13 there): itsrelationshipattribute isallowordeny, and its text, split at white space (space, tab, CR, LF) into non-empty tokens, is either the one tokenallor the one tokennone, or one or more tokens of two ASCII letters each: otherwiseimport_field_invalid. Two letters name the ISO 3166-1 alpha-2 code they spell in upper case, and the codes are taken in increasing order, each once.allowwithall,denywithnone, or no such child: shown everywhere;allowwithnone, ordenywithall: shown nowhere;allowwith codes: shown only in those countries;denywith codes: shown everywhere but those.
The item's title is RSS 2.0's title or Media RSS's media:title, whichever the item writes
first, and so for description and category. Elements that a media:group or a
media:content holds, other than the content itself, are not read.
4.4 The JSON form
The JSON form carries a channel's schedule, its catalogue and its breaks in one document. It is one JSON text (RFC 8259) in UTF-8, nested at most 64 deep, whose objects each name a member once. Its value is an object with exactly these members:
| member | value |
|---|---|
kind |
the string wai-channel-feed |
channel_name |
a string |
programmes |
an array of objects, each with exactly start_ms and stop_ms (integers) and asset_id (a string) |
assets |
an array of objects, each with exactly the members of the next table |
breaks |
an array of objects, each with exactly id (a string), start_ms and duration_ms (integers) and scte35 (a string, or null) |
| asset member | value |
|---|---|
asset_id, title, description, content_uri |
strings |
categories |
an array of strings |
rating |
null, or an object with exactly system and value, strings |
artwork |
an array of objects, each with exactly uri and sha256, strings |
geo |
null (shown everywhere), or an object with exactly one member, allow (shown only in) or deny (shown everywhere but), an array of strings |
rights_holder |
a string, or null |
duration_ms |
an integer: the asset's duration, 0 when unknown |
An integer is written as decimal digits with no sign, fraction, exponent or leading zero, at most
2^53 − 1. A document that is not so is refused import_json_malformed. Then, in document order,
each value is read as §3.2 and this paragraph say (import_field_invalid unless said otherwise):
channel_name a short text; each programme's asset_id a short text, with stop_ms after
start_ms (otherwise import_time_malformed); each asset's asset_id a short text (one that an
asset before it has is import_asset_duplicate), its title a long text of 1 byte or more, its
description and content_uri long texts, its categories as in §4.2, its rating's system a
short text or empty and its value a short text, its artwork at most 8, each uri a long text
of 1 byte or more and each sha256 64 lowercase hexadecimal digits, its geo codes each two
upper-case ASCII letters in strictly increasing order (a deny with none is refused), and its
rights_holder a short text when not null; each break as §4.5 says of a break.
4.5 Breaks from a Media Playlist
A Media Playlist is read as stitched-presentation §3.2 reads one: UTF-8, split at each line feed
with a CR before it dropped, its first line #EXTM3U (otherwise import_playlist_malformed).
Each line that begins #EXT-X-DATERANGE: is a date range tag, and the rest of the line its
attribute list, read by the rules of stitched-presentation §3.2; a tag whose list does not read,
or whose ID is not a quoted string, is refused import_playlist_malformed.
Tags are grouped by ID, each group in the order its first tag appears. Two tags of one group
that carry one attribute MUST carry the same value, a quoted string or not as both write it
(draft-pantos-hls-rfc8216bis-22 §4.4.5.1): otherwise import_playlist_malformed. A group is a
break when one of its tags carries SCTE35-OUT; the other groups are not read. A break's
attributes are those of its tags together, and it is read by these steps, each refused at the
first that fails:
- Its id is its
ID, a short text (import_field_invalid). - Its
START-DATEis a quoted string, an HLS date-time (§3.4): an absent one, or one that is not quoted, isimport_playlist_malformed. Its instant is the break's start. - Its
SCTE35-OUT(and itsSCTE35-IN, when it has one) is an unquoted hexadecimal-sequence (§4.2 there:0xor0X, then hexadecimal digits, read here in either case) of an even count of digits, whose bytes are asplice_info_section()as live-events §4.3 reads form 1:table_id0xFC, andsection_length(the low 12 bits of its bytes 1 and 2) plus 3 its length. Otherwiseimport_scte35_malformed. The bytes ofSCTE35-OUTare the break's section. - Its duration is its
DURATION, unquoted, when it has one; otherwise itsEND-DATE, a quoted HLS date-time, less its start, when it has one; otherwise itsPLANNED-DURATION, unquoted. A duration attribute that is quoted isimport_playlist_malformed, and one that does not read is refused as §3.4 says. A break with none of the three, or whose duration is 0 or above 2^32 − 1 milliseconds, or anEND-DATEbefore its start, is refusedimport_break_duration.
A break of the JSON form is its id (step 1); its start_ms; its scte35, when not null,
an even count of hexadecimal digits (either case, no 0x) read as step 3 reads a section
(import_scte35_malformed); and its duration_ms, refused as step 4 refuses one when 0 or above
2^32 − 1. A break of the JSON form with a null scte35 has no section.
4.6 The programmes and their assets
The programmes are, by the feeds:
- XMLTV: those of §4.2;
- the JSON form: its
programmes, each the span fromstart_mstostop_ms, with itsasset_id; none isimport_empty; - Media RSS alone: the items of the documents in the order given, each document's in
document order, one programme an item, back to back from
start: each programme begins where the one before it ends and lasts its item's duration. An item whose duration is unknown is refusedimport_duration_unknown, and an instant above 2^53 − 1import_time_malformed. No item isimport_empty.
The programmes are ordered by start (programmes of one start keep their order), and each next to
the one after it: a programme that ends before the next begins is refused import_gap, and one
that ends after the next begins import_overlap. Every programme's start, and the last one's
end, MUST be a whole second, a multiple of 1000 (import_time_malformed): a guide's XMLTV
times have no finer unit (§7.2).
Each distinct asset id of the programmes, in the order of the first programme that names it, gets its lineup entry (§6.2) by these steps, refused at the first that fails:
- Its description. With the JSON form, its
assetsentry, which MUST exist (import_asset_unknown). With XMLTV, the item of that asset id when a Media RSS document has one, otherwise the first programme that names it. With Media RSS alone, its item. The description is title, description, categories, artwork, rating, where it may be shown, content URI and duration; a programme gives no content URI, a duration of 0 and shown everywhere. - Its rights holder: the JSON form's
rights_holderwhen notnull; otherwise the owner of the Media RSS document its item came from, when given; otherwise the import's owner, when given. None is refusedimport_owner_unknown. - Its artwork. Each artwork URI of a description that came from XMLTV or Media RSS is bound
by its content hash: its name is the part of the URI after its last
/, once the first?or#and everything after it are removed. The name MUST be 1 to 255 bytes of ASCII letters, digits,.,_and-, other than.and.., and the artwork set MUST hold a file of that name; otherwiseimport_artwork_missing. A file of more than 16 777 216 bytes isimport_oversized. The artwork'ssha256is the SHA-256 of that file's bytes. The JSON form gives its artwork'ssha256itself.
Programmes and assets are read once each: an asset's later programmes give it nothing, and catalogue items no programme names are read (§4.3) but enter no lineup.
4.7 The schedule
The breaks are those of the JSON form, then those of the playlist, ordered by start (breaks of
one start keep their order). A break whose end (its start and its duration) is after the next
break's start is refused import_overlap; a break that does not lie within one programme, a
programme whose start is at most its start and whose end is at least its end, is refused
import_break_outside.
The schedule is, for each programme in order: a program entry from the programme's start, or from the end of its previous break, to the start of its next break or to its end, of the programme's asset id, wherever that span is not empty; and an ad entry for each of its breaks, from its start to its start plus its duration, whose asset id is the break's id. The schedule is therefore gapless and non-overlapping, and every entry of a programme lies within it.
4.8 The cues
Each break, in the order of §4.7, yields one cue payload of live-events §4.3, unchanged:
type 4 (break: out and back, its duration known), duration_ms the break's duration, and
form 1 with the break's section verbatim, or form 0 for a break with no section. Its
cue_id is the section's splice_event_id when the section's encrypted_packet bit (the high
bit of byte 4) is 0, its splice_command_type (byte 13) is 0x05 (splice_insert(), SCTE 35-1
§9.7.3) and it is 18 bytes or longer: the big-endian integer of bytes 14 to 17 (SCTE 35-1 §9.6,
Table 5, and §9.7.3, Table 10). Otherwise the cue_id is the big-endian integer of the first 4
bytes of SHA-256 over wai:channel-cue-id (ASCII), 0x01 and the break's id. A cue says, as its
message does, that the break is out of the program and back.
4.9 What an import yields
- The receipt:
ChannelReceipt::seal(SPEC §5,wai.video.channel) by the operator's key, with the import'ssigner_id,channel_idthe channel, the schedule of §4.7, the window (when not given, from the schedule's first start to its last end),joules_micro0 and no class (the import meters nothing; energy-measurement §2), the grant reference with no ceilings (grant_hash32 zero bytes,capabilityvideo.channel,joule_ceiling_micro2^64 − 1, nofunds_ceiling), andparent_receipt_hashas given. A window that is empty, or that does not lie within the schedule (its start before the schedule's first start, or its end after its last end), is refusedimport_window_outside. - The lineup (§6): the channel id, the channel name (the first of these that gives one: the
XMLTV document, the first Media RSS document, the JSON form; when none does, the channel id), the receipt's
schedule_root, the schedule's first start and last end, each programme's start, and the assets of §4.6 in the order of their asset ids' bytes, signed by the operator's key. - The cues of §4.8, each with its break's id and start.
4.10 The order of refusal
An import is refused for the first step that fails, in this order: §4.1; then each feed, the XMLTV document, the Media RSS documents in the order given, the JSON form and then the playlist, each first by its size (§3.1), then as XML, JSON or lines, then by its counts (§3.1), then by its own steps (§4.2 to §4.5), element by element in document order; then §4.6, §4.7 and §4.9. An import that is refused yields nothing.
5. The channel receipt's JSON carriage
A wai.video.channel receipt keeps its signed bytes, and this text defines no form of its own for
it: an import writes a receipt, and a verifier reads one, in the JSON carriage of
revenue-share §4.1, the one JSON form of a channel receipt. A file that holds
a receipt holds one JSON text (RFC 8259) in UTF-8 of that carriage. A receipt reads when it
is such a text and reads as revenue-share §4.1 says; its entries' leaves, its schedule root, its
signing payload, its signature and its identity (its receipt hash) are revenue-share §4.2's;
its rules are rules 1 to 5 of revenue-share §4.3, and its signature that section's rule 6. A
receipt verifies when its signature verifies and its rules hold.
6. The lineup
6.1 Layout
| size | field | meaning |
|---|---|---|
| 4 | magic |
WLU1 (ASCII) |
| 1 | version |
1 in this revision |
| short | channel_id |
the channel: a short text |
| short | channel_name |
its name, as a guide shows it: a short text |
| 32 | schedule_root |
the root of the schedule it describes (§5) |
| 8 | schedule_start_ms |
u64: the schedule's first start |
| 8 | schedule_end_ms |
u64: the schedule's last end |
| 4 | p |
u32: the programmes' count, 1 to 10 000 |
8 × p |
programmes |
each programme's start (u64), in order |
| 4 | a |
u32: the assets' count, 1 to 10 000 |
| … | assets |
a assets, §6.2, in the order of their asset ids' bytes |
| 32 | signer |
the operator's Ed25519 key |
| 64 | sig |
§6.3 |
A short field is a u8 length and that many bytes; a long field a u16 length and that
many bytes. Programme k runs from its start to the start of programme k + 1, the last one to
schedule_end_ms.
6.2 An asset
| size | field | meaning |
|---|---|---|
| short | asset_id |
the schedule's asset id: a short text |
| long | title |
a long text of 1 byte or more |
| long | description |
a long text |
| 1 | c |
the categories' count, 0 to 16 |
short × c |
categories |
each a short text |
| short | rating_system |
the rating's system, or no bytes |
| short | rating_value |
the rating, or no bytes for none |
| 1 | k |
the artworks' count, 0 to 8 |
| … | artwork |
k artworks, each 32 bytes (the SHA-256 of the image's bytes) and a long field (its URI, a long text of 1 byte or more) |
| 1 | geo |
where it may be shown: 0 everywhere; 1 only in the countries listed; 2 everywhere but them |
| 1 | g |
the countries' count |
2 × g |
countries |
each an ISO 3166-1 alpha-2 code, two of A–Z, in strictly increasing order |
| short | rights_holder |
the party that holds its rights, as the operator names it: a short text |
| long | content_uri |
the asset's media, or no bytes |
| 8 | duration_ms |
u64: the asset's duration, 0 when unknown |
A rights holder is a name, not a key: a contract between the operator and the rights holder (a share contract of the revenue it earns, for one) names its parties itself, and a lineup states what the operator published.
6.3 Digest and signature
The digest of a lineup is SHA-256 over its bytes from magic through signer. The
signature is Ed25519 by signer over wai:channel-lineup (ASCII) and 0x01, followed by the
bytes the digest covers.
6.4 Reading and rules
A reader reads exactly the bytes it is given as one lineup, in the order of §6.1, and refuses it
lineup_malformed when the bytes end before a field the layout requires or go on after sig,
or when:
- its magic is not
WLU1or its version not 1; channel_idorchannel_nameis not a short text;schedule_start_msis not belowschedule_end_ms, either is not a multiple of 1000, orschedule_end_msis above 2^53 − 1;pis 0 or above 10 000; or the programmes' starts are not strictly increasing, the first is notschedule_start_ms, the last is not belowschedule_end_ms, or one is not a multiple of 1000;ais 0 or above 10 000, or the asset ids are not in strictly increasing order of their bytes;- an asset breaks §6.2: an
asset_id, a category orrights_holderthat is not a short text; atitleempty or not a text; adescription, an artwork's URI orcontent_urinot a text; an empty artwork URI;cabove 16 orkabove 8; arating_systemthat is not empty or a short text, or one that is not empty beside an emptyrating_value; arating_valuethat is not empty or a short text;geoabove 2,geo0 withgabove 0,geo2 withg0, or countries not two ofA–Zeach in strictly increasing order; orduration_msabove 2^53 − 1.
6.5 A lineup and its schedule
A lineup describes a receipt's schedule when:
- its
channel_idis the receipt's; - its
schedule_rootis the receipt's, and itsschedule_start_msandschedule_end_msare the start of the receipt's first entry and the end of its last; - each programme's start is the start of an entry;
- each programme holds at least one program entry, and every program entry it holds (every entry that starts in its span) has one asset id, the programme's asset;
- the asset ids of its assets are exactly the programmes' assets.
A programme's ad and filler entries are its breaks; a guide lists the programme over them.
7. The guide
7.1 The programmes of a range
A guide is rendered from a receipt and a lineup that describes its schedule. The programmes of a
range from from to to, with from below to, are the lineup's programmes whose span meets it (a
programme that begins before to and ends after from), in order, each with its start, its
end, its asset's id and its asset's entry (§6.2).
7.2 The XMLTV guide
The XMLTV guide of a range is these bytes, in UTF-8, each line ending with a line feed:
<?xml version="1.0" encoding="UTF-8"?>
<tv source-data-url="urn:wai:guide:ROOT:LINEUP:FROM:TO">
<channel id="CHANNEL">
<display-name>NAME</display-name>
</channel>
<programme start="START" stop="STOP" channel="CHANNEL">
<title>TITLE</title>
<desc>DESCRIPTION</desc>
<category>CATEGORY</category>
<icon src="URI"/>
<episode-num system="wai-asset-id">ASSET</episode-num>
<rating system="SYSTEM">
<value>VALUE</value>
</rating>
</programme>
</tv>
ROOTis the receipt'sschedule_rootandLINEUPthe lineup's digest, each 64 lowercase hexadecimal digits, andFROMandTOthe range's ends in decimal, with no leading zero.urn:wai:guide:names a guide's sources; a guide names them in itssource-data-url, an attribute the DTD givestvfor where the data came from.CHANNELis the lineup'schannel_idandNAMEitschannel_name.- There is one
programmeelement for each programme of the range, in order.STARTandSTOPare its start and its end as XMLTV times (§3.4) in UTC with the zone written:YYYYMMDDhhmmss +0000. - Of a programme's children, in the order the DTD gives them: one
title; onedesconly when the description is not empty; onecategoryfor each category, in order; oneiconfor each artwork, in order, itssrcthe URI; oneepisode-num, whose text is the asset id; and oneratingonly whenrating_valueis not empty, with asystemattribute only whenrating_systemis not empty. - In text,
&,<and>are written&,<and>; in an attribute value, those and", tab and line feed are written&,<,>,",	and . Every other character is written as itself.
The guide's episode-num system is wai-asset-id, the import's default id_system (§4.1), so
a guide imports back into the programmes it lists.
7.3 The now/next document
The now/next document of an instant at is the JCS serialisation (RFC 8785) of this object,
followed by one line feed:
| member | value |
|---|---|
kind |
wai-now-next |
channel, channel_name |
the lineup's channel_id and channel_name |
schedule_root |
the receipt's, 64 lowercase hexadecimal digits |
lineup |
the lineup's digest, 64 lowercase hexadecimal digits |
at_ms |
at |
now |
the programme whose span holds at, or null |
next |
when now is a programme, the one after it, or null for the last; when at is before the schedule's first start, the first programme; otherwise null |
A programme is an object with the members start_ms, stop_ms and asset_id; title,
description and content_uri; categories, an array of strings; rating, null or
{"system", "value"}; artwork, an array of {"sha256", "uri"}; geo, null for everywhere,
{"allow": [...]} or {"deny": [...]}; rights_holder; and duration_ms.
7.4 The Media Playlist's cue tags
A receipt also gives the ad markers of a Media Playlist of the channel (RFC 8216), so that a packager that writes the channel's segments, MPEG-2 transport stream segments among them, marks each break where the signed schedule places it, and anyone holding the receipt and its cues recomputes the markers and compares (§8.4). Each break is marked in two forms:
- the tags
EXT-X-CUE-OUT,EXT-X-CUE-OUT-CONTandEXT-X-CUE-IN, in a form in common use, which no published standard defines and which this section fixes; and - for a break whose cue carries its section, the
EXT-X-DATERANGEtags of RFC 8216 §4.3.2.7.1, which carry the SCTE-35 section (ANSI/SCTE 35-1 2023r2 §9.6) inSCTE35-OUT.
A client ignores a tag it does not recognise (RFC 8216 §6.3.1), so a client that reads one form plays a playlist that carries both.
The breaks are the receipt's ad entries of a duration above 0, in schedule order. A
break's id is its entry's asset id, its start the entry's start, its duration the
entry's duration, and its end its start plus its duration. Each break has its cue, the one an
import wrote for it (§4.8, the cues.json of §9); its section is the cue's message when the
cue is of form 1, and it has none when the cue is of form 0. The receipt signs each break's id,
start and duration, which are all the cue tags are written from. It does not sign the section a
date range carries: that is the cue's, which §8.4 holds to its break's id and duration and no
further.
The window is what a Media Playlist lists: its media sequence number (RFC 8216 §4.3.3.2); the
instant its first segment begins; its segments in order, each a URI and a duration, each beginning
where the one before it ends; and whether it is ended (EXT-X-ENDLIST, RFC 8216 §4.3.3.4). A
segment's URI is 1 to 2 048 bytes of ASCII from ! to ~, the first of which is not #, and its
duration 1 to 2^32 − 1 milliseconds; the media sequence number is at most 2^53 − 1, and the last
segment ends before 253 402 300 800 000, the first instant of the year 10000. A break in the window
starts at the latest where the window ends, so every date below has four digits of year. A window
that is not so is refused playlist_malformed.
A segment starts in a break when it starts at or after the break's start and before its end. A break's last segment before it is the segment that ends at its start, and its return the segment that starts at its end. A break is in the window when the window holds its last segment before it, a segment that starts in it, or its return. The tags are refused:
cue_unaligned, at the first segment of the window, in order, inside which a break's start or end lies (after the segment's start and before its end): a marker applies from a segment's start, and the splice the schedule places would fall inside the segment;- then
cue_unwritable, at the first break, among the breaks, that is in the window, has a section and has an id holding a double quote, a line feed or a carriage return, which no quoted-string can hold (RFC 8216 §4.2).
Otherwise the playlist of the window is these lines, each ended by a line feed:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:TARGET
#EXT-X-MEDIA-SEQUENCE:SEQUENCE
#EXT-X-PROGRAM-DATE-TIME:FIRST
then, for each segment of the window in order, its cue lines (below), #EXTINF:DURATION, and its
URI; and then #EXT-X-ENDLIST when the window is ended.
TARGETis the longest segment's duration in seconds, rounded up, so that eachEXTINFrounded to the nearest integer is at most it (RFC 8216 §4.3.3.1);SEQUENCEthe media sequence number in decimal, with no leading zero; andFIRSTthe first segment's start as a date.- A date is an instant written
YYYY-MM-DDTHH:MM:SS.mmmZ, in UTC, always three digits after the point (RFC 8216 §4.3.2.6); a span is a count of milliseconds written as seconds with three digits after the point (90.000). A segment'sDURATIONis its duration as a span: a decimal duration, which needsEXT-X-VERSION3 (RFC 8216 §4.3.2.1, §7).
A segment's cue lines are, in this order:
-
The date range tags written at it, break by break in schedule order, of each break that has a section: its out tag at the first segment of the window that puts it in the window, and its in tag at its return, when the window holds it:
#EXT-X-DATERANGE:ID="ID",START-DATE="START",PLANNED-DURATION=SPAN,SCTE35-OUT=0xSECTION #EXT-X-DATERANGE:ID="ID",START-DATE="START",DURATION=SPANIDis the break's id,@and its start in decimal;STARTits start as a date;SPANits duration as a span; andSECTIONits section in uppercase hexadecimal, a hexadecimal-sequence (RFC 8216 §4.2). -
#EXT-X-CUE-IN, when a break ends at the segment's start. -
#EXT-X-CUE-OUT:SPAN,SPANthe break's duration, when a break starts at the segment's start; otherwise#EXT-X-CUE-OUT-CONT:ElapsedTime=ELAPSED,Duration=SPAN, when the segment starts in a break,ELAPSEDthe time from the break's start to the segment's as a span.
So:
- A date range announces its splice and is kept while it maps a segment. RFC 8216 §4.3.2.7.1
places an out section in
SCTE35-OUT, with aSTART-DATEthat is the splice's and aPLANNED-DURATION, and has a tag announcing a splice added with the last segment before it; once the end is established, another tag of the sameIDsignals it. RFC 8216 §6.2.1 keeps a date range while any of its dates maps to a segment the playlist holds. Two tags of oneIDagree on every attribute both carry (RFC 8216 §4.3.2.7), hereIDandSTART-DATE, which that section requires of every date range. The in tag carries noSCTE35-IN: an import keeps the out section alone (§4.5 step 3). - An
IDnames one break. A channel's schedule may repeat a break's id on another day; its start makes theIDunique in any playlist, as RFC 8216 §4.3.2.7.1 requires of an id that may recur. - The cue form is a function of each segment. A segment's
EXT-X-CUElines depend on its own start and the schedule alone, so it carries the same ones in every window that holds it, and a player that joins inside a break reads, on the first segment it is given, how far into the break it is and how long the break is. The date range tags depend on the window, as RFC 8216 places them. - A break with no section has no date range: RFC 8216 §4.3.2.7.1 maps a section, and there is none to map. Its cue form is written all the same.
- Every playlist states its program date-time (
FIRST), which RFC 8216 §4.3.2.7 requires of a playlist that carries a date range, on the receipt's UTC timeline. - Nothing is spliced. The segments are the channel's own, the program and the ad entries
alike; the tags say where a downstream inserter replaces the ad entries' segments. No
EXT-X-DISCONTINUITYis written, since the window's segments run on one timeline.
For brk-1 of the corpus's sample (§11), 90 seconds from 18:12:00 UTC, in ten-second segments, the last segment before it, its first and its return carry:
#EXT-X-DATERANGE:ID="brk-1@1793297520000",START-DATE="2026-10-29T18:12:00.000Z",PLANNED-DURATION=90.000,SCTE35-OUT=0xFC30…
#EXTINF:10.000,
seg-71.ts
#EXT-X-CUE-OUT:90.000
#EXTINF:10.000,
seg-72.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=10.000,Duration=90.000
#EXTINF:10.000,
seg-73.ts
…
#EXT-X-DATERANGE:ID="brk-1@1793297520000",START-DATE="2026-10-29T18:12:00.000Z",DURATION=90.000
#EXT-X-CUE-IN
#EXTINF:10.000,
seg-81.ts
8. Verification
8.1 Inputs
A verifier is given a receipt in its JSON carriage (§5), a lineup's bytes, and optionally a
guide's bytes: an XMLTV guide when its first byte is <, a now/next document when it is {.
8.2 Steps
A verification gets one verdict, verified or the reason of the first step it fails:
- The receipt reads (§5):
channel_receipt_malformed. - Its signature verifies under
signer_pubkey(§5):bad_key,bad_signature. - Its rules hold (§5):
channel_receipt_invalid. - The lineup reads (§6.4):
lineup_malformed. - Its signature verifies under
signer(§6.3):bad_key,bad_signature. - Its
signeris the receipt'ssigner_pubkey: one operator signs both. Otherwiselineup_signer_mismatch. - It describes the receipt's schedule (§6.5):
lineup_schedule_mismatch. - The guide, when one is given, reads:
guide_malformed. An XMLTV guide reads when it is in the subset of §3.3 with the roottv; itssource-data-urlisurn:wai:guide:, two runs of 64 lowercase hexadecimal digits and two decimal integers with no leading zero, the first below the second, separated by:; and each of itsprogrammechildren hasstartandstopattributes that are XMLTV times (§3.4) and anepisode-numchild of systemwai-asset-id, the text of the first of which is its asset id. A now/next document reads when it is one JSON object whose members are those of §7.3, withat_msan integer of at most 2^53 − 1,schedule_rootandlineupstrings, andnowandnexteachnullor an object whosestart_msandstop_msare integers and whoseasset_idis a string. - It names the receipt's
schedule_rootand the lineup's digest:guide_source_mismatch. - Each of its programmes is the lineup's: for an XMLTV guide, each programme has the start, the
end and the asset of one of the programmes of the guide's range (§7.1); for a now/next
document,
nowandnexthave those of the programmes §7.3 names for itsat_ms, and arenullwhere those are. Otherwiseguide_entry_unscheduled, at the first programme, in the guide's order, that is not (its index among the guide'sprogrammeelements; for a now/next document,now0 andnext1). - Its bytes are the guide of §7.2 for its range, or the document of §7.3 for its
at_ms:guide_mismatch.
Step 10 catches a guide that places a programme where the schedule does not; step 11 every other difference: a title, a rating or a picture the lineup does not state, a programme left out.
8.3 What a verification states
- its verdict, and the part it is about:
receiptfor steps 1 to 3,lineupfor steps 4 to 7,guidefor steps 8 to 11, none when verified; - the receipt's identity, when it reads; and the lineup's digest, when it reads;
- for a guide that reads: its format (
xmltvornow-next), its range or itsat_ms, and its programmes, each with its start, its end, its asset id and whether it aired: whether an entry the receipt'sairedlists meets its span. A guide shows what the schedule placed; the receipt states which of it was on air in its window; - for
guide_entry_unscheduled, the index of step 10.
8.4 A playlist's cue tags
A verifier given a receipt in its JSON carriage (§5), a cues document and a Media Playlist's bytes gets one verdict, verified or the reason of the first step it fails:
- The receipt reads, as §8.2 step 1:
channel_receipt_malformed. - Its signature verifies, as §8.2 step 2:
bad_key,bad_signature. - Its rules hold, as §8.2 step 3:
channel_receipt_invalid. - The cues read: one JSON text (RFC 8259) in UTF-8, nested at most 64 deep, whose objects each
name a member once, an array of objects of exactly
break(a string),start_ms(an integer of at most 2^53 − 1, written as §4.4 writes one) andpayload(an even count of lowercase hexadecimal digits), aschannel.importwritescues.json(§9). Otherwisecues_malformed. - They are the breaks' cues (§7.4): there are as many as there are breaks, and the cue at each index
is its break's: its
breakthe break's id and itsstart_msthe break's start, and its payload the one §4.8 yields for a break of that id and duration and of the section the payload carries, when it carries a message of form 1 that reads as §4.5 step 3 reads a section, or of none, when it is of form 0. Otherwisecues_mismatch, at the first index that is not, a cue missing or added at the first index past the shorter list. A break whose duration is above 2^32 − 1 milliseconds has no cue of §4.8, and its index fails. - The playlist reads: UTF-8 text whose lines, split at each line feed, the last ended by one,
begin with
#EXTM3U; with exactly one line#EXT-X-MEDIA-SEQUENCE:and a decimal integer with no leading zero, and a line#EXT-X-PROGRAM-DATE-TIME:and an HLS date-time (§3.4) before its first segment; whose segments are each a line#EXTINF:, a duration (§3.4) and,, followed by a line, the segment's URI, at least one of them. It is ended when one of its lines is#EXT-X-ENDLIST. Its window is that media sequence number, the first such date-time's instant as its first segment's start, its segments in order and whether it is ended, which MUST hold to §7.4's bounds. Otherwiseplaylist_malformed. No other line is read here: step 8 compares every line. - Its window's tags can be written (§7.4):
cue_unaligned, at the segment's index;cue_unwritable, at the break's index among the breaks. - Its bytes are the playlist of §7.4 of its window:
playlist_mismatch.
Step 8 catches a marker the schedule does not place, at a segment or in a date range: a break another duration, another start or another section than its cue's, a tag left out, added or moved.
A verification states its verdict and the part it is about (receipt for steps 1 to 3, cues
for steps 4 and 5, playlist for steps 6 to 8, none when verified); the receipt's identity, when it
reads; for a playlist that reads, its window (its media sequence number, its first segment's start,
how many segments it holds and whether it is ended) and the breaks in it, each with its id, its
start, its duration and whether it has a date range; and the index of step 5 or step 7.
9. Operations and a worked example
Three operations of the WAI command line carry this text (the operation catalogue,
wai/catalogue/ops.json), each with the toolchain's agent rules: --json for the envelope,
--dry-run on an operation that writes, and --yes before a file is replaced.
channel.import(wai channel import): feeds in; the receipt (channel.json, its JSON carriage), the lineup (lineup.wlu) and the cues (cues.json) out, into a directory. A refusal's code is the error's code, and its exit status 1.channel.guide(wai channel guide): a receipt and a lineup in; an XMLTV guide of a range (the whole schedule unless--fromand--tosay otherwise), or with--now-next --ata now/next document, out.channel.verify(wai channel verify): a receipt, a lineup and optionally a guide in; what §8.3 states out, exit status 0 when verified and 1 otherwise.
The corpus's sample channel (§11) is imported, guided and verified so:
wai channel import --channel sample-1 --xmltv sample.xmltv.xml \
--mrss sample-a.mrss.xml --mrss-owner owner-a --mrss sample-b.mrss.xml --mrss-owner owner-b \
--owner owner-c --playlist sample.m3u8 --artwork . --key operator.key --signer-id sample-operator \
--window-start 1793296800000 --window-end 1793300400000 --out-dir out
wai channel guide --receipt out/channel.json --lineup out/lineup.wlu --out guide.xml
wai channel guide --receipt out/channel.json --lineup out/lineup.wlu --now-next --at 1793299545000 --out now-next.json
wai channel verify --receipt out/channel.json --lineup out/lineup.wlu --guide guide.xml
The test tests/channel_lineup.rs runs these commands on the corpus's files and requires the
corpus's receipt, lineup, cues and guides from them, byte for byte.
10. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
import_inputs_invalid |
§4.1 |
import_oversized |
§3.1, §4.6 step 3 |
import_xml_malformed |
§3.3 |
import_json_malformed |
§4.4 |
import_playlist_malformed |
§4.5 |
import_time_malformed |
§3.4, §4.2, §4.3, §4.4, §4.6 |
import_timezone_unknown |
§3.4 |
import_no_stop |
§4.2 step 2 |
import_empty |
§4.2, §4.6 |
import_asset_unknown |
§4.2 step 3, §4.3 step 1, §4.6 step 1 |
import_asset_duplicate |
§4.3 step 1, §4.4 |
import_field_invalid |
§3.2, §4.2 to §4.5 |
import_scte35_malformed |
§4.5 step 3 |
import_break_duration |
§4.5 step 4 |
import_duration_unknown |
§4.6 |
import_gap |
§4.6 |
import_overlap |
§4.6, §4.7 |
import_owner_unknown |
§4.6 step 2 |
import_artwork_missing |
§4.6 step 3 |
import_break_outside |
§4.7 |
import_window_outside |
§4.9 |
channel_receipt_malformed |
§8.2 step 1 |
channel_receipt_invalid |
§8.2 step 3 |
lineup_malformed |
§6.4, §8.2 step 4 |
lineup_signer_mismatch |
§8.2 step 6 |
lineup_schedule_mismatch |
§8.2 step 7 |
guide_malformed |
§8.2 step 8 |
guide_source_mismatch |
§8.2 step 9 |
guide_entry_unscheduled |
§8.2 step 10 |
guide_mismatch |
§8.2 step 11 |
cues_malformed |
§8.4 step 4 |
cues_mismatch |
§8.4 step 5 |
playlist_malformed |
§7.4, §8.4 step 6 |
cue_unaligned |
§7.4, §8.4 step 7 |
cue_unwritable |
§7.4, §8.4 step 7 |
playlist_mismatch |
§8.4 step 8 |
A verification also gives live-events' bad_key and bad_signature (§8.2 steps 2 and 5, §8.4
step 2).
11. Conformance
live-conformance/channels/ holds the corpus, written from fixed
seeds by wai_channel_lineup_vectors gen: vectors.json, which holds the operator's key and
each vector's inputs and outcome; the feeds of a sample channel (an XMLTV document with a second
channel's programmes beside it, two Media RSS documents of two content owners, a Media Playlist
with SCTE-35 date ranges, and the JSON form of the same channel); the artwork files; the receipt
and the guides the sample yields. Every document is a fixture written for its case, of
programmes, owners and pictures made for it. The vectors:
- imports: the sample from XMLTV, Media RSS and the playlist; the same channel from the JSON form, which yields the same receipt and lineup byte for byte; a channel made of one owner's Media RSS items alone, back to back; and a control of each refusal of §4, each the sample with one change (an edit the vector states, which a verifier applies to its copy);
- verifications: the sample's receipt and lineup alone, with its XMLTV guide, with a guide of part of the schedule, and with now/next documents before, inside and after the schedule; and a control of each step of §8.2 (a receipt that does not read, a flipped signature, a key of small order, a program-as-run the window does not give, a lineup that does not read, one another key signed, one for each rule of §6.5 that it alone breaks, a guide that does not read, one naming another schedule, one that places a programme the schedule does not, and one whose title the lineup does not state);
- playlists: windows of the sample's Media Playlist and the playlist each renders (§7.4): brk-1
from before its last segment before it to after its return; a live window that begins inside
brk-2, whose section is a
time_signal(); brk-3, which begins half a second past a whole second, in six-second segments, the window ended; a window that begins at a return; one no break is in; and brk-1 from the JSON form with no section, which yields the same receipt and other cues, and no date range. And a control of each refusal of §7.4: segments cut off a break's start, a segment across a return, a break named with a double quote, and a segment of no duration; - playlist windows at their bounds: brk-1 in segments of 6.006 seconds, whose
EXTINFandElapsedTimestate milliseconds and whoseTARGETDURATIONis 7, 6.006 rounded up; a segment URI of 2 048 bytes; a segment of 2^32 − 1 milliseconds; a media sequence number of 2^53 − 1; and, of a channel scheduled at the end of the year 9999, a window that ends at 9999-12-31T23:59:59.999Z, where a break starts, its date range dated in the year 9999. And a control of each bound: brk-1 named with a line feed, and with a carriage return in a receipt the operator signed (cue_unwritable); that window with a segment of a millisecond after it, which ends at 10000-01-01T00:00:00Z; a window of no segment; a segment URI of no byte, of 2 049 bytes, holding a space, holding a letter outside ASCII and beginning with#; a segment of 2^32 milliseconds; and a media sequence number of 2^53 (playlist_malformed); - playlist verifications: the playlists of the first six windows, the one of 6.006-second
segments and the one of the year 9999, each verified against its receipt and its cues (§8.4); and a
control of each step: a receipt that does not read, a flipped signature, a
program-as-run its window does not give, cues that do not read, a cue a millisecond longer than its
break, one whose section names another
splice_event_id, one a second late, one left out, another receipt's, a playlist with no media sequence number, one that ends at 10000-01-01T00:00:00Z, one whose segment URI begins with#, one whose segments move off a break's start, a break named with a double quote, and playlists that state another duration, leave out anEXT-X-CUE-OUT-CONT, return a segment early, carry another section, start a date range a second late, and leave out a date range's end.
A conforming implementation gives every import its outcome (the receipt, the lineup and the cues,
or the refusal), every verification its verdict and facts, every playlist window its playlist or
its refusal, and every playlist verification its verdict and facts, as the corpus states.
wai_channel_lineup_vectors verify and the test tests/channel_lineup.rs check that every file
regenerates byte for byte, that wai-rs gives every vector its outcome, that §10 lists exactly
the codes the reference adds, and that the corpus gives every reason of §10 and both of
live-events'. live-conformance/channel_lineup_verify.py is the second implementation, written
from this text: it shares no code with wai-rs; it verifies Ed25519 with WAI's strict rule
(claim-conformance/_ed25519.py), BLAKE3 with claim-conformance/_blake3.py, and reads a channel
receipt's carriage, its signed bytes, its identity and its rules as the revenue-share corpus's
second implementation reads them (§5); it reads XML, JSON and playlists itself, and renders and
reads a Media Playlist's cue tags itself (§7.4, §8.4); and it mutates copies of the corpus and requires
every copy to fail, and drops each of its own checks in turn and requires the corpus to catch the
weakened verifier.
12. Security and privacy considerations
- What a lineup proves. That the operator stated this description of each programme asset of one schedule, and where its guide's programmes begin. It does not prove that a rights holder agreed: a rights holder's name in a lineup is the operator's statement, and a party paid by it checks a contract it signed, not the lineup.
- What a guide proves. Nothing by itself: it is recomputed from a receipt and a lineup and compared, so a guide that verifies says exactly what the two signed records say, and a guide a third party altered is refused. A guide is a schedule published ahead of air; which of it was on air in a window is the receipt's program-as-run (§8.3), and a guide covering a span the receipt's window does not is checked against the schedule alone.
- Artwork. A lineup binds each picture by its SHA-256, so a player that fetches the URI and checks the hash shows the picture the operator stated, wherever it was fetched from.
- Bounded reading. An import reads bounded documents (§3.1), XML to a bounded depth with no document type declaration that defines anything (§3.3), so no entity is ever expanded, and integers it reads are checked against their bounds before any arithmetic.
- What the cue tags prove. A playlist that verifies marks each break where the receipt's
signed schedule places it, with its signed duration, and every segment of it; one that moves a
break, shortens it or leaves a segment of it unmarked is refused. The section a date range carries
is the import's (§4.8): the receipt does not sign it, and a verifier holds it only to its break's
id and duration, and through the cue id to a
splice_insert()'ssplice_event_id. A party that splices on a section's own fields holds the playlist it was imported from, or the signedcueevents of a room that carried it (live-events §3.2, §4.3), which sign the section itself. - Availability. Where an asset may be shown is the operator's statement of the catalogue's
restriction; a player enforces it, or a delivery that a
wai.video.manifestdescribes does. A lineup holds no viewer's data.