Some checks failed
wai-audit collector and player integration / does this change touch it (push) Successful in 37s
WAI operation catalogue / does this change touch it (push) Successful in 34s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 44s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 45s
wai-quantum studio and desktop / does this change touch it (push) Successful in 22s
WAI Studio / does this change touch it (push) Successful in 23s
wai-sink-android native sink library / does this change touch it (push) Successful in 31s
wai-sink-swift native sink package / does this change touch it (push) Successful in 29s
wai-app and Create / wai-app · does this change touch it (push) Successful in 2m45s
wai-ledger service / does this change touch it (push) Successful in 2m43s
wai-key-proxy / does this change touch it (push) Successful in 2m44s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 2m53s
wai-quantum for JavaScript / does this change touch it (push) Successful in 2m47s
wai-fleet service / does this change touch it (push) Successful in 2m46s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 2m56s
wai-pack / wai-pack · does this change touch it (push) Successful in 2m45s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 2m58s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 5m12s
wai-pack / wai-pack · the floor through a media prober (push) Successful in 1m38s
wai-pack / wai-pack · dependency licences (push) Successful in 44s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 9m48s
wai-pack / wai-pack · linux-arm64 (push) Successful in 4m49s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 6m15s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 7m28s
wai-pack / wai-pack · independent verifier (Python) (push) Successful in 20m32s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 8s
WAI examples / WAI examples · does this change touch them (push) Successful in 10s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 9s
wai-audit collector and player integration / collector · linux (push) Successful in 3m2s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 46s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 32s
wai-key-proxy / proxy · linux (push) Successful in 1m19s
wai-quantum for JavaScript / javascript · linux (push) Successful in 4m31s
wai-ledger service / service · linux (push) Successful in 4m48s
wai-fleet service / service · linux (push) Successful in 5m12s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m58s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 9m26s
wai-pack / wai-pack · linux (push) Successful in 5m44s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 14m11s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 12m41s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m53s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 15m41s
WAI Studio / Studio in WebKit · linux (push) Successful in 15m32s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 7m6s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m59s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 51s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 9m4s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 3m51s
wai-ledger service / service · macos (push) Successful in 56s
wai-fleet service / service · macos (push) Successful in 37s
WAI examples / WAI examples · portable · linux (push) Successful in 14m44s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m50s
wai-pack / wai-pack · macos (push) Successful in 3m41s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 18m3s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 20m9s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 11m18s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 15m16s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 12m49s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 13m27s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 6m45s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 26m54s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 14m53s
extensions/shoppable-events.md (profile live) registers the record wai.live.offer. An offer is the payload WOF1 of a signed meta event of live-events, anchored to the object the programme shows it at, by a room key or a principal whose grant permits meta and carries the attribute offer = publish. It names the merchant's item by the merchant's identifier and the digest of the item's signed description (wai-offer-item, carried in the room as WOI1), and states a price as an integer of minor units with its currency and exponent, a window in the room clock, the regions it may be shown in, and its stock as a count or as unknown; unknown written as zero is refused offer_stock_zero_when_unknown. live-events gains a sentence naming the two payloads and live-rooms 4.4 names the attribute; neither's rules change, and no core text changes. A sink presents an offer only while it reads, its description is held, it is open and it is eligible for the location the sink aggregated itself, shows the price as one exact text, the seller, the item and that it is an offer, and sends nothing about an offer until its viewer acts. The viewer's intent (wai-offer-intent) is signed under a key made for the session; the merchant answers with a signed order or refusal, and takes a whole order back with a signed reversal. No payment is processed: an intent may name a mandate of the repository's payments standard by its digest, and the order carries the same. A merchant's tariff gives the room's operator, the creator and the delivery path each a share in parts per million; a ledger's statement lists one line per order and role, each naming its order, intent and offer, rounded down once. An order with no verifiable intent earns nothing, one intent earns once, and a reversal takes back exactly its order's lines. The reference is the shoppable_events module of wai-rs (feature shoppable_events); the corpus is live-conformance/offers/ (232 cases, each of the 42 reasons shown by one), and shoppable_events_verify.py is its second implementation, with nothing beyond Python's standard library: 232 cases agree, 46 of 46 altered copies fail and 126 of 126 weakened verifiers are caught. Ten faults planted in the reference each fail its corpus test: an intent let by outside the window, an offer's signature not verified (a price changed after signing), an intent for another offer, an order naming another viewer key, a statement's line with no order, an excluded region, zero stock read as unknown, an order earning with no intent, a line rounded up, and a reversal of another order. Decision D28 records the reading: what a sink must show is an engineering floor, not a statement about any consumer law; the extension's section 13 lists what the records ensure and what they do not. Co-Authored-By: Claude Fable 5.2 <[email protected]>
706 lines
37 KiB
Markdown
706 lines
37 KiB
Markdown
# WAI Extension: Live Events
|
||
|
||
> Status: Draft. Registers the record `wai.live.events`: a signed, timeline-bound event
|
||
> track. Timed metadata, ad and chapter cues, chat, reactions, polls, markers,
|
||
> remote-control commands, captions, moderation decisions and room lifecycle are all
|
||
> *events*, and an event is one record, `WEV1`: signed by its principal, hash-chained to
|
||
> that principal's previous event, stamped with the room clock, and optionally anchored to
|
||
> one media object. The same bytes ride an MSF event timeline over MoQ, an
|
||
> `EXT-X-DATERANGE` tag, a CMAF `emsg` box, a WebRTC data channel and a recording. No new
|
||
> transport: WAI defines none by charter, and this extension does not change that. Field
|
||
> semantics and rules over them only. The reference implementation is the `live_events`
|
||
> module of `wai-rs` (feature `live_events`); the corpus is
|
||
> [`live-conformance/events/`](../live-conformance/events/), and its second implementation
|
||
> is `live-conformance/live_events_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Drafts read for this revision: `draft-ietf-moq-msf-01` (MSF),
|
||
> `draft-wilaw-moq-scte35-event-timeline-00`, `draft-wilaw-moq-webvtt-msf-00`,
|
||
> `draft-jennings-moq-mocha-chat-00` and `draft-jennings-moq-mocha-reactions-00`, all
|
||
> Internet-Drafts, none past Working Group Last Call; with RFC 8216 §4.3.2.7 and AOM
|
||
> id3-emsg v1.0.0. A section number of one of those drafts in this text is that
|
||
> revision's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the event record and its digest and signature (§3); the kinds and their
|
||
payloads (§4); the event policy a verifier holds each principal to (§5); verification of
|
||
one record and of each principal's chain, with a delivery gap told apart from corruption
|
||
(§6); poll tallies as `wai.feed.state` cells (§7); and the field mappings onto MoQ, LL-HLS,
|
||
CMAF and DASH, WebRTC and a recording, with what a subscriber joining mid-stream receives
|
||
(§8).
|
||
|
||
Out of scope: a transport; the room descriptor, which fixes a room's id and its clock, and
|
||
the room grant, which carries a principal's policy (both belong to a later extension);
|
||
the contents of a `take_commit` (§4.12), which [capture binding](capture-binding.md)
|
||
§5 defines; moderation
|
||
workflow, which [live-moderation](live-moderation.md) defines; the ad breaks a room's cues
|
||
open, and their interstitials and asset lists, which [live-ads](live-ads.md) defines; and how a
|
||
sink renders any event.
|
||
|
||
Relationship to the core spec: `wai.live.events` is a **record** ([SPEC](../SPEC.md) §5):
|
||
a signed record a WAI tool emits and verifies. It names no payload format and is not
|
||
dispatchable.
|
||
|
||
## 2. Terms
|
||
|
||
- **Room**: a multi-party live session. Its id is 32 bytes. The room extension defines it
|
||
as the digest of the signed room descriptor; here it is opaque, and a verifier is given
|
||
it.
|
||
- **Room clock**: microseconds since the room's `t0`, a UTC instant the room fixes. A
|
||
binding that needs a wallclock is given `t0` as microseconds since 1970-01-01T00:00:00Z;
|
||
one that needs a media time is given the presentation time of `t0`.
|
||
- **Principal**: an Ed25519 public key (RFC 8032), 32 bytes. Its `did:key` form names the
|
||
same key.
|
||
- **Event**, **record**: one `WEV1` (§3).
|
||
- **Chain**: one principal's events in one room, ordered by `seq`.
|
||
- **Head**: the event of a chain a verifier accepted last.
|
||
- **Stream order**: the order in which a verifier is offered one principal's records. Each
|
||
binding says what it is (§8).
|
||
|
||
## 3. The event record
|
||
|
||
### 3.1 Layout
|
||
|
||
Integers are little-endian.
|
||
|
||
| offset | size | field | meaning |
|
||
|---|---|---|---|
|
||
| 0 | 4 | `magic` | `WEV1` (ASCII) |
|
||
| 4 | 1 | `version` | 1 in this revision |
|
||
| 5 | 32 | `room` | the room's id |
|
||
| 37 | 32 | `principal` | the author's Ed25519 public key |
|
||
| 69 | 8 | `seq` | u64: the event's position in its chain, from 0 |
|
||
| 77 | 32 | `prev` | the digest (§3.2) of the chain's event `seq − 1`; 32 zero bytes when `seq` is 0 |
|
||
| 109 | 8 | `t` | u64: the room clock at the event |
|
||
| 117 | 1 | `anchor_flag` | 0: no anchor; 1: an anchor follows |
|
||
| 118 | 48 | `anchor` | only when `anchor_flag` is 1: `track` (32 bytes), `group` u64, `object` u64 |
|
||
| *a* | 1 | `kind` | §4 |
|
||
| *a* + 1 | 2 | `payload_len` | u16 |
|
||
| *a* + 3 | `payload_len` | `payload` | §4 |
|
||
| *a* + 3 + `payload_len` | 64 | `sig` | §3.2 |
|
||
|
||
*a* is 118 without an anchor and 166 with one.
|
||
|
||
An **anchor** binds an event to one MoQ object, so that a marker, a cue or a caption lands
|
||
on an exact frame. `track` is SHA-256 over the track's namespace as its MSF catalog entry
|
||
has it (its own, or the catalog track's that it inherits, MSF §5.2.2), one zero byte, and
|
||
the track's name; `group` and `object` are the object's MOQT Group ID and Object ID. An
|
||
anchor names an object on MoQ; the other bindings carry it unchanged.
|
||
|
||
`payload_len` lets a payload be up to 65535 bytes, the format's limit; the bound a
|
||
verifier applies is the policy's `max_payload` (§5).
|
||
|
||
### 3.2 Digest and signature
|
||
|
||
The **digest** of an event is SHA-256 (FIPS 180-4) over its record from `magic` through
|
||
`payload`: every byte but `sig`. It is written as 64 lowercase hexadecimal digits. An event
|
||
is named by its digest wherever another event refers to it: `prev`, a vote's poll, a
|
||
reaction's or a decision's target.
|
||
|
||
The **signature** is Ed25519 (RFC 8032, pure Ed25519) by `principal`, over the 15 bytes
|
||
`wai:live-event` (ASCII) and 0x01, followed by the bytes the digest covers. It is verified
|
||
strictly, as [SPEC](../SPEC.md) §7 *Signatures* requires: a key that fails step 1 or 2 is
|
||
`bad_key`, and a signature that fails a later step is `bad_signature`.
|
||
|
||
### 3.3 Reading a record
|
||
|
||
A reader reads exactly the bytes it is given as one record, in the order of §3.1, and
|
||
refuses at the first of these that holds:
|
||
|
||
1. Fewer than 4 bytes: `truncated`. A magic other than `WEV1`: `malformed`.
|
||
2. No byte after the magic: `truncated`. A version other than 1: `unknown_version`. A
|
||
reader reads no further field, since a later version's layout is its own.
|
||
3. The bytes end before a field the layout requires: `truncated`. An `anchor_flag` other
|
||
than 0 or 1, when it is read: `malformed`.
|
||
4. Bytes after `sig`: `malformed`.
|
||
|
||
## 4. Kinds and payloads
|
||
|
||
| code | kind | payload |
|
||
|---|---|---|
|
||
| `0x01` | `meta` | §4.2 |
|
||
| `0x02` | `cue` | §4.3 |
|
||
| `0x03` | `chat` | §4.4 |
|
||
| `0x04` | `reaction` | §4.5 |
|
||
| `0x05` | `poll_open` | §4.6 |
|
||
| `0x06`, `0x07` | `vote`, `poll_close` | §4.7 |
|
||
| `0x08` | `marker` | §4.8 |
|
||
| `0x09` | `control` | §4.9 |
|
||
| `0x0A` | `caption` | §4.10 |
|
||
| `0x0B` | `decision` | §4.11 |
|
||
| `0x0C` | `take_commit` | §4.12 |
|
||
| `0x10`–`0x16` | `join`, `leave`, `publish`, `unpublish`, `grant_exchange`, `takeover`, `failover` | §4.13 |
|
||
|
||
No other code is defined in this revision; a record of another kind is refused
|
||
`unknown_kind` (§6.1). Codes `0x17` to `0x1F` are reserved for lifecycle kinds.
|
||
|
||
A payload is read in the order its fields are written, and each field's rule applies when
|
||
it is read. A payload that ends before a field, that holds bytes after its last field, or
|
||
whose field holds a value this section does not allow, is `malformed_payload`; a text
|
||
field's own rules are §4.1's. Integers are little-endian. A digest field is 32 bytes.
|
||
|
||
### 4.1 Text
|
||
|
||
A text field is a u16 length followed by that many bytes. Its rules, in order:
|
||
|
||
1. The bytes are UTF-8 (RFC 3629): no overlong form, no surrogate, nothing above U+10FFFF.
|
||
Otherwise `bad_utf8`.
|
||
2. They hold at most the policy's `max_text_cp` code points (§5; 500 by default).
|
||
Otherwise `text_too_long`.
|
||
|
||
Each text field is bounded on its own. Empty text is allowed.
|
||
|
||
### 4.2 `meta`
|
||
|
||
Opaque application metadata: the whole payload, read for its length alone. It is the timed
|
||
metadata a player hands to its application. One payload is defined beside this extension: a
|
||
machine descriptor (labels, regions and embeddings a model produced about the anchored
|
||
object), which [machine-descriptors](machine-descriptors.md) lays out and a descriptor reader
|
||
reads; a room's verifier carries it as any other `meta` payload. Two more are an offer a room
|
||
presents and the description of the item it offers, which
|
||
[shoppable-events](shoppable-events.md) lays out and its readers read.
|
||
|
||
### 4.3 `cue`
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `type` | u8 | 1 out (leave the program for a break), 2 in (return to it), 3 chapter, 4 break (out and back, its duration known) |
|
||
| `cue_id` | u32 | ties an out to its in; an SCTE-35 `splice_event_id` where the message has one |
|
||
| `duration_ms` | u32 | out and break: the planned duration; in: the actual duration since its out; chapter: its length; 0 when not known |
|
||
| `form` | u8 | 0: no SCTE-35 message; 1: an SCTE 35-1 `splice_info_section()`; 2: an SCTE 35-2 event-based signaling message |
|
||
| `scte_len` | u16 | only when `form` is not 0 |
|
||
| `scte` | `scte_len` | the message, verbatim |
|
||
|
||
Form 1 is read only as far as this: `table_id` is 0xFC, and `section_length` (the low 12
|
||
bits of bytes 1 and 2) plus 3 is `scte_len`. WAI reads no command, descriptor or CRC of
|
||
it. Form 2 is at least one byte, carried opaque. A `type` or `form` not in the table is
|
||
`malformed_payload`. The summary (`type`, `cue_id`, `duration_ms`) is what a sink acts on
|
||
without parsing SCTE-35. Where a message is carried too, both describe one splice, and a
|
||
producer **MUST NOT** write a summary its message contradicts.
|
||
|
||
### 4.4 `chat`
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `text` | text | the message |
|
||
| `origin` | u8 | optional: 1 human, 2 machine, 3 machine-reviewed |
|
||
| `model` | 32 | only when `origin` is 2 or 3: a digest naming the model that produced the message |
|
||
|
||
`origin` is present exactly when a byte follows `text`; a chat without it states no origin.
|
||
`origin` and `model` mean what a caption's mean (§4.10): they are the message's
|
||
machine-generation disclosure, and a sink that labels machine-generated messages takes the
|
||
label from them. A room may require them of a principal: [live-rooms](live-rooms.md) §4.4
|
||
marks an AI participant, whose every chat states origin 2 or 3.
|
||
|
||
### 4.5 `reaction`
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `type` | u8 | 1 emoji, 2 vote, 3 raise, 4 applause, 5 custom: the reaction types of `draft-jennings-moq-mocha-reactions-00` §4.4 |
|
||
| `value` | u16 | not 0: for emoji and raise, that draft's §4.5 integer encodings; for the others, the application's |
|
||
| `flags` | u8 | bit 0: a target follows; bit 1: this retracts the reaction it repeats; every other bit 0 |
|
||
| `target` | 32 | only when bit 0 is set: the digest of the event reacted to |
|
||
| `origin` | u8 | optional, as a chat's (§4.4): present exactly when a byte follows the field before it |
|
||
| `model` | 32 | only when `origin` is 2 or 3 |
|
||
|
||
A reaction without a target is **standalone**. Standalone reactions are held to the
|
||
policy's reaction rate (§6.3), as that draft holds them to one per second per device
|
||
(§5.1 there). A room's polls use `poll_open` and `vote`, not reaction type 2.
|
||
|
||
### 4.6 `poll_open`
|
||
|
||
`question` (text), `n` (u8, at least 2), then `n` option texts. A poll is named by the
|
||
digest of its `poll_open`.
|
||
|
||
### 4.7 `vote` and `poll_close`
|
||
|
||
- `vote`: `poll` (a poll's digest), `option` (u8).
|
||
- `poll_close`: `poll`, `n` (u8, at least 2), then `n` counts (u32 each): the author's
|
||
statement of the final tally.
|
||
|
||
§7 says which votes count, which `poll_close` closes a poll, and when a stated tally is
|
||
checked.
|
||
|
||
### 4.8 `marker`
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `taxonomy` | u32 | what the marker observes, in the application's taxonomy |
|
||
| `polarity` | u8 | 0 neutral, 1 positive, 2 negative |
|
||
| `text` | text | a note |
|
||
| `has_attachment` | u8 | 0 or 1 |
|
||
| `attachment` | 32 | only when `has_attachment` is 1: a digest naming an attached object, such as a voice note sealed as a WAI envelope ([jwp-receipts](jwp-receipts.md) §2.1 content hash) |
|
||
|
||
### 4.9 `control`
|
||
|
||
`target` (the principal the command is for), `command` (u8), then the command's argument:
|
||
|
||
| `command` | name | argument |
|
||
|---|---|---|
|
||
| 1 | `record_start` | `count_in_ms` u32: recording starts this long after `t` |
|
||
| 2 | `record_stop` | none |
|
||
| 3 | `zoom` | `q16` u32: the zoom factor in Q16.16, not 0 |
|
||
| 4 | `mute` | `on` u8: 1 mute, 0 unmute |
|
||
| 5 | `layer_request` | `layer` u8: the layer asked for |
|
||
|
||
A control event takes effect at its `t` on the room clock, so a count-in lines up for every
|
||
participant. Whether the target acts on it is the target's; the event records that it was
|
||
asked, by whom and when.
|
||
|
||
### 4.10 `caption`
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `duration_ms` | u32 | how long the caption shows; 0 when not known |
|
||
| `origin` | u8 | 1 human, 2 machine, 3 machine-reviewed |
|
||
| `model` | 32 | only when `origin` is 2 or 3: a digest naming the model that produced the text |
|
||
| `lang_len` | u8 | |
|
||
| `lang` | `lang_len` | a BCP 47 language tag (RFC 5646), in ASCII |
|
||
| `text` | text | the caption |
|
||
|
||
`lang` is read here only as far as this: 2 to 35 characters; a first subtag of 2 to 8
|
||
letters; then subtags of 1 to 8 letters or digits, each after one hyphen. A tag that fails
|
||
is `malformed_payload`. `origin` is the event's machine-generation disclosure: a sink that
|
||
labels machine-generated content, in its player, in a playlist characteristic or in a
|
||
provenance assertion, takes the label from it.
|
||
|
||
### 4.11 `decision`
|
||
|
||
`verdict` (u8: 1 allow, 2 deny, 3 redact), `target` (the digest of the event decided on),
|
||
`reason` (u16: a reason code the room defines). A decision is an event of the principal
|
||
that decided, and the target stays in its chain unchanged. A sink **SHOULD NOT** show
|
||
content a decision of a principal the room authorises to moderate denies or redacts.
|
||
[Live-moderation](live-moderation.md) defines the statement that follows a decision in its
|
||
author's chain, what else a target may name, and how one decision supersedes another.
|
||
|
||
### 4.12 `take_commit`
|
||
|
||
Read here for its length alone, which `max_payload` bounds: a room's verifier carries it
|
||
opaque. [Capture binding](capture-binding.md) §5 defines its contents, a device's rolling
|
||
commitment to the master it is capturing, and a take verifier reads them.
|
||
|
||
### 4.13 Lifecycle
|
||
|
||
| code | kind |
|
||
|---|---|
|
||
| `0x10` | `join` |
|
||
| `0x11` | `leave` |
|
||
| `0x12` | `publish` |
|
||
| `0x13` | `unpublish` |
|
||
| `0x14` | `grant_exchange` |
|
||
| `0x15` | `takeover` |
|
||
| `0x16` | `failover` |
|
||
|
||
Payload: `subject` (the principal the event is about), then `detail`, the rest of the
|
||
payload, opaque in this revision: a track name, or the digest of a grant. The room
|
||
extension defines `detail` for each kind. A room authority, or a relay acting for it,
|
||
authors these; the default policy permits them to no one (§5).
|
||
|
||
## 5. The event policy
|
||
|
||
An event policy states what one principal may send. A verifier holds every principal to a
|
||
policy: a default one, and one of the principal's own where it has one. In this revision
|
||
the verifier's caller gives it the policies. The room grant of a later extension carries
|
||
these same fields, per principal.
|
||
|
||
The policy record, `WEP1`, is 65 bytes; integers are little-endian.
|
||
|
||
| offset | size | field | meaning | default |
|
||
|---|---|---|---|---|
|
||
| 0 | 4 | `magic` | `WEP1` (ASCII) | |
|
||
| 4 | 1 | `version` | 1 in this revision | |
|
||
| 5 | 32 | `kinds` | kind *k* is permitted when bit *k* mod 8 of byte *k* div 8 is set | every kind of §4 but the lifecycle kinds |
|
||
| 37 | 2 | `max_payload` | the longest payload, in bytes | 1024 |
|
||
| 39 | 2 | `max_text_cp` | the most code points in one text field | 500 |
|
||
| 41 | 4 | `max_events` | the most events in one window (§6.3) | 50 |
|
||
| 45 | 8 | `window_us` | the window, in microseconds of room clock | 1 000 000 |
|
||
| 53 | 4 | `max_reactions` | the most standalone reactions in one reaction window | 1 |
|
||
| 57 | 8 | `reaction_window_us` | the reaction window | 1 000 000 |
|
||
|
||
A reader refuses a policy record: with fewer than 4 bytes, `truncated`; with another magic,
|
||
`policy_malformed`; with no version byte, `truncated`; with another version,
|
||
`unknown_version`; shorter than 65 bytes, `truncated`; longer, `policy_malformed`; that
|
||
permits a kind §4 does not define, or in which any bound is 0, `policy_malformed`.
|
||
|
||
Every bound this extension gives a number for is one of these defaults, and a room
|
||
**MAY** set any other. A room authority's policy is the default with the lifecycle kinds
|
||
also permitted.
|
||
|
||
## 6. Verification
|
||
|
||
A verifier verifies one room's records, against the room id it is given. It is offered
|
||
records one at a time, each principal's in stream order. A record it refuses changes
|
||
nothing it holds.
|
||
|
||
### 6.1 One record
|
||
|
||
In this order; the first check that fails names the refusal:
|
||
|
||
1. The record reads (§3.3): `truncated`, `malformed` or `unknown_version`.
|
||
2. Its `kind` is one §4 defines: otherwise `unknown_kind`.
|
||
3. Its `room` is the verifier's room: otherwise `wrong_room`.
|
||
4. Its signature verifies (§3.2): otherwise `bad_key` or `bad_signature`.
|
||
5. Its principal's policy permits its kind: otherwise `kind_not_permitted`.
|
||
6. `payload_len` is at most the policy's `max_payload`: otherwise `payload_too_large`.
|
||
7. Its payload reads (§4), with the policy's `max_text_cp`: otherwise `malformed_payload`,
|
||
`bad_utf8` or `text_too_long`.
|
||
|
||
### 6.2 A principal's chain
|
||
|
||
For each principal the verifier keeps the digests of the events it holds, by `seq`, and
|
||
the chain's head. For a record that passed §6.1:
|
||
|
||
1. If the verifier holds an event with this `seq` and this digest, the record is a
|
||
**duplicate**: it is ignored, counted nowhere, and checked no further. The records a
|
||
group repeats at its start (§8.2) are duplicates to a subscriber that holds them.
|
||
2. With a head:
|
||
- `seq` at or below the head's: `seq_replay` when an event with this `seq` is held (a
|
||
second, different event claims a `seq` already used), and `seq_not_increasing`
|
||
otherwise (it arrives behind the head);
|
||
- `seq` one above the head's: `prev` **MUST** be the head's digest, otherwise
|
||
`chain_broken`;
|
||
- `seq` more than one above the head's: the seqs between are a gap;
|
||
- in each of the last two cases, `t` **MUST NOT** be below the head's `t`, otherwise
|
||
`time_reversed`.
|
||
3. Without a head, the first record of the principal the verifier accepts: when `seq` is
|
||
0, `prev` **MUST** be 32 zero bytes, otherwise `chain_broken`; when `seq` is above 0,
|
||
seqs 0 to `seq − 1` are a gap.
|
||
|
||
Then §6.3. An event accepted becomes the head, and a gap it opens is recorded.
|
||
|
||
A gap is a **detectable delivery gap**, reported with the code `delivery_gap`. The
|
||
verifier knows exactly which seqs it does not hold, and that the events it does hold are
|
||
the principal's, in order. A gap is not a refusal, and a verifier **MUST NOT** report it as
|
||
corruption. A receiver **MAY** fill a gap by fetching the events it lacks (MOQT FETCH of
|
||
earlier groups, or a recording) and verifying the chain again from its start. It **MUST
|
||
NOT** splice an event behind its head into a chain it has moved past: that record is
|
||
refused `seq_not_increasing`, by step 2.
|
||
|
||
### 6.3 Rates
|
||
|
||
Over the events of the principal the verifier has accepted (a duplicate or a refused
|
||
record is not counted):
|
||
|
||
1. Counting this event, more than `max_events` events whose `t` is less than `window_us`
|
||
before this event's `t`: `rate_exceeded`.
|
||
2. For a standalone reaction (§4.5), counting it, more than `max_reactions` standalone
|
||
reactions whose `t` is less than `reaction_window_us` before its `t`:
|
||
`rate_exceeded`.
|
||
|
||
An earlier event at `t_f` is within a window `w` of an event at `t` when `t − t_f < w`:
|
||
two events exactly one window apart do not share one. A verifier with a gap counts only
|
||
what it holds; one that holds the whole chain checks the rate exactly.
|
||
|
||
### 6.4 Outcomes
|
||
|
||
Verifying a sequence of records gives one outcome:
|
||
|
||
- **refuse**: the reason and the index of the first record refused. The verifier stops
|
||
there.
|
||
- **gap**: no record was refused, and the verifier does not hold every seq of every chain
|
||
it saw. It states each gap as the principal and the first and last seq it lacks, in the
|
||
order found, under the code `delivery_gap`.
|
||
- **accept**: no record was refused, there is no gap, and every stated tally equals the
|
||
replay (§7). A stated tally that does not is **refuse**, `tally_mismatch`, at the index
|
||
of its `poll_close`.
|
||
|
||
## 7. Polls replay as feed state
|
||
|
||
A poll's tally is a record of `wai.feed.state` cells ([state-feeds](state-feeds.md)): the
|
||
state an op log of `wai.world.sim.feed0` replays to. A tally is therefore
|
||
replay-equivalent, and a tally an author states is checkable.
|
||
|
||
Over the events a verifier accepted:
|
||
|
||
- A **poll** is a `poll_open`. Its **close** is the `poll_close` naming it with the lowest
|
||
`seq` among those its author wrote. A `poll_close` by any other principal closes
|
||
nothing.
|
||
- A `vote` **counts** when the verifier holds its poll, its `option` is below the poll's
|
||
`n`, its `t` is at or after the `poll_open`'s `t`, and the poll has no close or the
|
||
vote's `t` is before the close's `t`.
|
||
- A principal's vote in a poll is its last vote that counts, by `seq`; an earlier one is
|
||
replaced.
|
||
|
||
The feed: each poll is the node `mint_node_id(author, seq)` of its `poll_open`
|
||
([interactive-worlds](interactive-worlds.md) §5.4), whose record is the `Int` cells
|
||
`state, count_0, …, count_{n−1}`. Its ops are:
|
||
|
||
- `CREATE` with `Int(1)` and `n` cells `Int(0)`, the actor the poll's author and the
|
||
Lamport counter the `poll_open`'s `seq`;
|
||
- `ADD_INT(1 + option, 1)` for each principal's vote;
|
||
- `SET_CELL(0, Int(0))` when the poll has a close.
|
||
|
||
Every verifier derives these ops from signed events; they are not sent and not signed. A
|
||
vote one principal changes is, equally, `ADD_INT −1` on the old option and `+1` on the new,
|
||
and the replay reaches the same state. The tally's state hash is feed0's,
|
||
`BLAKE3("wai:world-state\x01" ‖ canonical state bytes)` ([state-feeds](state-feeds.md)
|
||
§4), over every poll the verifier holds.
|
||
|
||
A `poll_close`'s counts are its author's statement. A verifier that holds every chain
|
||
whole (no gap) checks each close: its `n` equals the poll's, and each count equals the
|
||
votes for that option. Otherwise it refuses with `tally_mismatch`, at the lowest index of
|
||
such a close. With a gap a verifier does not make this check: a tally it computes is
|
||
unchecked.
|
||
|
||
## 8. Bindings
|
||
|
||
Field semantics only. Each binding says how the record is carried, what it derives from
|
||
the record, its stream order (§6), and how a reader refuses what it cannot read
|
||
(`binding_malformed`) or what disagrees with the record it carries (`binding_mismatch`).
|
||
The record a reader takes from a binding is then verified by §6.
|
||
|
||
### 8.1 MoQ: an MSF event timeline
|
||
|
||
- Each principal publishes its events on a track of its own, as
|
||
`draft-jennings-moq-mocha-chat-00` §3.3 has each device publish on its own track. A
|
||
chain and a track are then one unit, and a relay fans tracks out without merging them.
|
||
The room extension names the tracks.
|
||
- The track is an MSF event timeline (MSF §8). Its catalog entry has `packaging`
|
||
`"eventtimeline"` (MSF §5.2.4), `eventType` `"urn:wai:event:1"` (MSF §5.2.5), `mimeType`
|
||
`"application/json"` (MSF §5.2.19), `isLive` (MSF §5.2.7), and `depends` (MSF §8.2): the
|
||
tracks its anchors name that are in its own namespace, since MSF §5.2.14 names a
|
||
dependency in the declaring track's namespace alone. A room's events track is the only
|
||
track of its namespace (live-rooms §6.1), so there its `depends` is `[]`; an anchor names
|
||
its track by digest (§3.1) wherever it is.
|
||
- A **record** of the timeline (MSF §8.1) has one index member and `data`:
|
||
- an anchored event whose `group` and `object` are each at most 2^53 − 1 has
|
||
`"l": [group, object]`, the MOQT Location of the anchored object;
|
||
- any other event has `"t"`: `⌊(t0 + t) / 1000⌋`, the wallclock in milliseconds since
|
||
1970 (MSF §7.1.1), which **MUST** be at most 2^53 − 1;
|
||
- `data` is `{"wev1": "<base64url>"}`, the record in base64url without padding
|
||
(RFC 4648 §5).
|
||
|
||
It is written `{"t":<t>,"data":{"wev1":"<b64>"}}` or
|
||
`{"l":[<group>,<object>],"data":{"wev1":"<b64>"}}`, with no whitespace. This binding
|
||
never writes `m`: an event has no media timestamp of its own, and an anchor names its
|
||
object exactly. An events track uses `t` and `l` record by record, as
|
||
`draft-wilaw-moq-scte35-event-timeline-00` §4.2 also does; MSF §8.1's preference for
|
||
one index type is a **SHOULD**.
|
||
- A reader parses the record as JSON and ignores members this binding does not define.
|
||
It refuses as `binding_malformed`: anything but a JSON object; no string `data.wev1`;
|
||
one that is not the canonical base64url of its bytes (the RFC 4648 §5 alphabet, no `=`,
|
||
and the encoding of what it decodes to); bytes that §3.3 does not read as a record of a
|
||
kind §4 defines; and a record with other than exactly one of `t`, `l` and `m`. It refuses
|
||
as `binding_mismatch` an index other than the one this binding writes for the event:
|
||
`t` a JSON integer equal to the event's, or `l` an array of exactly two integers equal
|
||
to its anchor's.
|
||
- **Groups.** An event's group is `⌊t / 60 000 000⌋`, the minute of room clock it falls
|
||
in, as `draft-jennings-moq-mocha-chat-00` §3.4 groups messages by minute. Group IDs
|
||
increase with `t`, since `t` never decreases along a chain. Object 0 of a group is the
|
||
independent object of MSF §8.3: a JSON array of the join set (§8.2) at the group's
|
||
start. Each event of the group is then the next object, an array of its one record, an
|
||
incremental update (MSF §8.3). MSF §8.3 has the independent object hold every record
|
||
"accumulated and accessible" so far. For an events track the records accessible at a
|
||
group boundary are its join set; earlier records stay reachable by FETCH of earlier
|
||
groups.
|
||
- **Stream order** is Location order: by group, then by object, and the records of one
|
||
object in the order written.
|
||
- **Cues.** A publisher **MAY** also publish each cue of form 1 on a timeline of
|
||
`draft-wilaw-moq-scte35-event-timeline-00`, its `scte35_payload` the base64 (RFC 4648
|
||
§4) of the cue's section and its index chosen by that draft's §4.2, so that a consumer
|
||
of SCTE-35 alone reads it unchanged. That draft registers the event type
|
||
`urn:scte:scte35:2022:bin`, and MSF §14.2's initial table spells it
|
||
`urn:scte:scte35:2013:bin`; this revision takes no position between the two.
|
||
- **Ad creatives.** What fills a break is signalled on a sibling timeline of
|
||
`draft-riedl-moq-ad-creative-signaling-01`, whose records [live-ads](live-ads.md) §13 writes
|
||
from a break and a fill and holds to the room. It is another track with another `eventType`:
|
||
no record of it is an event of this extension.
|
||
- **Captions.** A publisher **MAY** also publish each caption as a cue of
|
||
`draft-wilaw-moq-webvtt-msf-00` on a timeline of its own. This revision defines no
|
||
mapping between the two.
|
||
- **Registration.** WAI requests `urn:wai:event:1` in the "MSF Event Timeline Types"
|
||
registry (MSF §14.2, Expert Review). The URN is the form of that registry's entries,
|
||
and it is also this extension's `emsg` scheme (§8.4). Until it is registered it is a
|
||
custom type in MSF §10.5's sense.
|
||
|
||
### 8.2 Joining mid-stream
|
||
|
||
The **join set** of a principal's track at the start of group *g* (room clock *g* × 60 s)
|
||
is drawn from the principal's events whose `t` is before that instant. In `seq` order it
|
||
holds:
|
||
|
||
- every `poll_open` whose poll has no `poll_close` of its author before the instant
|
||
(**MUST**);
|
||
- in each of those polls, the principal's last vote (**MUST**);
|
||
- every `caption` with a `duration_ms` above 0 whose interval, `t` to
|
||
`t + duration_ms`, contains the instant (**MUST**);
|
||
- every `cue` of type out or break with no later `in` of the same `cue_id` (**MUST**);
|
||
- any other event (**MAY**).
|
||
|
||
The records are repeated byte for byte, so each is a duplicate (§6.2 step 1) to a
|
||
subscriber that holds it and the state a joining subscriber starts from. This is how
|
||
`draft-wilaw-moq-webvtt-msf-00` §5 has the first object of each group carry the cues
|
||
accumulated so far. A subscriber that joins verifies from the first record it holds and
|
||
reports what it lacks as gaps (§6.2), so it knows a tally it computes is unchecked. It
|
||
learns a final tally from the poll author's `poll_close`, or checks one by fetching the
|
||
voters' earlier groups.
|
||
|
||
### 8.3 LL-HLS: `EXT-X-DATERANGE`
|
||
|
||
An event in a Media Playlist is one `EXT-X-DATERANGE` tag (RFC 8216 §4.3.2.7, which
|
||
low-latency HLS keeps). Its attributes are written in this order, separated by commas with
|
||
no spaces:
|
||
|
||
| attribute | when | value |
|
||
|---|---|---|
|
||
| `ID` | always | quoted: the event's digest, 64 lowercase hexadecimal digits |
|
||
| `CLASS` | always | quoted: `science.transaction.wai.event` |
|
||
| `START-DATE` | always | quoted: `t0 + t` as `YYYY-MM-DDTHH:MM:SS.mmmZ`, in UTC, the milliseconds floored, always three digits after the point |
|
||
| `DURATION` | a cue of type in or chapter, or a caption, with `duration_ms` above 0 | `duration_ms` as decimal seconds with three digits after the point (`30.000`) |
|
||
| `PLANNED-DURATION` | a cue of type out or break with `duration_ms` above 0 | as `DURATION` |
|
||
| `SCTE35-OUT`, `SCTE35-IN`, `SCTE35-CMD` | a cue of form 1 | `0x` and the section in uppercase hexadecimal: `SCTE35-OUT` for out and break, `SCTE35-IN` for in, `SCTE35-CMD` for chapter (RFC 8216 §4.3.2.7.1) |
|
||
| `X-WAI-EVENT` | always | quoted: the record in base64url without padding |
|
||
|
||
- RFC 8216 §4.3.2.7.1 represents a splice out/in pair with tags that share one `ID`. Here
|
||
each tag carries an event of its own under its own `ID`, because two tags of a playlist
|
||
that share an `ID` must agree on every attribute both carry, and their `X-WAI-EVENT`
|
||
values differ. `cue_id` ties the pair.
|
||
- RFC 8216 asks a client attribute's name to follow reverse-DNS form. `X-WAI-EVENT`
|
||
appears only under its `CLASS`, whose semantics every date range of that class follows
|
||
(RFC 8216 §4.3.2.7).
|
||
- A playlist that carries one of these tags **MUST** carry `EXT-X-PROGRAM-DATE-TIME`
|
||
(RFC 8216 §4.3.2.7), on the same UTC timeline as `t0`.
|
||
- **Stream order** is the order of the tags in the playlist, across its updates.
|
||
- A reader takes the tag as `#EXT-X-DATERANGE:` and an attribute list (RFC 8216 §4.2):
|
||
attribute names of `A`–`Z`, `0`–`9` and `-`, each at most once, and a quoted value that
|
||
runs to the next quote. It refuses as `binding_malformed`: another tag; an attribute list
|
||
that does not read; no `X-WAI-EVENT`, one not quoted, or one that is not canonical
|
||
base64url (§8.1); and bytes that §3.3 does not read as a record of a kind §4 defines. It
|
||
then renders the tag for that record and refuses as `binding_mismatch` unless each
|
||
attribute of the table above is, in the tag and in the rendering, either absent from
|
||
both or present in both with the same value as written. Attributes the table does not
|
||
name are ignored, and their order is not compared.
|
||
|
||
### 8.4 CMAF and DASH: `emsg` version 1
|
||
|
||
An event is one Event Message box, version 1 (ISO/IEC 23009-1, the syntax AOM id3-emsg
|
||
v1.0.0 §2.2.2 reproduces). Integers are big-endian:
|
||
|
||
| field | value |
|
||
|---|---|
|
||
| `size` | the box's length in bytes |
|
||
| `type` | `emsg` |
|
||
| `version`, `flags` | 1, 0 |
|
||
| `timescale` | 1000 |
|
||
| `presentation_time` | u64: `pt0 + ⌊t / 1000⌋`, where `pt0` is the presentation time of `t0` in milliseconds on the track's timeline |
|
||
| `event_duration` | a cue or a caption: `duration_ms`, or 0xFFFFFFFF (not known) when it is 0; any other kind: 0 |
|
||
| `id` | the first four bytes of the event's digest, as a u32 |
|
||
| `scheme_id_uri` | `urn:wai:event:1`, then a zero byte |
|
||
| `value` | the kind's name (§4), then a zero byte |
|
||
| `message_data` | the record |
|
||
|
||
- `id` lets a player drop a box it has seen. A receiver identifies an event by its digest
|
||
(§6.2), since four bytes of a digest can repeat.
|
||
- **Stream order** is presentation order: the order of the boxes in the segments, by
|
||
`presentation_time`, and within one time in the order the segment holds them.
|
||
- A reader refuses as `binding_malformed`: a box shorter than 32 bytes, whose `size` is
|
||
not its length, whose type is not `emsg` or whose version is not 1; a box without two
|
||
strings each ended by a zero byte; and `message_data` that §3.3 does not read as a
|
||
record of a kind §4 defines. It then renders the box for that record and refuses as
|
||
`binding_mismatch` unless the two are equal byte for byte.
|
||
- A publisher whose players surface only ID3 timed metadata **MAY** also carry the record
|
||
in ID3 as AOM id3-emsg v1.0.0 has it (scheme `https://aomedia.org/emsg/ID3`). This
|
||
revision defines no ID3 frame for it.
|
||
|
||
### 8.5 WebRTC
|
||
|
||
A data channel (RFC 8831), reliable and ordered, with binary messages. One message is one
|
||
record, whole. **Stream order** is message order. A sink that needs an event's media time
|
||
takes it from the anchor or from the room clock; the RTP absolute capture time header
|
||
extension, where a sender adds it, is experimental.
|
||
|
||
### 8.6 Recording
|
||
|
||
A recording holds each principal's chain whole: its records concatenated in `seq` order,
|
||
each record's length following from its own fields (§3.1). **Stream order** is that order.
|
||
In a sealed package the events are one component, which
|
||
[live-recording](live-recording.md) §3.3 defines. A recording verifies with the outcome accept, or states its gaps.
|
||
|
||
## 9. Reasons
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `truncated` | §3.3, §5 |
|
||
| `malformed` | §3.3 |
|
||
| `unknown_version` | §3.3, §5 |
|
||
| `unknown_kind` | §6.1 step 2 |
|
||
| `wrong_room` | §6.1 step 3 |
|
||
| `bad_key` | §3.2, §6.1 step 4 |
|
||
| `bad_signature` | §3.2, §6.1 step 4 |
|
||
| `kind_not_permitted` | §6.1 step 5 |
|
||
| `payload_too_large` | §6.1 step 6 |
|
||
| `malformed_payload` | §4, §6.1 step 7 |
|
||
| `bad_utf8` | §4.1 |
|
||
| `text_too_long` | §4.1 |
|
||
| `seq_replay` | §6.2 step 2 |
|
||
| `seq_not_increasing` | §6.2 step 2 |
|
||
| `chain_broken` | §6.2 steps 2 and 3 |
|
||
| `time_reversed` | §6.2 step 2 |
|
||
| `delivery_gap` | §6.2, §6.4: a gap, not a refusal |
|
||
| `rate_exceeded` | §6.3 |
|
||
| `tally_mismatch` | §7 |
|
||
| `policy_malformed` | §5 |
|
||
| `binding_malformed` | §8.1, §8.3, §8.4 |
|
||
| `binding_mismatch` | §8.1, §8.3, §8.4 |
|
||
|
||
## 10. Conformance
|
||
|
||
[`live-conformance/events/`](../live-conformance/events/) holds the corpus, in one file,
|
||
`vectors.json`, written from fixed seeds by `wai_live_events_vectors gen`. It has:
|
||
|
||
- **vectors**: a room, the policies its principals are held to, and records in stream
|
||
order, with the outcome §6.4 gives them. An accepted vector also states each record's
|
||
digest, the tally (§7) and each record's MSF record, `EXT-X-DATERANGE` tag and `emsg`
|
||
box (§8); a vector with a gap states its gaps. Every kind has an accepted vector, and
|
||
chains of several principals are interleaved in one. Every refusal of §6 has a vector
|
||
refused with it, each a control of one defect: a flipped signature bit and three
|
||
forgeries that a lenient Ed25519 verifier accepts, a broken chain, a reorder, a replayed
|
||
`seq`, `t` going back, an oversize payload, a kind not permitted, both rates exceeded,
|
||
invalid UTF-8 and overlong text, truncated and malformed records, an unknown version and
|
||
kind, the wrong room, malformed payloads (a chat stating a machine origin without its
|
||
model among them) and a stated tally the replay does not reach. Chats and a reaction
|
||
stating each origin have an accepted vector.
|
||
- **policy cases**: policy records, read (§5) or refused with their reason.
|
||
- **binding cases**: tags, boxes and MSF records, read back to their record (§8) or
|
||
refused with their reason.
|
||
|
||
A conforming verifier gives every vector, policy case and binding case the outcome, the
|
||
reason, the index, the gaps, the digests, the tally and the renderings the corpus states.
|
||
`wai_live_events_vectors verify` and the test `tests/live_events.rs` check that the file
|
||
regenerates byte for byte and that `wai-rs` gives every case its outcome.
|
||
`live-conformance/live_events_verify.py` is the second implementation, written from this
|
||
text: it shares no code with `wai-rs`, verifies signatures with WAI's strict rule written
|
||
from RFC 8032 (`claim-conformance/_ed25519.py`) and computes BLAKE3 from its specification
|
||
(`claim-conformance/_blake3.py`). It gives every case the same outcome.
|
||
|
||
## 11. Security and privacy considerations
|
||
|
||
- Every field of an event is signed. A relay can drop an event but cannot alter one, and
|
||
a drop is a gap the receiver detects (§6.2).
|
||
- `t` is its author's claim. A room clock authority and per-hop receipts, both of later
|
||
extensions, witness when an event was seen; this revision checks only that a chain's
|
||
`t` never goes back.
|
||
- Two different events with one `seq` are refused `seq_replay`. A receiver that holds
|
||
both holds two signatures by one key over one position: evidence the author
|
||
equivocated.
|
||
- Payloads are untrusted data: an SCTE-35 message, `meta`, a lifecycle `detail` and a
|
||
`take_commit` are carried, not interpreted, as
|
||
`draft-wilaw-moq-scte35-event-timeline-00` §6.2 has SCTE-35 payloads treated.
|
||
- The policy's payload, text and rate bounds limit how much one principal can make every
|
||
receiver hold and check.
|
||
- A principal's events are linkable to one another, since one key signs them all. A room
|
||
that needs participation that cannot be linked across rooms gives principals a key per
|
||
room. Events persist in recordings, and a `decision` that redacts an event does not
|
||
remove it from the chains that hold it (§4.11).
|
||
- Confidentiality is the carrying transport's, and end-to-end encryption of tracks is
|
||
[confidentiality](confidentiality.md)'s; this extension signs events and does not
|
||
encrypt them.
|
||
|
||
## Appendix A — Registration request
|
||
|
||
For the "MSF Event Timeline Types" registry (MSF §14.2):
|
||
|
||
| Event Type | Description | Specification |
|
||
|---|---|---|
|
||
| `urn:wai:event:1` | WAI live event records (`WEV1`), one per record, in `data.wev1` | this extension |
|