Main gained wai.live.compose (#296), which edits the same lines: SPEC §5's live-records list and its paragraph, REGISTRY's list, wire-identifier table and magics, the registry's capability constants, statuses and wire rows, Cargo features, binaries and tests, the module list, the live-conformance README, the int_kernels_vectors reason and the independent-verifiers job. Both are kept, compose before recording; the two CI steps stay separate. capabilities.json is regenerated by wai registry export. Co-Authored-By: Claude Opus 5.5 <[email protected]>
48 KiB
WAI Extension: Live Rooms
Status: Draft. Registers the record
wai.live.room: the signed room descriptor that fixes a room, its id, its clock and what each role may do; and the signed room grant under which one principal takes part, carrying the event policy live-events §5 holds it to. A grant is exchanged for another at a tick of the room clock, and revoked through the room authority's key log (jwp-receipts §7). Tracks are named in the MoQ namespace tuple; a slot lets a publisher of higher precedence take a virtual track over, and a standby take it on failover; the publish profiles fix codecs, layers and limits as data. A room verifier replays every principal's events under the grant in effect at each instant of the room clock. No new transport and no new token format: the grant maps onto existing token formats (§10). The reference implementation is thelive_roommodule ofwai-rs(featurelive_room); the corpus islive-conformance/rooms/, and its second implementation islive-conformance/live_rooms_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Drafts read for this revision:
draft-ietf-moq-transport-22(MOQT),draft-ietf-moq-msf-01(MSF),draft-ietf-moq-c4m-01anddraft-ietf-moq-privacy-pass-auth-03, all Internet-Drafts, none past Working Group Last Call; with RFC 9246, RFC 8392, and CTA-5007-B as the IANA "CBOR Web Token (CWT) Claims" registry lists its claims (keys 308 to 323). A section number of one of those drafts in this text is that revision's.
1. Scope
In scope: the room descriptor, its digest (the room id) and signature (§3); the room
grant, its checks, its attributes, how a receipt names it, and its exchange (§4);
revocation through the key log (§5); track naming (§6); precedence and redundancy over
slots, and the detail of every lifecycle kind of live-events §4.13 (§7); the publish
profiles (§8); room verification (§9); and the bindings onto token formats (§10).
Out of scope: a transport; how a principal obtains a grant; composition, which reads the grant's attributes (live-compose); the review workflow, of which this extension fixes only the policy's fields and their meaning (§3.6); and what a recording holds, which the recording policy constrains (§3.7) and later extensions define: a device's master recording is capture-binding's take.
Relationship to the core spec: wai.live.room is a record (SPEC §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
2. Terms
- Room authority: the Ed25519 key (RFC 8032) that signs a room's descriptor and its grants.
- Clock: the key, named by the descriptor, that stamps the room clock and authors the room's lifecycle events for the authority. It MAY be the authority's key.
- Room key: the authority's key or the clock's.
- Room clock, principal, event, chain: as in live-events §2.
- Grant in effect: for a present principal, the grant its latest
joinorgrant_exchangenamed (§9.3). - Slot: a virtual track the descriptor declares, which one live publisher stands behind at a time (§7.1).
- Name: 1 to 32 bytes of
a–z,0–9,_and-. A slot name is 1 to 32 bytes ofa–z,0–9and_. A token is 1 or more bytes of!to~(0x21–0x7E). - Kinds bitmap: 32 bytes in which kind k is set when bit k mod 8 of byte k div
8 is set, as live-events §5 writes
kinds.
3. The room descriptor
3.1 Layout
Integers are little-endian. A short field is a u8 length and that many bytes; a long field is a u16 length and that many bytes.
| field | size | meaning |
|---|---|---|
magic |
4 | WRD1 (ASCII) |
version |
1 | 1 in this revision |
authority |
32 | the room authority's Ed25519 key |
clock |
32 | the clock's Ed25519 key |
t0 |
8 | u64: the room's t0, in microseconds since 1970-01-01T00:00:00Z |
tick |
8 | u64: the room-clock tick, in microseconds, that a grant exchange takes effect on (§4.6) |
origin |
long | the origin whose key log lists the authority (§5); empty for none |
max_present |
4 | u32: the most principals present at once (§3.5) |
max_publishers |
2 | u16: the most principals publishing at once (§7.3) |
reval |
4 | u32: the re-validation interval, in seconds, a token of the room carries (§10.2); 0 for none |
ceiling |
65 | a WEP1 record (live-events §5): the event ceiling no grant exceeds (§3.5) |
roles |
1 + … | a u8 count, then each role: name (short), publish (u8), subscribe (u8), max_layer (u8), powers (u8), events (32, a kinds bitmap) (§3.3) |
slots |
1 + … | a u8 count, then each slot: name (short), media (u8), redundancy (u8), failover_ms (u32) (§7.1) |
profiles |
1 + … | a u8 count, then each publish profile (§8.1) |
review |
37 | kinds (32, a kinds bitmap), deadline_ms (u32), fallback (u8) (§3.6) |
recording |
12 + … | mode (u8), retention_days (u16), commit_interval_ms (u32), jitter_ms (u32), then a u8 count and that many 2-byte regions (§3.7) |
sig |
64 | §3.2 |
3.2 Digest, room id and signature
The digest of a descriptor is SHA-256 (FIPS 180-4) over its record from magic
through recording: every byte but sig. It is the room id, the 32 bytes every event
of the room names (live-events §3.1).
The signature is Ed25519 by authority over the 14 bytes wai:live-room (ASCII) and
0x01, followed by the bytes the digest covers. It is verified strictly, as
SPEC §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 Roles and capability sets
A role maps a name to a capability set:
publish: bit m − 1 for each media kind m the role may publish: 1audio, 2video, 3screen(§6.1).subscribe(0 or 1) andmax_layer: whether the role may subscribe, and the highest layer (§6.3) it may subscribe to; 0 when it may not subscribe.events: the event kinds it may send, a kinds bitmap within the ceiling'skinds.powers: bit 0 moderate (itsdecisionevents, live-events §4.11, are a moderator's), bit 1 direct (it may sendcontrolcommands other than recording), bit 2 compose (it may sign the room's composition descriptor and publish a composed track, live-compose §3.5, §7), bit 3 record (it may sendrecord_startandrecord_stop).
A role whose events permit decision has the moderate power; one whose events permit
control has direct or record. A grant narrows its role's set and never widens it
(§4.3).
3.4 Reading and field rules
A reader reads exactly the bytes it is given as one descriptor, in the order of §3.1, and refuses at the first of these that holds:
- Fewer than 4 bytes:
truncated. A magic other thanWRD1:malformed. No byte after the magic:truncated. A version other than 1:unknown_version. - The bytes end before a field the layout requires:
truncated. - Bytes after
sig:malformed.
It then holds the descriptor to these rules, and refuses one that breaks any of them as
descriptor_invalid:
tickis above 0;originis empty or a token of at most 255 bytes;max_presentandmax_publishersare above 0.ceilingis a policy live-events §5 reads, and it permits no lifecycle kind (0x10to0x1F).- There are 1 to 16 roles, each named by a name no other role has, with
subscribe0 or 1,publishsetting no bit above bit 2,powersnone above bit 3,max_layer0 whensubscribeis 0,eventswithin the ceiling'skinds, and the powers §3.3 requires of itsevents. - There are at most 16 slots, each named by a slot name no other slot has, of a media
kind of §6.1, with
redundancy1 andfailover_ms0, orredundancy2 andfailover_msabove 0. - There are at most 16 profiles, each meeting §8.1 and named by a name no other profile has. A profile whose name §8.2 registers is that registered profile, field for field.
- The review policy and the recording policy meet §3.6 and §3.7.
Then the signature (§3.2): bad_key or bad_signature.
3.5 Limits
max_present: ajointhat would make more principals present than this is refusedparticipant_limit(§7.2).max_publishers: §7.3.ceiling: no grant's policy exceeds it (§4.3 step 8): itsmax_payload,max_text_cp,max_eventsandmax_reactionsare at most the ceiling's, and itswindow_usandreaction_window_usat least the ceiling's, so every window of the ceiling's length holds no more events than the ceiling allows. The ceiling'skindsbound the roles'.
3.6 The review policy
kinds names the event kinds held for review; all zero, nothing is reviewed. An event of
a reviewed kind is shown by a sink only once a principal with the moderate power has sent
a decision on it (live-events §4.11), or once deadline_ms milliseconds of room clock
have passed after its t with none, when the fallback verdict applies: 1 allow, 2 deny.
With no reviewed kind, deadline_ms and fallback are 0; with one, the kinds are within
the ceiling's, deadline_ms is above 0, and fallback is 1 or 2. This revision fixes the
policy's fields and their meaning; the review workflow and its verification are a later
extension's.
3.7 The recording policy
mode: 0 the room is never recorded; 1 it is recorded from arecord_startcommand (live-events §4.9) of a principal with the record power; 2 it is always recorded.retention_days: the most days a recording is kept; 0 states no bound.commit_interval_msandjitter_ms: how often a device capturing a take commits to it in the room's events, and the jitter its timing is allowed (capture-binding §5, §7). Withmode0 both are 0. Otherwisecommit_interval_msis above 0, 2 000 (two seconds) by default, andjitter_msis at mostcommit_interval_ms, 500 by default.regions: the region pin, for data residency: ISO 3166-1 alpha-2 codes, each two capitalsA–Z, in ascending order, at most 16. A recording of the room, and the media and events a relay keeps of it beyond delivery, MUST be stored only within those regions; none means no pin. A take's upload receipts state where its master is stored (capture-binding §6.3), and a recording's manifest where the package is kept (live-recording §4.4); each is checked against the pin.
mode is at most 2, and commit_interval_ms and jitter_ms keep the rule above.
4. The room grant
4.1 Layout
| field | size | meaning |
|---|---|---|
magic |
4 | WRG1 (ASCII) |
version |
1 | 1 in this revision |
room |
32 | the room id |
principal |
32 | the Ed25519 key the grant is for |
prev |
32 | the digest of the grant this one is exchanged for (§4.6); 32 zero bytes for a first grant |
effective |
8 | u64: for an exchanged grant, the room-clock tick it takes effect at; 0 for a first grant |
nbf, exp |
8 each | u64: seconds since 1970-01-01T00:00:00Z, as RFC 8392's NumericDate: the grant is good from nbf until before exp |
role |
1 | the index of its role in the descriptor |
publish, subscribe, max_layer, powers |
1 each | its capability set (§3.3), within its role's |
precedence |
1 | its precedence on a slot (§7.1) |
once |
16 | a single-use nonce: when it is not zero, no two joins name grants with this once (§7.2); zero, the grant may be used again within its window |
viewer |
16 | the viewer id revocation names (§5); zero for none |
policy |
65 | a WEP1 record (live-events §5): the event kinds it may send, its payload and text bounds, and its rate windows |
joules_ceiling |
8 | u64: the energy its use may spend, in microjoules; 2^64 − 1 for no ceiling |
funds_ceiling |
8 | u64: the funds its use may spend, in minor units of currency; 2^64 − 1 for no ceiling |
currency |
3 | ISO 4217 alphabetic code; three zero bytes when there is no funds ceiling |
profiles |
1 + n | a u8 count, then the index in the descriptor of each publish profile it publishes with |
origins |
1 + … | a u8 count, then each delivery origin it is good for (short): a URI's scheme and authority, such as https://relay.example |
attrs |
long | its attributes (§4.4) |
sig |
64 | §4.2 |
4.2 Digest and signature
The digest of a grant is SHA-256 over its record from magic through attrs. The
signature is Ed25519 by the descriptor's authority over the 20 bytes
wai:live-room-grant (ASCII) and 0x01, followed by the bytes the digest covers, verified
strictly (SPEC §7 Signatures).
4.3 Checking the grants
A verifier is given a room's grants in a list. It checks each in the list's order and refuses at the first check that fails, naming the grant by its index:
- The record reads, as §3.4 steps 1 to 3 read a descriptor with
WRG1forWRD1:truncated,malformedorunknown_version. - Its signature verifies under the descriptor's
authority(§4.2):bad_signature. - When
previs not zero, it is the digest of a grant earlier in the list (exchange_unlinked), and that grant'sroomandprincipalare this one's (exchange_mismatch). - Its
roomis the room id:wrong_room. - Its fields keep these rules, and
grant_invalidrefuses it otherwise:expis abovenbf;- a first grant's
effectiveis 0, and an exchanged grant's a multiple of the descriptor'stick; rolenames a role;subscribeis 0 or 1;publishsets no bit above bit 2,powersnone above bit 3;max_layeris 0 whensubscribeis 0;policyis a policy live-events §5 reads, permits no lifecycle kind, and the powers §3.3 requires of its kinds are amongpowers;funds_ceilingis 2^64 − 1 exactly whencurrencyis three zero bytes, andcurrencyis otherwise three capitalsA–Z;- each entry of
profilesnames a profile of the descriptor, of a media kind the grant may publish, and no two name profiles of one media kind; - there are at most 8
origins, each a token; attrsmeets §4.4;- its digest is no earlier grant's.
exp−nbfis at most 1 209 600 seconds, fourteen days: otherwisegrant_too_long.- It is within its role (§3.3): its
publishandpowersset no bit its role's do not, it subscribes only where its role does, itsmax_layeris at most its role's, and its policy's kinds are within its role'sevents. Otherwisecaps_exceed_role. - Its policy is within the room's ceiling (§3.5): otherwise
grant_exceeds_room.
A grant is good at room-clock t when nbf ≤ ⌊(t0 + t) / 10^6⌋ < exp.
4.4 Attributes
attrs is a sequence of entries, each a key (a short field holding a slot name) and a
value (a long field holding UTF-8, RFC 3629). Keys are in strictly ascending byte order,
and the entries take at most 1024 bytes, not counting the field's own length. An entry
that runs past the field's end breaks the rule.
Attributes are for composition: live-compose lays a room out from
them. It reads featured (true or false), order (a decimal integer) and pip
(true or false), and gain and pan (decimal integers) for the mix, under the keys a
composition descriptor names, and gives each its syntax and its meaning (live-compose
§4.3); this revision checks only the form of the entries. Capture binding
(capture-binding §3) reads two more: subject, the principal a
device captures for, as 64 lowercase hexadecimal digits of its key; and minor (true
or false), whether the authority marks that principal a minor. Attributes are signed with the grant,
so a layout computed from them is computed from what the authority stated.
4.5 How a receipt names a grant
A receipt that names the grant it was carried under does so with the grant reference
WAI's receipts already carry, the members grant_hash, capability,
joule_ceiling_micro and funds_ceiling that a package receipt
(packaging §9) and a playlist receipt (http-delivery, Appendix H) write:
grant_hash is the grant's digest (§4.2), capability is wai.live.room,
joule_ceiling_micro is joules_ceiling (2^64 − 1 for no ceiling, as there), and
funds_ceiling is funds_ceiling, or none when that is 2^64 − 1. The reference carries
no currency; the grant it names does. Every object's receipt so names the grant it was
carried under, and the grant states the ceilings a meter holds that use to.
4.6 Grant exchange
A principal's grant is exchanged for a new grant that is chained to it: the new
grant's prev is the old grant's digest, and its room and principal are the old
one's (§4.3 step 3). The exchange takes effect at the new grant's effective, a tick of
the room clock, and is recorded as the clock's grant_exchange lifecycle event at that
t (§7.2). An exchange can widen what a principal may do (a viewer promoted to publish)
or narrow it, and only the authority can make one, since only it signs grants.
An exchange MUST NOT leave the principal publishing what its new grant does not let it publish: an authority that narrows a publisher's grant unpublishes first (§7.2).
5. Revocation through the key log
A room whose descriptor names an origin is revocable. Its authority's key is an entry
of that origin's key log (jwp-receipts §7.2) whose may_sign holds
the authority live/room (jwp-receipts §7.5). Revocation then goes through that log:
- A grant is revoked by adding its digest to the head's
revoked_modules. - A viewer is revoked by adding its tag, SHA-256 over the 17 bytes
wai:live-viewer(ASCII) and 0x01 followed by the 16-byteviewer, to the same list. Every grant naming that viewer is revoked with it, in every room whose descriptor names that origin. - The authority is revoked as any key is (jwp-receipts §7.6): with the key, every descriptor and grant it signed is refused.
The list is append-only (jwp-receipts §7.2 rule 6), so a revocation stays. A relay checks
a grant against the key status it holds (http-delivery §8.4) at
admission and at each re-validation (§10.2), so a grant revoked stops being honoured
within reval plus the head's status_max_age_s seconds.
A room verifier is given the head the caller reached for the descriptor's origin
(jwp-receipts §7.4 step 3): anchoring the origin and walking its log to the head are the
caller's, and the verifier is a function of the head it is given. When origin is
empty, no key log is read, a head given is not read, and nothing is revoked. Otherwise,
in this order:
- A head is given: otherwise
key_log_missing. - It is held for
origin: jwp-receipts §7.2 rules 1 to 3 hold for it. Otherwisekey_log_not_held. - It has an entry whose
keyis the descriptor'sauthorityand whosemay_signholdslive/room: otherwiseauthority_not_listed. - That entry's
statusis notrevoked: otherwiseauthority_revoked.
A grant whose digest, or whose viewer's tag, the head's revoked_modules lists is
revoked. A revocation refuses every use of the grant (§7.2): like a key's, it is not
bounded by a time, since a revoked grant may have been in another party's hands. An
authority that only wants a principal gone from a room removes it with leave (§7.2).
6. Track naming
6.1 The Full Track Name
A track of a room is named by a MOQT Full Track Name (MOQT §2.4.1):
- its Track Namespace is three fields:
- the room id, as 64 lowercase hexadecimal digits;
- the owner: the publishing principal's key as 64 lowercase hexadecimal digits,
or, for a slot (§7.1),
_followed by the slot's name; - the media kind:
audio(1),video(2) orscreen(3); oreventsfor the principal's events track (live-events §8.1);
- its Track Name is the layer (§6.3) in decimal, with no leading zero:
0,1, …; the events track's is0.
Every byte of these fields is one of a–z, 0–9 and _, so the bytes are their
own canonical form (MOQT §2.4.1) and their rendering in MOQT §8.8's format is themselves:
the fields joined by -, then -- and the name, as
<room>-<principal>-video--1. No field is empty and none begins with . (MOQT §2.4.3,
§8.7), and the whole is 64 + 64 + 6 + 3 bytes at most, within MOQT §8.7's limit.
The layer is the Track Name, and not a fourth namespace field, because moq-streaming-format §4.1 has the tracks of a staged set share one namespace. Namespace prefix matching (MOQT §2.4.2) on the first field reaches every track of a room; on the first two, every track of one publisher or slot.
6.2 The catalog
A publisher's MSF catalog entry for a track carries its namespace (MSF §5.2.2) as the
MOQT §8.8 rendering of the namespace, and its name (MSF §5.2.3) as the Track Name. The
codec, dimensions, frame rate, bitrate, sample rate and channels of the entry are the
track's profile layer's (§8.1). A track that carries WAI envelopes declares packaging
"wai" (moq-streaming-format §3), and the tracks of a staged
profile carry wai_staged with layer equal to the Track Name (moq-streaming-format
§4.1). An anchor's track (live-events §3.1) is SHA-256 over that rendering of the
namespace, one zero byte, and the Track Name.
6.3 Layers
A layer is an index from 0, the lowest. A profile's layering (§8.1) says what its
layers are:
single: one track, layer 0.simulcast: layer k is the profile's k-th layer, an independent encoding on a track of its own.staged: layer 0 is the base and layer k the k-th refinement (staged-delivery §1), each on a track of its own.svc: one track, layer 0, carries every layer of the profile's scalabilitymode, named as the W3C "Scalable Video Coding (SVC) Extension for WebRTC" names them (L1T3,S3T3). A relay that forwards it to a subscriber whosemax_layeris m forwards the spatial layers 0 to m alone.
A subscriber whose grant's max_layer is m subscribes to the tracks named 0 to m
(§10.2). A layer_request command (live-events §4.9) asks a publisher for a layer; it
grants nothing.
7. Precedence and redundancy
7.1 Slots, takeover and failover
A slot is a virtual track the descriptor declares: a name, a media kind, a
redundancy of 1 or 2, and a failover_ms. Subscribers subscribe to the slot's tracks
(§6.1, owner _<name>), and which principal publishes behind them can change without
any subscriber subscribing again: a relay switches the source and the subscription stands.
A slot has a live publisher, and, when its redundancy is 2, a standby.
- Takeover. A principal whose grant in effect has a higher
precedencethan the live publisher's replaces it: the clock records atakeover, the new publisher goes live, and the replaced one leaves the slot. A takeover by a precedence not higher is refusedtakeover_not_higher. - Redundancy. With
redundancy2, two publishers are bound to the slot: the first to publish into it is live, the second its standby. A third is refusedslot_full, as is a second whereredundancyis 1. - Failover. When the live publisher's media stops, the clock records a
failoverto the standby. The rule: the standby goes live only oncefailover_msmilliseconds of room clock have passed since the live publisher's last object reached the relay, which the event states aslast_seen; earlier is refusedfailover_early. A failover to a principal that is not the standby is refusedfailover_not_standby. The failed publisher leaves the slot, and can publish into it again as the standby.
A standby whose live publisher unpublishes, or leaves the room, goes live with no event of
its own: its going live follows from the unpublish or leave.
7.2 Lifecycle detail
The lifecycle events of live-events §4.13 are authored by a room key. Their payload is the
subject, the principal the event is about, then detail, which this section defines
for each kind. A slot field is a short field holding a slot name or, where the
table allows, nothing (the subject's own track). media is a media kind of §6.1.
| kind | detail |
|---|---|
join |
grant (32): the digest of the grant the subject takes part under |
leave |
reason (u8): 1 left, 2 removed, 3 its grant ended, 4 connection lost |
publish |
slot (empty for an own track), media (u8), profile (u8, the index of the profile in the descriptor), layers (u8, at least 1) |
unpublish |
slot (empty for an own track), media (u8) |
grant_exchange |
old (32), new (32): the grants' digests |
takeover |
slot (not empty), media (u8), profile (u8), layers (u8, at least 1), previous (32): the live publisher replaced |
failover |
slot (not empty), media (u8), failed (32): the live publisher that stopped, last_seen (u64): the room clock at its last object |
A detail that does not read, with bytes after its last field, or with a value the table
does not allow, is lifecycle_malformed. Each event is then checked against the room
state (§9.3) at its t, in the order listed, and applied:
join: the grant is one the verifier holds (grant_unknown); itsprincipalis the subject (grant_wrong_principal); the subject is not present (already_present); the grant is not revoked (§5,grant_revoked); a grant whoseonceis not zero names aonceno earlierjoinnamed (grant_reused); the grant is good att(outside_grant_window); and the room holds fewer thanmax_presentprincipals (participant_limit). The subject is present, under that grant.leave: the subject is present (not_present). It is not present any more, its own tracks are unpublished, and it leaves every slot; a standby whose live publisher leaves goes live.publish: the subject is present (not_present); aslotnamed is a slot of the descriptor of that media kind (slot_unknown); the grant in effect may publish the media kind (publish_not_permitted), and itsprofilesholdprofile, a profile of that media kind with at leastlayerslayers (profile_not_granted); the subject does not already publish that own track or stand in that slot (already_published); the slot has room (§7.1,slot_full); and then at mostmax_publishersprincipals publish (§7.3,publisher_limit). An own track is published withlayerstracks; a slot's publisher is live if the slot has none, and its standby otherwise.unpublish: the subject is present (not_present); aslotnamed is a slot of the descriptor of that media kind (slot_unknown); the subject publishes that own track or stands in that slot (not_published). It is unpublished; a standby whose live publisher unpublishes goes live.grant_exchange: the subject is present (not_present);oldis its grant in effect (exchange_not_current);newis a grant the verifier holds (grant_unknown) whoseprevisold(exchange_unlinked);tisnew'seffective(exchange_off_tick);newis not revoked (grant_revoked) and is good att(outside_grant_window); and for everything the subject publishes, its own tracks by media kind and then its places in slots in the descriptor's order,newmay publish the media kind (publish_not_permitted) and gives the profile (profile_not_granted).newis then the grant in effect.takeover: the subject is present (not_present); the slot is the descriptor's, of that media kind (slot_unknown);previousis its live publisher (not_published); the grant in effect may publish the media kind and gives the profile with at leastlayerslayers (publish_not_permitted,profile_not_granted); the subject is notprevious, and its grant'sprecedenceis higher than the grant in effect ofprevious(takeover_not_higher); and then at mostmax_publishersprincipals publish (publisher_limit). The subject is live, withlayerslayers;previousleaves the slot, and the subject is no longer its standby.failover: the subject is present (not_present); the slot is the descriptor's, of that media kind (slot_unknown);failedis its live publisher (not_published); the subject is its standby (failover_not_standby);last_seenis at mostt, andt−last_seenis at leastfailover_ms× 1000 (failover_early). The standby is live, andfailedleaves the slot.
7.3 Publishers
A principal publishes while it has an own track published or stands in a slot, live
or standby. At most max_publishers principals publish at once; a publish or a
takeover after which more would is refused publisher_limit.
8. Publish profiles
8.1 The profile record
A profile fixes, as data, what a publisher sends on one media kind: its codecs, its layers and their limits, and its audio processing.
| field | size | meaning |
|---|---|---|
name |
short | the profile's name |
media |
1 | its media kind (§6.1) |
codecs |
1 + … | a u8 count (1 to 4), then each codecs value (RFC 6381) as a short field holding a token of at most 64 bytes, in the order a publisher prefers them |
layering |
1 | 0 single, 1 simulcast, 2 svc, 3 staged (§6.3) |
mode |
short | for svc, the scalability mode (§6.3): 1 to 16 of A–Z, 0–9 and _; otherwise empty |
layers |
1 + 14 × n | a u8 count (1 to 8; exactly 1 for single), then each layer: width, height, fps (u16 each), min_kbps, max_kbps (u32 each) |
sample_rate |
4 | u32: audio's sample rate in Hz; 0 for video |
channels |
1 | audio's channel count, 1 to 8; 0 for video |
processing |
1 | audio's processing: bit 0 echo cancellation, bit 1 noise suppression, bit 2 automatic gain control, bit 3 discontinuous transmission (DTX); 0 for video |
Each layer's max_kbps is above 0 and min_kbps at most max_kbps: min_kbps is a
floor a publisher SHOULD NOT send below while the network allows, and max_kbps a
ceiling it MUST NOT exceed. An audio layer has width, height and fps 0, and an
audio profile a sample_rate above 0, 1 to 8 channels, and processing setting no bit
above bit 3; a video or screen layer has all three above 0. A publisher publishing under a
profile uses one of its codecs, its layering and its layers, and turns on exactly the
processing it names.
8.2 Registered profiles
| name | media | codecs | layering | layers (width × height @ fps, kbps) | audio |
|---|---|---|---|---|---|
speech |
audio | opus |
single | 16–64 | 48 000 Hz, 1 channel; echo cancellation, noise suppression, gain control and DTX on |
music |
audio | opus |
single | 128–510 | 48 000 Hz, 2 channels (stereo, fullband); no echo cancellation, noise suppression, gain control or DTX |
screen |
screen | av01.0.08M.08, avc1.640028 |
svc, L1T3 |
1920 × 1080 @ 15, 300–2500 | — |
camera-720 |
video | av01.0.05M.08, avc1.64001F |
simulcast | 640 × 360 @ 30, 150–500; 1280 × 720 @ 30, 500–1500 | — |
camera-1080 |
video | av01.0.08M.08, avc1.640028 |
simulcast | 640 × 360 @ 30, 150–500; 1280 × 720 @ 30, 500–1500; 1920 × 1080 @ 30, 1500–4000 | — |
opus is RFC 6716 audio, av01 AV1 and avc1 ITU-T H.264. music's 128 kbps floor
and its processing turned off keep what an instrument plays: echo cancellation, noise
suppression and gain control are built for speech and remove parts of music, and DTX
stops sending in what it takes for silence. screen drops frames rather than resolution
(L1T3, temporal layers alone), since text on a screen does not survive scaling. A room
lists the profiles it allows in its descriptor, under these names with these values or
under names of its own; wai_live_room_vectors writes each registered profile's record
into the corpus, so an implementation checks its table against them.
9. Room verification
9.1 Inputs and steps
A room verifier is given a descriptor, a list of grants, the head of the authority's key
log (or none), and the room's chains: a list of lists of WEV1 records, each list one
principal's records in its stream order (live-events §6). It runs these steps and stops
at the first refusal, which it reports with the reason and where:
- The descriptor (§3.4, §3.2): its refusal, at
descriptor. - The key log (§5): its refusal, at
key_log. - The grants (§4.3): the refusal, at the grant's index.
- The records, in the merged order (§9.2), each by §9.2: the refusal, at the record's chain and its index in that chain.
- The outcome (§9.4).
9.2 The merged order and each record
The merged order takes the records of every chain into one sequence, keeping each
chain's own order: repeatedly, of the chains with records left, it takes the next record
of the one whose next record has the least key. A record's key is its t, then 0 for a
room key's record and 1 for any other, then its chain's index; a record that live-events
§3.3 does not read has the key (0, 0, its chain's index). Lifecycle events therefore take
effect before the events of other principals at the same t.
One live-events verifier (live-events §6) verifies every record, for the room id. Before it is offered a record, the verifier is given the policy the record is held to. For each record, in the merged order:
- live-events §6.1 steps 1 to 4: the record reads, its kind is defined, its
roomis the room id, its signature verifies. Otherwise that section's reason. - A record whose digest is that of a record accepted before is a duplicate: it is offered under the policy it was accepted under, which live-events §6.2 step 1 ignores, and it changes nothing.
- A room key's record is offered under the room authority's policy (live-events §5),
and refused with live-events' reason. A lifecycle event then has its
detailread and is applied (§7.2). - Any other principal's record: the principal is present (
not_present), its grant in effect is good at the record'st(outside_grant_window), and the record is offered under that grant'spolicyand refused with live-events' reason: a kind the grant does not permit iskind_not_permitted, a payload over its boundpayload_too_large, and an event past its rate windowrate_exceeded. A participant's lifecycle event is thereforekind_not_permitted, as no grant permits one (§4.3 step 5). - A
controlevent of such a principal needs a power of its grant in effect (§3.3):record_startandrecord_stopthe record power, any other command the direct power. Otherwisepower_not_granted.
Each principal's effective policy at t is so the policy of its grant in effect at t,
and the room keys' is the authority's. Each record is reported with the grant it was
verified under, or none for a room key's.
9.3 Room state
The room state is replayed from the lifecycle events alone, in the merged order:
- present: each present principal and its grant in effect;
- published: each own track, by principal and media kind, with its profile and its number of layers;
- slots: each slot's live publisher and standby, and the live publisher's precedence: the slot's current precedence holder;
- active grants: each change of a principal's grant in effect (a
joinor agrant_exchange), with itst, in the order they took effect.
The tracks a subscriber of the room can subscribe to are named by §6.1: each own track's layers, and each slot's live publisher's.
9.4 Outcomes
- refuse: the reason and where (§9.1). Step 5's
tally_mismatch(live-events §7) is at the record of itspoll_close. - gap: no record refused, and the live-events verifier does not hold every seq of
every chain (live-events §6.2): the gaps, under the code
delivery_gap. The room state is the replay of what it holds, and a tally it computes is unchecked. - accept: no record refused, no gap, and every stated tally equal to the replay.
10. Bindings
A grant is WAI's own signed record. A relay, a CDN or an origin that authorises with an existing token format is given a token that maps the grant's claims onto that format's; WAI defines no second token format. A claim the grant has and the format does not stays in the grant, which the relay holds beside the token (§10.1).
10.1 Common Access Token (CTA-5007-B)
A Common Access Token is a CWT (RFC 8392). Its claims are named here by name and by the key the IANA CWT Claims registry gives them; the value syntax of each CAT claim is CTA-5007-B's.
| grant claim | CWT / CAT claim | mapping |
|---|---|---|
| the authority | iss (1) |
the authority's signer_id (jwp-receipts §7.1): origin-bound, with its key-log entry's id, where origin is set; its did:key otherwise |
principal |
sub (2) |
the principal's did:key |
room |
aud (3) |
the room id, 64 lowercase hexadecimal digits |
nbf, exp |
nbf (5), exp (4) |
the same NumericDates |
once |
cti (7), catreplay (308) |
a once not zero is the token's cti, with catreplay set to prohibit replay; zero, catreplay permits it |
origins |
catu (312), cath (315) |
each origin as a URI match on scheme and host; a deployment that matches on a request header carries it in cath |
| the token's renewal | catr (323) |
a renewed token's exp is never later than the grant's; an exchange (§4.6) issues a new token |
principal, as a key binding |
catdpop (321), cnf (8) |
proof of possession by the principal's key; cattpk (319) binds a TLS key, and applies only where the client's TLS key is the principal's |
| client network, geography | catnip (311), catgeoiso3166 (316) |
no grant claim: a deployment MAY add them; a room's restriction by place is a sink-evaluated predicate (unidirectional-delivery §2) |
caps, max_layer, the slots |
moqt |
§10.2 |
reval (descriptor) |
moqt-reval |
§10.2 |
role, precedence, policy, profiles, attrs, joules_ceiling, funds_ceiling, viewer |
none | WAI's alone; the relay holds the grant, and a deployment that links the two inside the token carries the grant's digest in a private-use claim (RFC 8392 §9.1) |
catv (310) is the version CTA-5007-B gives; catpor (309), catm (313), catalpn
(314), catgeocoord (317), catgeoalt (318), catif (322) and catifdata (320) have
no grant claim. This revision implements no CAT encoding: the crate has no CBOR or COSE,
and the claims above are mapped by name and key.
10.2 MOQT: the moqt claim (draft-ietf-moq-c4m-01)
A token for MOQT carries the moqt claim, an array of scopes, each [actions, namespace matches, track match] (draft-ietf-moq-c4m-01 §2.1). Every action is blocked unless a
scope enables it (§2 there), so the scopes a grant maps onto are all it can do. With the
room id R and the principal P as 64 lowercase hexadecimal digits, the scopes are, in
this order:
[[0]]:CLIENT_SETUP.- For each media kind the grant may publish, in ascending order of its code, with m its
name (§6.1):
[[2, 6], [R, P, m, nil]](PUBLISH_NAMESPACEandPUBLISHof its own tracks), then, for each slot of the descriptor of media kind m, in the descriptor's order,[[6], [R, "_" + slot, m, nil]]. - When the grant's policy permits any kind:
[[2, 6], [R, P, "events", nil], "0"]. - When the grant may subscribe:
[[3], [R]](SUBSCRIBE_NAMESPACE), then for each layer l from 0 tomax_layer,[[4, 7, 8], [R], "l"](SUBSCRIBE,FETCH,TRACK_STATUSof any track of the room named l).
Each namespace or track match is a byte string, an exact match; nil ends a namespace
match at that length. The token's moqt-reval is the descriptor's reval: a relay
re-validates the token, and with it the grant's revocation (§5), at least that often, and
stops what the grant authorised when it fails (§2.2 there). A reval of 0 asks for no
re-validation, and then only expiry ends the token. A grant exchange (§4.6) issues a new
token at the tick. The corpus states each accepted vector's scopes per grant, written as
JSON: an exact match as a string and nil as null.
10.3 URI Signing (RFC 9246)
Over HTTP, a room's delivery (an LL-HLS or DASH rendition of its tracks) is authorised with a URI Signing JWT:
| grant claim | URI Signing claim | mapping |
|---|---|---|
| the authority | iss (§2.1.1 there) |
as §10.1 |
principal |
sub (§2.1.2) |
the principal's did:key, as a JWE as that section requires |
nbf, exp |
nbf (§2.1.5), exp (§2.1.4) |
the same, with no leeway |
once |
jti (§2.1.7) |
a once not zero, so a CDN that keeps token ids refuses a replay |
origins, room, max_layer |
cdniuc (§2.1.11) |
a regex: container (§2.1.15.2) matching the grant's origins, the room's path, and the renditions of layers 0 to max_layer |
| client network | cdniip (§2.1.10) |
no grant claim; a deployment MAY add it |
| renewal | cdniets, cdnistt, cdnistd (§2.1.12–§2.1.14) |
a renewed token's exp is never later than the grant's |
| the rest | none | as §10.1 |
cdniv is 1.
10.4 Privacy Pass (draft-ietf-moq-privacy-pass-auth-03)
A viewer that need not be identified is admitted by a Privacy Pass token instead of a
grant. The token's challenge carries the scope in its origin_info
(MoQAuthorizationInfo, §3.2.4 there), and its presentations are unlinkable to one
another (§2.2 there).
| grant claim | Privacy Pass field | mapping |
|---|---|---|
room, subscribe, max_layer |
origin_info scopes |
the subscribing scopes of §10.2 step 4, each a MoQAuthScope whose namespace_match is MATCH_PREFIX on [R] and whose track_name_match is MATCH_EXACT on the layer |
nbf, exp |
redemption_context (§3.2.1 there) |
a hash of the time window the token is good for |
once |
the token's nonce | every token is single-use (§3.3.2 there) |
principal, viewer |
none | the token names no one; revocation is of the issuer's key, not of a viewer |
policy, publish caps, ceilings, attrs |
none | a Privacy Pass viewer sends no event and publishes nothing |
A viewer that is to send events holds a grant for a key of its own; a key per room keeps its events unlinkable across rooms (live-events §11).
11. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
descriptor_invalid |
§3.4 |
key_log_missing |
§5 step 1 |
key_log_not_held |
§5 step 2 |
authority_not_listed |
§5 step 3 |
authority_revoked |
§5 step 4 |
grant_invalid |
§4.3 step 5 |
grant_too_long |
§4.3 step 6 |
caps_exceed_role |
§4.3 step 7 |
grant_exceeds_room |
§4.3 step 8 |
exchange_unlinked |
§4.3 step 3, §7.2 grant_exchange |
exchange_mismatch |
§4.3 step 3 |
lifecycle_malformed |
§7.2 |
grant_unknown |
§7.2 join, grant_exchange |
grant_wrong_principal |
§7.2 join |
grant_revoked |
§5, §7.2 join, grant_exchange |
grant_reused |
§7.2 join |
already_present |
§7.2 join |
not_present |
§7.2, §9.2 step 4 |
participant_limit |
§3.5, §7.2 join |
outside_grant_window |
§4.3, §7.2, §9.2 step 4 |
power_not_granted |
§9.2 step 5 |
publish_not_permitted |
§7.2 publish, grant_exchange, takeover |
profile_not_granted |
§7.2 publish, grant_exchange, takeover |
already_published |
§7.2 publish |
not_published |
§7.2 unpublish, takeover, failover |
slot_unknown |
§7.2 |
slot_full |
§7.1, §7.2 publish |
publisher_limit |
§7.3 |
exchange_not_current |
§7.2 grant_exchange |
exchange_off_tick |
§4.6, §7.2 grant_exchange |
takeover_not_higher |
§7.1, §7.2 takeover |
failover_not_standby |
§7.1, §7.2 failover |
failover_early |
§7.1, §7.2 failover |
Of live-events §9's codes, a room verifier also raises truncated, malformed and
unknown_version when a descriptor or a grant does not read (§3.4, §4.3); bad_key and
bad_signature for their signatures (§3.2, §4.2); wrong_room for a grant of another
room (§4.3 step 4); every code live-events §6 raises for a record (§9.2); tally_mismatch
(§9.4); and delivery_gap for a gap, which is not a refusal.
12. Conformance
live-conformance/rooms/ holds the corpus, in one file,
vectors.json, written from fixed seeds by wai_live_room_vectors gen. It has the
records of each registered profile (§8.2), and vectors: a descriptor, grants, a
key-log head or none, and chains, with the outcome §9.4 gives them.
- Accepted: a lesson-style room (a director, a performer, an audience, and the
director's remote record control of the performer, under a key log whose head revokes
a grant and a viewer the room never admits); a broadcast room (one publisher, many
viewers, a moderator); a takeover; a redundant pair with failover; and a grant
exchange that promotes a viewer to a publisher. Each states the room id, every grant's
digest, the merged order with the grant each record was verified under, the room state
(§9.3) with the names of the tracks a subscriber can reach (§6.1), the tally
(live-events §7), and each grant's
moqtscopes and re-validation interval (§10.2). - Gap: a chain with a record not delivered.
- Refused: each reason of §11 at least once, each vector a control of one defect, and the codes of live-events §9 that a descriptor, a grant or a record of a room raises.
A conforming verifier gives every vector the outcome, the reason, the place, the gaps and
the state, order, tally and scopes the corpus states. wai_live_room_vectors verify and
the test tests/live_room.rs check that the file regenerates byte for byte, that wai-rs
gives every vector its outcome, and that §11 lists exactly the reference's codes.
live-conformance/live_rooms_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), verifies each event with the live-events
corpus's own second implementation (live_events_verify.py), and gives every vector the
same outcome.
13. Security and privacy considerations
- Every claim a principal acts under is signed by the room authority, and every event a principal sends by the principal; a relay can drop either but alter neither, and a room verifier replays the room from them alone.
tis its author's claim (live-events §11). The clock's lifecycle events fix when a principal was present and under which grant; a participant'stoutside its grant's window or outside its presence is refused. A clock that lies about time is the room's own key lying, which its signature makes evidence of.- A grant is a bearer capability only together with the principal's key: every event
under it is signed by that key, so a copied grant lets no one else send. A token mapped
from it (§10) is a bearer token, which is why the mapping binds it to the principal's
key where the format allows (
catdpop,cnf) and why its window is fourteen days at most. - Revocation is through the key log, so it is as available as the log (jwp-receipts §7.8): a relay with no fresh status for the authority's origin fails closed.
- A viewer's tag is a hash of an id the authority assigns; the key log publishes the tag, not the id, and an authority SHOULD assign ids that are not otherwise meaningful.
- A grant names its principal, so a room's grants link that principal's participation; a principal that needs participation unlinkable across rooms uses a key per room, and a viewer that needs no identity is admitted by Privacy Pass (§10.4).
- The region pin (§3.7) binds where a recording is kept; checking a recording's receipt against it is the recording extension's.