Skip to main content

WAI Extension: Cue Injection

Mirrored from the canonical text at commit 260cbd09 ().

Status: Draft. Registers the record wai.live.injection: SCTE 104 automation, translated to SCTE 35 sections and signed as a room’s cue events. A broadcast automation system drives its injector over the API SCTE 104 defines; here the injector is a WAI ingest gateway. It answers each request as that standard says, builds the SCTE 35 section the request asks for, derives the cue the room’s ad breaks are made of (live-events §4.3, live-ads §4.3), and signs that cue, by its own principal, when the splice comes on the room clock. Before each cue its chain holds the cue’s injection record, WIJ1: the SCTE 104 message whole, when it arrived, and the parameters it was translated with, so that a verifier recomputes the cue, its section and its instant from the record alone. No new transport, and nothing in the cue’s layout changes: the record rides in the meta kind. The reference implementation is the scte module of joule-moq-ingest in joule-code/joule-code-rs/crates/; the corpus is live-conformance/injection/, and its second implementation is live-conformance/injection_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: ANSI/SCTE 104 2023 (its §7, §8, §9, §12, §14 and Appendix A to C); ANSI/SCTE 35-1 2023r2 (its §9.6 to §9.9, §10.2 and §10.3, and the sample messages of §14). SMPTE ST 2010, which carries SCTE 104 messages in a video signal’s vertical ancillary data, and SMPTE EG 40, which SCTE 104 names for frame-time arithmetic, were not available to this revision (§5.6, §10). A section number of one of these texts is that revision’s.

1. Scope

In scope: the injector’s API connection and its responses (§3); the messages it takes, their checks, and when their requests are processed (§4); the SCTE 35 section each request builds (§5); the cues a request yields, and what it withdraws (§6); the injection record, the order in which an injector signs, and the rules it holds its own chain to (§7); how a verifier checks an injector’s chain (§8); the reasons (§9); and the carriage of SCTE 104 in a video signal, as a mapping note (§10).

Out of scope: a transport (SCTE 104 runs over TCP, Appendix A there); the PAMS interface of SCTE 104 §10 and §11, which provisions injectors and is not an automation system’s; the splicing of SCTE 35 downstream; ad decisions, which live-ads §7 makes; and the SCTE 35-2 event-based signalling a cue’s form 2 carries, which no SCTE 104 request builds.

Relationship to the core spec: wai.live.injection is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable. Relationship to live-events: this extension changes nothing in it. A cue is a cue event as it is, of form 1 or 0 (§4.3 there); the injection record is the payload of a meta event (§4.2 there), bound to its cue by the chain, as live-ads §4.2 binds a break annotation and timed-text binds a caption’s annotation. Relationship to live-ads: an injector’s cues are breaks as any other principal’s are; this extension makes their timing and their SCTE 35 messages recomputable from what the automation system sent.

2. Terms

Integers of the SCTE 104 and SCTE 35 bit streams are big-endian, as those standards write them; integers of the injection record are little-endian, as live-events’ are. A digest is SHA-256 (FIPS 180-4).

3. The API connection

An injector listens for API connections on TCP, at port 5167 unless it is configured otherwise (SCTE 104 §9.1, Appendix A). Each message on a connection begins with its opID, or 0xFFFF, and its messageSize, the size of the whole message: a reader takes four bytes, then the rest of messageSize.

4. Requests

4.1 The message

A multiple_operation_message() (SCTE 104 Table 8-2) is read in its order, and refused at the first of these that holds:

  1. Reserved is not 0xFFFF: it is a single_operation_message() (§3).
  2. messageSize is not the message’s size, a field runs past the message, or bytes follow its last request: message_size (114).
  3. protocol_version or SCTE35_protocol_version is not 0: version_unsupported (127).
  4. timestamp()’s time_type is not 0 or 1: time_type_unsupported (123). A time_type 1 whose instant precedes the room’s t0 is timestamp_before_room (115).
  5. num_ops is 0: no_operation (115).
  6. The requests do not group (§4.3): supplemental_first (115), operation_unsupported (124) or opid_unknown (125).
  7. A request’s data does not read as §5 reads it, or holds a value §5 does not allow: the reason §5 names.
  8. The message yields more than 255 cues: too_many_cues (115).

A message refused yields nothing: no section, no cue, no withdrawal.

4.2 When its requests are processed

The processing instant P of a message, on the room clock, is:

A VITC timestamp (time_type 2) and a GPI (3) need a video signal’s time code and a contact closure, which a gateway that takes compressed contribution does not have; they are refused (§4.1 step 4).

The parameters of a translation are the room’s t0, leap, and the frame rate fps_num/fps_den that §5.6’s duration_extension_frames is counted in.

4.3 Groups

The requests of a message, in order, form groups (SCTE 104 §8.2.3.1, §8.3.1):

4.4 What this revision does not implement

inject_section_data_request() (0x0100) carries a command image whose pts_time the automation system computed on a clock of its own; the injector’s program clock is the room clock (§5.2), so the image’s times cannot be held to its cue. proprietary_command_request (0x010C) has no cue. Encryption (0x0107, 0x0300, 0x0301), component mode (0x0106, deprecated by SCTE 35-1), schedules (0x0103, 0x0105, 0x010D, 0x010E), audio descriptors and provisioning (0x0111, 0x0112) and the alternate break duration (0x0113) are not implemented. Each is refused operation_unsupported (124).

5. Sections

5.1 The section

Each group builds one splice_info_section() (SCTE 35-1 Table 5), but a splice_cancel, which builds none (§5.3):

fieldvalue
table_id0xFC
section_syntax_indicator, private_indicator0, 0
sap_type3: not specified (SCTE 104 states no stream access point)
section_lengththe bytes after it, at most 4 093; past that, section_too_long (115)
protocol_version0
encrypted_packet, encryption_algorithm, pts_adjustment0, 0, 0
cw_index0xFF: present and undefined in the clear (§9.6.1), as §14’s samples write it
tierthe group’s insert_tier_data(), or 0xFFF without one (SCTE 104 §8.2.3)
splice_command_lengththe command’s length
splice_command_type and the command§5.3 to §5.5
descriptor_loop_length, the descriptors§5.6, in the order of the group’s requests
alignment_stuffingnone
CRC_32the MPEG-2 CRC (ISO/IEC 13818-1 Annex A: polynomial 0x04C11DB7, register preset to all ones, no reflection, no final inversion) over every byte before it

Every reserved bit is 1 (SCTE 35-1’s definition of reserved). A field of 1 bit that SCTE 104 carries in a byte (a flag) is 0 or 1; another value is field_range (115).

5.2 The program clock

The program’s 90 kHz clock is the room clock: a splice instant S µs is pts_time ⌊S · 9 / 100⌋ modulo 2^33, with pts_adjustment 0. A device that restamps the program’s times adds its delta to pts_adjustment, as SCTE 35-1 §9.6.1 has it, and the cue’s t stays the instant on the room clock.

5.3 splice_request_data() to splice_insert()

splice_request_data() (SCTE 104 Table 9-5) is 14 bytes, or 15 with not_an_entry_flag; another length is message_size. Its splice_insert_type gives the command (Table 9-7 there), splice_event_cancel_indicator 0:

splice_insert_typeout_of_network_indicatorsplice_immediate_flagbreak_duration()splice instant S
1 spliceStart_normal11 where pre_roll_time is 0, else 0where break_duration is not 0P + pre_roll_time ms
2 spliceStart_immediate11where break_duration is not 0P
3 spliceEnd_normal01 where pre_roll_time is 0, else 0noneP + pre_roll_time ms
4 spliceEnd_immediate01noneP
5 splice_cancel————

5.4 time_signal_request_data() to time_signal()

Two bytes, pre-roll_time; another length is message_size. The command is time_signal() with splice_time() of S = P + pre-roll_time ms, time_specified_flag 1.

5.5 splice_null_request_data() to splice_null()

No data; data is message_size. The command is splice_null(), empty, and S is P.

5.6 Descriptors

Each Supplemental request adds what follows to its group’s section, in the order of the requests:

5.7 The standard’s samples

Built by §5, the request whose fields are those of SCTE 35-1 §14.1’s sample (a time_signal() with a Provider Placement Opportunity Start of 307 s) gives that sample’s section byte for byte, its CRC included, at the instant whose pts_time is the sample’s; the corpus holds it. §14.2’s splice_insert() sample is written byte for byte by the reference’s section writer from its fields, but no splice_request_data() states its break_duration, which is not a whole number of tenths of a second.

6. Cues

6.1 From a splice_insert()

6.2 From a time_signal()

Each segmentation request of the group that is not cancelled yields a cue at S, cue_id segmentation_event_id, form 1 with the section, by its segmentation_type_id (SCTE 35-1 Table 23):

segmentation_type_idcueduration_ms
0x22 Break Start; 0x30, 0x32 Provider and Distributor Advertisement Start; 0x34, 0x36 Provider and Distributor Placement Opportunity Startout (1)⌊segmentation_duration / 90⌋, 0 without one
0x23, 0x31, 0x33, 0x35, 0x37: their Endsin (2)0
0x20 Chapter Startchapter (3)as for an out
any othernone—

An overlay or promotional opportunity does not replace the program, and an ad block holds the placements that do, so neither is a break; a content, program or network boundary is not one either. The cues of one group are its ins, then its outs, then its chapters, each in the order of its requests: a section that ends one break and opens the next closes the first before it opens the second. A cancelled segmentation request withdraws (§6.3). A splice_null() yields no cue, nor do the segmentation descriptors of a splice_insert(), whose cue is §6.1’s.

6.3 Withdrawals

A splice_cancel withdraws the held cues (§7.3) of its splice_event_id that a splice request yielded; a cancelled segmentation request, those of its segmentation_event_id that a time signal yielded; a splice end, the held derived in of its splice_event_id.

6.4 The cue and its index

A cue is live-events §4.3’s payload: type, cue_id (u32), duration_ms (u32), form, and, for form 1, scte_len (u16) and the section. A message’s cues, over its groups in order and within each in §6.1’s and §6.2’s order, form one list; a cue’s index is its position in it, from 0.

7. The injection record and the injector’s chain

7.1 Layout

The injection record is the payload of a meta event (live-events §4.2):

offsetsizefieldmeaning
04magicWIJ1 (ASCII)
41version1 in this revision
58receivedu64: the room-clock instant, µs, the message’s last byte arrived (R, §4.2)
131leapthe leap seconds of the translation (§4.2)
144fps_numu32
184fps_denu32, neither 0
221indexthe index of the cue that follows (§6.4)
232message_lenu16
25message_lenmessagethe multiple_operation_message(), verbatim

The record holds nothing after message. The room’s t0, the last parameter, is the room descriptor’s.

7.2 Signing

An injector signs each cue when the room clock reaches its splice instant, never before: a withdrawal that comes in time then leaves nothing to undo in a signed chain. Among the cues due at once it signs in order of instant, then of the arrival of their messages, then of index. Each cue is two events of the injector’s chain, at consecutive seq: its injection record (a meta event), then the cue, both with t the cue’s splice instant. An injector takes a message’s arrival instant after every instant it has signed at, so the t of its chain never decreases (live-events §6.2).

A cue is signed as it was translated. The record and the cue together are at most the principal’s max_payload each (live-events §5); a message whose record or cue would pass it is refused injection_too_large (115).

7.3 The injector’s rules

An injector holds each cue a message yields until its splice instant, and keeps, for its own chain, the room’s breaks as live-ads §4.3 makes them: the open break, if any, and the end of the last closed one. When a message is taken:

  1. A message whose message_number is that of a message with cues still held is a repeat (SCTE 104 §9.3: “the Injector can detect that these are duplicates of one another by comparison of the message_number fields”); it is answered and changes nothing (repeated_message, counted).
  2. Its withdrawals (§6.3) remove held cues. A splice_cancel or a cancelled segmentation request that removes none came too late, its splice already signed or never made: it is answered 121 (cancel_too_late).
  3. Its cues are held. An injector holds at most its configured number of cues (256 by default); a message whose cues would pass it is answered 124 and changes nothing.

When a held cue’s splice instant comes, in the order of §7.2, it is signed unless live-ads §4.3 would refuse it in the injector’s chain, in which case it is dropped and counted: an out while a break is open, or before the end of the last closed one (break_overlap); an in with no open break of its cue_id (cue_in_unmatched), or less than a millisecond after its out (break_zero_duration). A chapter is always signed. A room with other cue authors orders its cues by the merged order (live-rooms §9.2), which an injector does not see; it holds only its own chain to these rules.

7.4 Carriage

The injector publishes its events on its principal’s events track (live-rooms §6.1; live-events §8.1), from its start, so a member can subscribe before the first cue. Each group’s first object is the join set of live-events §8.2: the out of the break open when the group began, if one is. A relay whose door checks receipts (jwp-receipts §4.1) names the room’s media kinds in its rules, since an events track carries none.

8. Verification

A verifier of an injector’s chain verifies each event as live-events §6 does (its signature, bad_signature; its link, chain_broken). Then, in seq order, it refuses at the first of these that holds:

  1. A meta event whose payload begins WIJ1 is an injection record: refused injection_malformed where it is shorter than 25 bytes, its version is not 1, a frame-rate field is 0, or message_len does not end it.
  2. Its message is translated (§4 to §6) with received, leap, fps_num, fps_den and the room’s t0: refused injection_untranslatable where the translation refuses it.
  3. The next event of the chain is a cue: otherwise the record is refused injection_unanswered.
  4. That cue’s payload is the payload of the translation’s cue index, and its t and the record’s t are that cue’s instant: otherwise the cue is refused injection_mismatch (an index past the translation’s cues among them).
  5. A cue of an injector’s chain that does not follow its injection record is refused injection_missing. A verifier is told which principals are injectors: the room’s configuration names them, and a cue of another principal is not held to this section.

A chain that verifies states, for each of its cues, the SCTE 104 message it came from and the instant that message arrived, and the cue is that message’s translation: a reader recomputes the section a splicer acted on, the instant it splices at, and the break live-ads §4.3 makes of it.

9. Reasons

Each refusal of a message with the SCTE 104 result code (Table 14-1 there) its response carries, and each refusal of §8.

reasonresultraised at
not_initialized124§3, a request before init_request
injector_in_use110§3, an init_request while another connection holds the injector
dpi_pid_unknown126§3
message_size114§4.1 step 2; §5.3 to §5.6
version_unsupported127§4.1 step 3
time_type_unsupported123§4.1 step 4
timestamp_before_room115§4.1 step 4
no_operation115§4.1 step 5
supplemental_first115§4.3
operation_unsupported124§4.3, §4.4
opid_unknown125§4.3; §3, a single_operation_message() of another opID
too_many_cues115§4.1 step 8
splice_type121§5.3
preroll_too_small122§5.3: issued, and answered so
field_range115§5.1, §5.6
descriptor_image_standard115§5.6
descriptor_too_long115§5.6
section_too_long115§5.1
injection_too_large115§7.2
repeated_message100§7.3 step 1: counted, not refused
cancel_too_late121§7.3 step 2
break_overlap—§7.3: a held cue dropped
cue_in_unmatched—§7.3: a held cue dropped
break_zero_duration—§7.3: a held cue dropped
injection_malformed—§8 step 1
injection_untranslatable—§8 step 2
injection_unanswered—§8 step 3
injection_mismatch—§8 step 4
injection_missing—§8 step 5
bad_signature, chain_broken—§8, live-events §6

10. Carriage in a video signal (mapping)

SCTE 104 §7.2 carries the same messages within a serial digital video signal’s vertical ancillary data, by SMPTE ST 2010, and SCTE 104 Appendix C has a receiving injector process each message and remove it from the video. This revision maps that carriage and implements none of it:

11. Status and conformance

partstatuswhere
the API connection (§3)implementedjoule-moq-ingest scte: init, alive, one connection at a time, responses and completions
translation (§4 to §6)implementedscte::scte104, scte::scte35
the injection record, signing, the rules (§7)implementedscte::Engine, scte::Injector
verification (§8)implementedinjection_verify.py; the reference’s tests
carriage in a video signal (§10)specified—

The corpus (live-conformance/injection/) is generated through the reference’s own code paths from fixed seeds and regenerates byte for byte (tests/conformance_corpus.rs): translations of every request and descriptor this revision implements, the standard’s sample (§5.7) among them; every refusal of §4 and §5 with its result code; scenarios of messages on a clock with the chain the injector signs for them, the breaks live-ads §4.3 makes of it, and what was dropped; and chains §8 refuses, each at its event with its reason. injection_verify.py, written from this text and sharing no code with the reference, translates every message, runs §7.3 over every scenario, verifies every chain and derives its breaks, and agrees with the corpus on every vector. The reference’s end-to-end test (tests/scte104_injection.rs) drives an injector over TCP with an automation system of its own, and a subscriber at a relay reads the chain from the events track.

12. Security and privacy considerations