Some checks failed
WAI conformance / does this change touch it (push) Has been skipped
WAI conformance / cargo-class corpora · linux (push) Has been skipped
WAI conformance / cargo-class corpora · linux-arm64 (push) Has been skipped
WAI conformance / cargo-class corpora · macos (push) Has been skipped
WAI conformance / cargo-class corpora · windows (push) Has been skipped
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (push) Has been skipped
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (push) Has been skipped
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (push) Has been skipped
WAI conformance / v2 vocabulary · manifests validate as stated (push) Has been skipped
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (push) Has been skipped
WAI conformance / independent verifiers · every verifier step, each corpus's Rust side (push) Has been skipped
WAI conformance / each feature of the independent verifiers' steps compiles alone (push) Has been skipped
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux-arm64 (push) Has been skipped
WAI conformance / cross-arch byte-equality · macos (push) Has been skipped
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (push) Has been skipped
WAI conformance / C2PA manifest emission · signed, read back, validated (push) Has been skipped
WAI conformance / independent verifiers · each needs= is what a run without packages shows (nightly) (push) Has been skipped
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 24s
WAI examples / WAI examples · does this change touch them (push) Successful in 22s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 29s
joule-moq-agent AI participants / joule-moq-agent · does this change touch it (push) Successful in 30s
WAI operation catalogue / does this change touch it (push) Successful in 30s
WAI conformance / a push to main · corpus provenance and sidecars, signer ids, the registry export (push) Successful in 58s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 49s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 1m22s
wai web player plugins / the browser run's room compiles · linux (push) Successful in 1m25s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 2m16s
wai-app and Create / wai-app · does this change touch it (push) Successful in 2m17s
WAI Studio / does this change touch it (push) Successful in 2m18s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 47s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m9s
wai web player plugins / the plugins' core under Node · linux (push) Successful in 4m14s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 5m16s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 8m38s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 10m5s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 8m40s
wai web player plugins / the four players in Chromium, Firefox and WebKit · macos (push) Failing after 13m11s
WAI examples / WAI examples · portable · linux (push) Successful in 14m41s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 16m6s
WAI Studio / Studio in WebKit · linux (push) Successful in 16m32s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 22m5s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 20m38s
wai release binaries build on Windows / the seven release binaries · windows (push) Successful in 21m12s
joule-code conformance gate / joule-moq-relay, every target (push) Successful in 14m55s
joule-moq-agent AI participants / joule-moq-agent, every target (push) Successful in 5m8s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 10m47s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 13m38s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 11m52s
joule-code conformance gate / joule-moq-ingest, every target (push) Successful in 35m47s
The room service granted the role injector only in a room with ads, to a key [ads] injectors names, so a contribution's captions in a room without ads had no grant to be signed under, though no text ties an injector to ads: cue-injection §2 has an injector be a gateway with a principal of the room and a grant permitting cue and meta (and caption where it signs a contribution's captions), and essence-contribution §10.6 has the room's injector principal sign them in the room the contribution publishes into. In a room without ads, a key [room] injectors names (at most 16) now takes the same grant (epoch::injector_policy: meta, cue and caption, no media); the participant's role then carries those kinds, so that the grant is within its role (live-rooms §4.3), and the clock records the injector's join when its events track appears. A room with ads keeps its one list, [ads] injectors, whose cues its stitcher reads; the two lists are refused together, as is [room] injectors in an end-to-end encrypted room, a watch room, a world room, a remote desktop room, a conversation or a call. No text changes. tests/ads_room.rs an_injector_is_granted_in_a_room_without_ads: a key the list does not name is refused not_an_injector; the grant permits exactly the three kinds and no media, within its role and the room's ceiling; the clock records the injector present under it; the room verifies (live-rooms §9) with a caption's partial and its final in the injector's chain, signed as the gateway's injector signs them. Planted and caught: the service before (the grant refused 403) and the participant's role as it was (the grant not within it). lib.rs room_injectors_are_read_and_bounded holds the configuration. D108 corrected in place. Co-Authored-By: Claude Opus 5.5 <[email protected]>
602 lines
51 KiB
Markdown
602 lines
51 KiB
Markdown
# joule-moq-room
|
|
|
|
A room service for WAI live rooms (`wai/extensions/live-rooms.md`) over
|
|
`joule-moq-relay`. Its binary is `wai-room` (the crate keeps its name). It holds one room's
|
|
authority and clock keys and:
|
|
|
|
- **issues grants.** A visitor's page makes an Ed25519 key and asks for a grant for it
|
|
(`POST /grant`). The service signs a short-lived `WRG1` grant: a publisher's (its own
|
|
audio and video, one of the room's places) or a viewer's (the room to watch). A grant is
|
|
public, so it is useless without its key: the relay, configured with the authority's key
|
|
(`[[auth.authorities]]`), honours it only beside a proof of possession over the relay's
|
|
own challenge (§10.5), which only the page that holds the key can make. Grants are
|
|
bounded per client address and overall, and publishers per room. Every role, and so
|
|
every grant, holds the seek power (live-rooms §3.3, bit 4): a visitor may fetch a track
|
|
below its live edge (§10.6), from the relay's cache or its DVR window, as a late joiner
|
|
does for the groups before it joined. A relay refuses such a fetch to a grant without it.
|
|
- **authors the lifecycle** (§7.2). The service's clock is a session on the relay. It
|
|
watches each publisher's video track (a SUBSCRIBE that asks for no objects): when the
|
|
track appears the clock signs `join` and `publish` for the publisher on its events track
|
|
(live-events §8.1), and when the relay ends the track (the publisher's session closed) it
|
|
signs `leave`. Pages author none of it. The events track is published with the room's
|
|
first grant, before anything is signed on it, so a viewer who enters a room nobody has
|
|
joined subscribes to a track that is there.
|
|
- **serves the room.** `GET /room` gives the signed descriptor, the composition descriptor
|
|
and the publishers' grants, which a page verifies the room with (live-rooms §9).
|
|
|
|
- **admits AI captioners** (`[ai]`, WAI live-rooms §4.4, `src/ai.rs`). With `captioners` above
|
|
0, every room's descriptor has a role `captioner` holding the ai power (§3.3): no media to
|
|
publish, the room to subscribe to, the seek power, and its kinds a `caption` and its annotation
|
|
(timed-text §4.2). An AI participant asks for its grant as any participant does (`POST /grant`,
|
|
the role `captioner`, `ai` the pin of the model that acts for it); the grant carries the pin in
|
|
its attribute `ai`, is listed with the room's, and holds no publisher's place, and the clock
|
|
records the captioner joining when its events track appears and leaving when it ends.
|
|
`models` names the pins a captioner may name (`403` `ai_model_not_admitted` otherwise), and
|
|
`captioners` how many hold a place at once (`409` `full`). Since a role of the descriptor holds
|
|
the ai power, `ai` asked under a role without it is refused, `403` `ai_role_not_permitted`, as
|
|
every verifier of the room refuses such a grant (§4.3 step 9); in a room with co-performance
|
|
that takes AI participants, the publishers' roles hold the power too. Not an end-to-end
|
|
encrypted room, a watch room, a world room, a remote desktop room, a conversation or a call. The
|
|
captioner itself is
|
|
`joule-moq-agent`'s (`wai-room-agent caption`).
|
|
- **grants injectors** (`[room] injectors`, WAI cue-injection §2, essence-contribution §10.6;
|
|
decision D108). In a room without ads, a key the list names (64 hexadecimal digits each, at
|
|
most 16) takes a grant of the role `injector` (`POST /grant`): the participant's role with no
|
|
media to publish and the kinds `meta`, `cue` and `caption` alone, the kinds a gateway's
|
|
injector signs, a contribution's captions with their annotations and windows and its cues. The
|
|
participant's role then carries those kinds, so that the grant is within its role (live-rooms
|
|
§4.3). The grant is listed with the room's and holds no publisher's place; the clock records
|
|
the injector joining when its events track appears. Any other key is refused, `403`
|
|
`not_an_injector`. A room with ads names its injectors in `[ads] injectors` instead (below),
|
|
whose cues its stitcher reads, and the two lists are not given together. Not an end-to-end
|
|
encrypted room, a watch room, a world room, a remote desktop room, a conversation or a call.
|
|
- **keeps remote desktop rooms** (`[remote]`, WAI `extensions/remote-desktop.md`). Every room
|
|
is then one host's screen shared into it, its descriptor's roles a host (it publishes the
|
|
`screen` kind and holds the direct power), a controller and a viewer, and every grant's policy
|
|
permits `meta`, so that each principal sends the extension's statements. The host's grant
|
|
carries `remote_host`; a controller's the control power, `remote_control` = `control`; a
|
|
viewer's neither. The host is the key `host` names or, when it names none, the first key that
|
|
presents an invitation the room's authority signed (`wai-room invite`), to which the
|
|
invitation is then bound. Who may ask for a controller's grant is `allow`: `all`, `listed`
|
|
(the keys `controllers` lists) or `none`. The host hands control over by a grant exchange
|
|
(`POST /remote/control`, its grant and a proof over the service's challenge), which takes
|
|
effect at a tick at least `lead_ms` ahead, so that every party holds the clock's
|
|
`grant_exchange` before the instant it governs. The clock watches the host's screen track (its
|
|
`join` and its `publish` of the screen when the track appears, its `leave` when it ends) and
|
|
every other principal's events track, and follows every principal's events: it takes each
|
|
record in its chain's order, under the policy of its author's grant in effect and after its
|
|
`join`, each statement read and held to who may sign it, and the host's screen statements to
|
|
§3.4 and to the age bound `max_age_ms`. It computes the host's floor in the merged order by
|
|
`wai-rs`'s §7.2 rule: a floor statement the rule refuses is kept, stated, and changes nothing,
|
|
as the host and a verifier have it (`GET /room`'s `remote.holder`). A principal whose
|
|
record is out of that order is cut there and removed (`leave` reason 2), and the host's agent
|
|
cuts it at the same record: the room the clock holds stays one a verifier accepts. The clock
|
|
bounds the records it holds of a session, per principal first. At `max_records_per_principal`
|
|
(100000 by default) it holds none of a principal's records past the bound, cuts the principal
|
|
there (`max_records_per_principal`) and removes it (`leave` reason 2), and the session goes on;
|
|
the host's agent reads the bound from `GET /room` and cuts the principal at the same record, so
|
|
the two hold the same records. The host has no bound of its own: its outcomes answer the
|
|
others' inputs. The default: a two-hour session at an average of ten input statements a second
|
|
is 72000 records, and a principal flooding at the policy's 120 events a second reaches the bound
|
|
in about 14 minutes, and is removed alone. `max_records` (1000000 by default) bounds the whole
|
|
session, the host's records among them, and is the safety net: ten times the bound per
|
|
principal, above a host and four controllers each at its bound (800000), so the bound per
|
|
principal bites first for any one principal. When a record would pass the total the clock holds
|
|
none past it and ends the session, every present principal's `leave` (reason 2) signed, the
|
|
host's last, and `GET /room`'s `remote.ended` states `max_records`; the session it holds stays
|
|
whole and verifies, and the next host starts a new room. It ends the session rather than refuse
|
|
inputs past the total: a record it did not hold would leave a hole in its author's chain, the
|
|
host would decide inputs the clock never held, and an input's refusal is the host's to state.
|
|
At the total the clock holds about a gigabyte. Key, text and clipboard actions reach the clock
|
|
sealed to the host (WAI remote-desktop §5.5): it holds and serves ciphertext, and no opening
|
|
key. A remote room is not also end-to-end encrypted, a watch room, a room with co-performance,
|
|
one admitted by age or a world room: the service refuses such a configuration. The relay holds
|
|
the host's screen to its key
|
|
with a door like the publishers' (`prefix = ["*", "*", "screen"]`).
|
|
|
|
- **keeps end-to-end encrypted rooms** (`[room] e2ee = true`, WAI
|
|
`extensions/confidentiality.md`). Each room then has a confidentiality record (`WRE1`) the
|
|
authority signs, and one MLS group whose only external sender is the room's clock: the clock
|
|
proposes the Add of each publisher it records joining and the Remove of each it records
|
|
leaving, and follows the group from its public messages, holding no key. The service is the
|
|
room's carrier of MLS messages (§5.1, §15.2): one log per room holding the clock's lifecycle
|
|
records, its proposals, and every GroupInfo, commit and Welcome a member posts, in the order
|
|
the clock took them; a post the clock refuses (§5.5) is not carried.
|
|
- **admits by age** (`[age]`, WAI `extensions/age-assurance.md` §5.6), as the Privacy Pass
|
|
origin of RFC 9577. Each room then has an age policy (§4) naming the configured issuers with
|
|
one key per threshold, and a rating (§3), a `room` claim, both signed by the authority. A grant
|
|
request without a token is answered `401` with a `PrivateToken` challenge for each key
|
|
(`WWW-Authenticate`, RFC 9577 §2.1.2): its redemption context HMAC-SHA-256 over the room and a
|
|
window of `token_window_s` under a key drawn at start (a restart turns outstanding challenges
|
|
away), its `origin_info` the service's server name (`origin`, else the request's `Host`). A
|
|
request with `Authorization: PrivateToken token="…"` has the token checked by `wai-rs`'s §5.2
|
|
against the room's age, and its grant states the token's threshold in `age` (§5.4), from which
|
|
the relay holds every track to the serving rule (§5.5). The nonces redeemed are kept for the
|
|
current window and the one before, at most `max_nonces` a window (past it, `503` and nothing
|
|
spent), and dropped as a window ages out. Each admission is counted by its window, issuer and
|
|
threshold, and nothing else of it is kept: `GET /age/receipt` gives the room's
|
|
`wai-age-admissions` receipt so far (§7), signed by the clock, every count under 50
|
|
suppressed, and with `receipts_out` each window's is appended there as it closes, and the
|
|
rest of a room's when it ends. The room's rating rides the clock's events track and the grant
|
|
answer's room, which a publisher's page carries on its tracks (§5.7). The clock's own session
|
|
proves both room keys in its SETUP (a `GRANT_PROOF` naming no grant, §5.5, over a challenge
|
|
the relay's SETUP gave on a first session, the relay reading a client's SETUP before it
|
|
sends its own), so the relay
|
|
serves it the room's rated tracks as the room's own service: it follows each publisher and,
|
|
with `[co]`, logs every performer's objects. Operating an issuer, or the attester that
|
|
establishes an age, is not the service's.
|
|
|
|
- **keeps co-performance** (`[co]`, WAI `extensions/co-performance.md`). With
|
|
`audience_input` above 0, every viewer's grant carries the audience-input power (the
|
|
attribute `audience_input`, the most inputs it may send to one selection window) and may send
|
|
`meta` events and reactions, and the viewers' grants are listed with the room (`co.audience`)
|
|
so a page verifies their inputs; with `latency_budget_ms` above 0, every publisher's grant
|
|
carries that budget. Either gives the room a co-performance policy (`WPP1`) the authority
|
|
signs (`co.policy`), its publishers `meta` events (their intents), a `lead` role with the
|
|
direct power for the first publisher (`lead`), and, with `ai`, a grant marked as an AI
|
|
participant's for a page that asks (`"ai": <model digest>`). The clock then keeps what only
|
|
it knows (`src/co.rs`, co-performance §8): it records each audience member joining when its
|
|
events track appears, holds the room as a verifier does (every member's chain, by FETCH
|
|
where it has a gap), logs each publisher's media objects with their arrival and their due
|
|
instant (`GET /co/objects`, the objects a verifier is given), and signs a mark (`WPM1`) for
|
|
each object out of turn under a schedule that marks or later than its budget, the
|
|
commitment (`WPK1`) and selection (`WPS1`) of each window once it closes, and the periodic
|
|
fidelity statement (`WPF1`). A page asks the clock for a turn schedule (`WPT1`), a handover
|
|
(`WPH1`) or a window (`WPW1`) over HTTP (`POST /co/...`), with its grant and a proof of
|
|
possession over a challenge the service issued (live-rooms §10.5.3); its powers decide what
|
|
it may ask. The relay holds the floor and the audience's inputs (its `[floor]`, which
|
|
`relay-lines` prints commented out).
|
|
|
|
- **keeps watch rooms** (`[room] watch_source`, WAI `extensions/co-watch.md`). Each room then
|
|
watches that file together: the authority signs a co-watch policy (`WXP1`) naming it, an
|
|
object sequence of one object, by the SHA-256 of its bytes and its duration, and the service
|
|
serves the bytes. A visitor enters the room's co-watch log by a proof of its grant's key over
|
|
a challenge the service issued, for the request it makes (live-rooms §10.5.3), and the clock
|
|
signs its `join`; every grant carries `watch_control`, the actions its principal may
|
|
control. The service is the room's carrier of its viewers' events: one log per room, holding
|
|
every record of the clock's chain and every control and presentation statement a viewer
|
|
posts, each carried only when a verifier would accept it (its chain, its signature, its
|
|
grant's policy and rate, and co-watch §5 and §7.2). Each window, the clock signs the skew
|
|
statements of co-watch §8.4 from what the log holds; and a viewer silent for four seconds, or
|
|
whose grant has ended, leaves. One that enters again before the clock has signed that `leave`
|
|
has it signed first, then its `join`, so that no `join` comes while it is present. A watch
|
|
room is never end-to-end encrypted (its source is served in the clear), admitted by age (its
|
|
source is served without a grant) or a room with co-performance: the service refuses such a
|
|
configuration.
|
|
|
|
- **has a library for playout timing** (the `tempo` module, WAI co-watch §15,
|
|
`draft-nandakumar-moq-tempo-00`): the draft's CaptureTimestamp, PlayoutDelay and HopTime
|
|
read and written, the instant a source's object is due under the controlled position, a
|
|
timing claim held to it and to the track's bound, and a change of delay as the controls
|
|
that make it. Corpus `wai/moqt-conformance/tempo/`, written with
|
|
`JOULE_MOQ_ROOM_WRITE_TEMPO=1 cargo test --test tempo_vectors`. A watch room's file is
|
|
still served over HTTP and stamped by nothing; the room's own source below is stamped.
|
|
|
|
- **publishes the room's own source over MOQT** (`[source]`, `src/source.rs`). A package
|
|
wai-pack wrote (`package`, its `video` and `audio` rungs) is published into each room in
|
|
real time, looped, as a principal of its own: its key derived from the clock's seed, its
|
|
grant a publisher's the authority signs, with `meta` events among its kinds. It holds one
|
|
of the room's publisher places, the clock signs its `join` and `publish` when its video
|
|
appears, and `GET /room` names it (`source.principal`) with its delay, its bound and the
|
|
property types its stamps are written under (`source.types`), which the room page reads them
|
|
by. Its catalog is MSF's
|
|
(`tests/catalogs/source.json`, held by `wai/ietf/catalog_check.py --strict`): each media
|
|
track a CMSF track with its receipts track and its `wai_tempo`, and the source's events
|
|
track. Each object is a CMAF chunk (`video_part_frames` frames of video at `framerate`, 3
|
|
at 30 by default; `audio_part_frames` of sound, 5) stamped with playout timing
|
|
(`draft-nandakumar-moq-tempo-00`): inside Immutable Properties a CaptureTimestamp, the
|
|
instant its last sample was captured, and a PlayoutDelay, `playout_delay_ms` (500 by
|
|
default, at most `max_delay_ms`, 2000); beside them a HopTime of the source's clock, which
|
|
the relay rewrites. The property types are `playout_delay_type`, `capture_timestamp_type`
|
|
and `hop_time_type`, as the relay's `[tempo]` names them, with its defaults: a deployment
|
|
that sets them on the relay sets the same here. A live source plays no controlled position,
|
|
so its instants are its captures plus its delay, and every object states one capture clock.
|
|
For each object as it is sent the source signs a running receipt (WAI live-http-egress
|
|
§3.5): a `meta` event anchored to the object, the next of its chain on its events track; the
|
|
group's receipts are signed and sent at the group's end, at a transport priority above its
|
|
media's and its events' above both. A relay door over the source's namespace
|
|
(`[[receipts.door]]`, as `wai dev` writes it) reads those running receipts and passes each
|
|
object as soon as its group's run covers it; the group's receipts close the run there. The
|
|
service's tests hold the source to that door: every object passed on its run, none refused.
|
|
The source is not
|
|
published in an end-to-end encrypted room, one admitted by age, a world, remote desktop or
|
|
call room, or a conversation, and the service refuses such a configuration.
|
|
- **stitches the source's breaks for each session** (`[ads]`, `src/ads.rs`; WAI live-ads
|
|
§14.8). In each room the source publishes into, the authority signs the room's ad policy
|
|
(claims required), the decider's grant (a key derived from the clock's, the room's budget as
|
|
`funds_ceiling` in `currency`) and the traffic policy (`[ads.traffic]`: the session cap, the
|
|
edges and the lists it pins). The source cues each break of the schedule (`first_break`,
|
|
`every`, `breaks`, `break_groups`, in video Groups) at the Group's instant, signed before the
|
|
Group's first Object; the stitcher reads the cues back over the relay, each held to its
|
|
signature, chain and principal. A session subscribes, under its session key `K`, to
|
|
`[room, source, video, stitched, D, K]` (`D` the traffic policy's digest): `K` its composed
|
|
video, `receipts` its group receipts, `ads` its creative records, `catalog` its catalog with
|
|
`wai_creative_signing` (`tests/catalogs/stitched.json`). Each break is decided for each session from the
|
|
SCTE 224 documents `scte224` names (read again at each decision) and the region the session
|
|
stated, then by the fill over the fixture creatives (`[[ads.creative]]`, rungs of wai-pack
|
|
packages, each with its sound where `audio_package` names a package's sound rung); a receipt
|
|
is sealed `claim_grace_ms` after its break ends. With sound, a session's composed sound is
|
|
`K` in `[room, source, audio, stitched, D, K]`, with its `receipts`, each Group taken from the
|
|
ingredient its video Group of that ID was taken from. Each composed Group's receipts are signed
|
|
as the Group ends, and each composed Object has a running receipt (WAI live-http-egress §3.5)
|
|
as it is sent, a `meta` event of the source's own chain on its events track, anchored to the
|
|
composed Object: so an HTTP egress serves each part as it is covered, not at the Group's end.
|
|
A creative's Objects are sent at the programme's pace, its Object `j` as the programme's Object
|
|
`j` arrives and what is left of its Group with the programme's last, so a composed Group's parts
|
|
come as its time goes.
|
|
The source's grant states the event policy that admits them, the default rate for each of its
|
|
own tracks and each session's composed two (`epoch::stitcher_policy`). An injector the
|
|
configuration names (`[ads] injectors`, WAI cue-injection) takes a grant of the role
|
|
`injector` (`meta`, `cue` and `caption`, no media: its cues, and an essence contribution's
|
|
captions, WAI essence-contribution §10.6); the clock signs its `join` when its events track
|
|
appears, and the stitcher reads its chain, captions and all, and its cues as the source's. A break whose cue falls inside a
|
|
programme Group is stitched from the next Group, each session's receipt stating the offset
|
|
(`start_offset_ms`, live-ads §14.3). Every composed Object carries its timing (live-ads
|
|
§14.3, *Its timing*): a CaptureTimestamp of the programme's capture clock at its end on the
|
|
composed timeline, the source's PlayoutDelay, and a HopTime; the session's catalog states the
|
|
source's `wai_tempo` on each composed track. While no creative lasts otherwise than the Groups
|
|
it fills, each programme Object carries the stamp its source gave it.
|
|
|
|
- **keeps a shared world** (`[world]`, WAI `extensions/live-worlds.md`). Each room then has a
|
|
world statement (`WLW1`) the authority signs: the world its operations replay in (for
|
|
`wai.world.model`, the model header) and the bounds of an op and of an object, served with
|
|
the room (`GET /room`'s `world`). A publisher's grant is an actor's: it lets its principal
|
|
publish its world track (the attribute `world` = `act`, §5.1) and no media. The clock watches
|
|
each actor's world track, `[room, actor, world]`, as it watches a publisher's video: it signs
|
|
the actor's `join` when the track appears and its `leave` when the track ends, and no
|
|
`publish`. The world's ops are the actors' own, signed by their keys and receipted on their
|
|
tracks; the service neither reads nor keeps them, and a page that enters late fetches every
|
|
track from the relay (§7), which the relay-lines' commented `[[dvr.window]]` keeps. With
|
|
`e2ee`, the world is private: every actor is a member of the room's group, and the world tracks
|
|
are sealed (§8). A world room is not also a watch room, a room with co-performance, one
|
|
admitted by age or a remote desktop room: the service refuses such a configuration.
|
|
|
|
- **keeps a conversation** (`[messaging]`, WAI `extensions/live-messaging.md`). The service's
|
|
room is then an end-to-end encrypted conversation, kept in a history store on disk
|
|
(`store`): `conversation.json`, its descriptor, confidentiality record and conversation
|
|
statement (`WMC1`), written once; and `journal`, every record the service took, appended and
|
|
on stable storage before it answers. A key that proves itself (a proof naming no grant, over a
|
|
challenge the service issued) is admitted (`admit`), the first as the conversation's owner,
|
|
and the clock signs its `join`; an owner removes a member and the clock signs its `leave`.
|
|
The clock is the MLS group's external sender, any member commits (live-messaging §7.2), and
|
|
after each GroupInfo or commit the clock signs an epoch statement (`WME1`). Members post sealed
|
|
messages and acknowledgements, which `wai-rs`'s conversation verifier checks before the
|
|
service takes them (§9.2), and read the log as §9.3 owes it: a removed member nothing past its
|
|
removal. A restart replays the journal, regenerating the clock's proposals and statements and
|
|
checking each against the one journalled, so the conversation goes on where it stopped. The
|
|
service holds no key of the group and no plaintext. A service that keeps a conversation keeps
|
|
nothing else (no watch room, co-performance, admission by age, shared world, remote desktop,
|
|
`[room] e2ee` or call): it refuses such a configuration.
|
|
- **keeps calls** (`[call]`, live-messaging §10). A caller proves its key and places a call: the
|
|
authority signs its descriptor (`WCT1`) and the caller's grant; the caller's invitations
|
|
(`WCI1`), checked by `wai-rs`'s call verifier, are held in each invitee's inbox, which it
|
|
reads on a proof of its key; its answers (`WCA1`) are checked the same way, and an accept comes
|
|
with its grant. At each invitation's deadline the clock signs a missed call. The media is the
|
|
room's audio and video, as in any room. `POST /grant` is refused: the grants come with the
|
|
call. A call room is not also a watch room, a room with co-performance, one admitted by age, a
|
|
world room or a remote desktop room: the service refuses such a configuration.
|
|
|
|
A room lasts while anyone publishes in it. When the last publisher has left, the next one
|
|
starts a new room: a new descriptor at a new `t0`, under the same authority and clock, so
|
|
each room's grants and chain hold one stretch of company.
|
|
|
|
## Running it
|
|
|
|
```text
|
|
wai-room keygen <keys.toml> new keys (mode 0600, never over a file); prints the
|
|
relay configuration lines that trust them
|
|
wai-room relay-lines <keys.toml> those lines again
|
|
wai-room invite <keys.toml> [<s>] an invitation to host a remote desktop room, signed by
|
|
the room's authority, good for <s> seconds (3600)
|
|
wai-room --config <room.toml> serve (room.example.toml shows every field)
|
|
wai-room participant --service <url> --relay <url> [--quic <addr> --cert-sha256 <hex> ...]
|
|
synthetic participants of a development stack on this
|
|
machine, driven by JSON control lines: what `wai dev` runs
|
|
```
|
|
|
|
The keys file must be readable by its owner alone; the service refuses it otherwise.
|
|
`Keys` has no `Debug`, so that no log prints a seed.
|
|
|
|
The relay needs, beside its listener and limits, the lines `relay-lines` prints:
|
|
|
|
```toml
|
|
[[auth.authorities]]
|
|
key = "<authority>"
|
|
|
|
[[auth.grants]] # the clock's events track, in any room
|
|
prefix = ["*", "<clock>", "events"]
|
|
token_sha256 = "<SHA-256 of the clock's token>"
|
|
publish = true
|
|
unlimited = true # no session or ingress limit for the clock's session
|
|
|
|
[[auth.grants]] # the clock watches the publishers
|
|
prefix = []
|
|
token_sha256 = "<SHA-256 of the clock's token>"
|
|
subscribe = true
|
|
```
|
|
|
|
and a door that holds each publisher's media to its own key:
|
|
|
|
```toml
|
|
[[receipts.door]]
|
|
prefix = ["*", "*", "video"]
|
|
trust_field = 1
|
|
```
|
|
|
|
### The image
|
|
|
|
`deploy/Containerfile` builds `wai-room` as an OCI image from the repository's root: the binary
|
|
built with the pinned toolchain from the locked dependencies, on a slim base pinned by digest, run
|
|
as the unprivileged user `wai` (uid 10001), its keys in the volume `/var/lib/wai-room`, and a
|
|
configuration (`deploy/room.toml`) that serves HTTP on port 9682 and reaches a relay container
|
|
named `relay` at `ws://relay:4480/moq`. The keys are made once, and the lines that make the relay
|
|
trust them are printed:
|
|
|
|
```text
|
|
docker run --rm -v wai-room:/var/lib/wai-room wai-room keygen /var/lib/wai-room/keys.toml
|
|
docker run -v wai-room:/var/lib/wai-room -p 9682:9682 wai-room
|
|
```
|
|
|
|
`deploy/check-image.sh` builds it and runs it beside the relay's image on one network, both with a
|
|
read-only root and no capabilities, and requires `/health` with the clock's session to the relay
|
|
up. A release pushes it to the forge's container registry as
|
|
`git.transaction.science/transaction-science/wai-room:<version>`.
|
|
|
|
### Synthetic participants
|
|
|
|
`wai-room participant` (`src/participant.rs`) is the client half of WAI's one local stack,
|
|
`wai dev`: test principals that enter a room through a relay and a room service on this machine,
|
|
each running as its own process from its configuration. It reaches the service over HTTP, for
|
|
grants and for the room it serves, and the relay over its WebSocket binding or, impaired, its
|
|
native QUIC binding, the relay's development certificate pinned by its SHA-256. The service and
|
|
the relay must be on loopback addresses, checked before anything is sent. Each object goes out
|
|
with its receipt: a group receipt signed by the publisher's key on the track's receipts track,
|
|
sent before the group's first object, and the receipt as the object's `WAI_RECEIPT` property, so
|
|
a relay that holds each publisher's tracks to its own key passes them. On the QUIC binding each
|
|
session goes through a UDP proxy of its own (joule-contrib's `lossy`) that drops, delays and
|
|
spreads its datagrams from a fixed seed; loss applies to datagrams, so to QUIC alone. A session
|
|
over a path that drops datagrams waits for the answer to a request (a PUBLISH, a SUBSCRIBE, a
|
|
FETCH) as long as it waits for an object, a minute, where a session otherwise waits ten seconds:
|
|
a request and its response are each recovered at QUIC's probe timeout when dropped, which on a
|
|
session that has measured no round trip yet starts near a second and doubles. The relay's own
|
|
ten seconds for a session's SETUP stand. `wai dev --impair` writes its relay's doors that same
|
|
minute's hold, where it otherwise writes two seconds.
|
|
|
|
Its first line on standard output is `{"ready": true, "participant": true, "development_only":
|
|
true, "service", "relay", "transport", "impair"}`, and it answers JSON control lines on standard
|
|
input, each with an `op` and an optional `id`:
|
|
|
|
| `op` | Fields | Answer |
|
|
|---|---|---|
|
|
| `join` | `seed` (a test principal's Ed25519 seed, 64 hex), `grant` (a publisher's grant for that key, hex), `objects` (per track, 1 to 64), `receipts` (default true) | the principal enters with its grant and a proof, publishes `audio` and `video` with that many objects on each, and the answer comes once the clock has signed its `join` and both `publish`es: `{"principal", "room", "tracks", "receipts", "objects_published", "objects_served", "events"}` |
|
|
| `leave` | `principal` (64 hex) | its session closes; the answer comes once the clock has signed its `leave` |
|
|
| `session` | | the room as live-rooms §9 gives it to a verifier: `{"room", "t0_us", "descriptor", "grants", "key_log_head": null, "chains"}` |
|
|
| `test` | `objects` (50), `every_ms` (20) | a publisher and a viewer of fresh keys: `{"transport", "impair", "objects_sent", "objects_received", "objects_lost", "undelivered", "latency_ms", "path", "impaired", "seconds"}`; `path` is QUIC's own count of packets sent and declared lost and its round trip, `impaired` what the proxy dropped, and `undelivered` (null when every object arrived) each missing object's group with where it stands at the viewer: its stream never opened there (the relay forwarded nothing of it: its door refused it, or it never reached the relay), open and unfinished, ended without it, or failed with its error |
|
|
| `load` | `viewers` (8, at most 64), `objects`, `every_ms` | one publisher and that many viewers: `{"viewers", "deliveries_owed", "deliveries_made", "delivered", "latency_ms", "enter_s", "path", ...}`, the client half alone |
|
|
| `describe` | | the ready line's fields |
|
|
|
|
Standard input closing ends it. `tests/participants.rs` holds it to a relay with doors: a
|
|
participant without receipts is dropped there (and served where there is no door), impairment
|
|
changes the loss QUIC measures, and a load is delivered in full.
|
|
|
|
`src/dev.rs` is a test fixture, not a mode of the binary: the relay and the service in one
|
|
process on loopback, keys in memory, for this crate's tests.
|
|
|
|
## HTTP
|
|
|
|
[`openapi.json`](openapi.json) describes this API (OpenAPI 3.1): every path, its requests,
|
|
answers and statuses, in every mode the service keeps. Each operation's `operationId` is its
|
|
name in the WAI toolchain's catalogue, `noun.verb` (`room.show` is `GET /room`, `room.grant` is
|
|
`POST /grant`), the name the other doors derive theirs from (`wai mcp`'s tool `room_grant`).
|
|
`tests/openapi_contract.rs` holds it to the code: every operation is named as the catalogue
|
|
names it, the routes the routers match are exactly its operations, every status it lists for an
|
|
operation is one that operation's handlers return and every status they return is listed, and
|
|
the service, run in each mode, answers every operation with a status it lists.
|
|
|
|
| Request | Answer |
|
|
|---|---|
|
|
| `GET /`, `GET /health` | `{"ok", "relay": the clock's session is open, "room"}` |
|
|
| `GET /room` | the room: `room`, `t0_us`, `descriptor`, `compose`, `grants` (hex), `present`, `publishers`, `max_publishers`, `e2ee` (`{"record": <hex>, "log"}`, or null), `co` (`{"policy": <hex>, "audience": [<hex>]}`, or null), and `world` (`{"statement": <hex>, "receipt_alias": 4}`, or null) |
|
|
| `POST /grant` `{"principal": <64 hex>, "role": "publisher" \| "viewer"}` | `{"grant", "digest", "exp", "role", "room": <as GET /room>}`; `409` `full` or `busy`; `429` `rate` with `retry_after_s`; `503` `relay` while the clock has no session |
|
|
| `POST /grant` `{"principal", "role": "captioner", "ai": <64 hex>}` in a room that admits captioners (`[ai]`) | an AI captioner's grant, its `ai` the model's pin; `400` with no `ai` or in a room with no captioner role; `403` `ai_model_not_admitted`; `409` `full` while the captioners hold every place. `ai` asked under any other role, in such a room: `403` `ai_role_not_permitted` |
|
|
| `GET /mls?room=<id>&from=<n>[&wait_ms=<ms>]` | an end-to-end encrypted room's log from entry `n`: `{"room", "entries": [{"seq", "event" or "mls": <hex>}], "next", "end"}`, held up to 8 s while nothing is past `n`; `410` for a room since replaced, `404` for a room that is not end-to-end encrypted |
|
|
| `POST /mls/key-package` `{"room", "key_package": <hex>}` | kept for the principal's Add: `{"ok", "principal"}`; `403` for a principal the room has not granted |
|
|
| `POST /mls/post` `{"room", "frames": [<hex>]}` | a GroupInfo, or a commit and its Welcome, checked by the clock as a member checks it, then carried: `{"ok", "seq"}`; `409` `{"error": <reason>}` when the clock refuses it |
|
|
| `POST /grant` in a room that admits by age | without a token: `401` `{"error": "token_required", "age"}` and a `WWW-Authenticate: PrivateToken challenge=…, token-key=…, max-age=…` for each key; with `Authorization: PrivateToken token="…"`: the grant, its `age` the token's threshold, or `403` `{"error": <age-assurance §11's reason>}` and the challenges again; `503` `busy` when the window's nonces are at their bound. `GET /room`'s `age` is `{"policy", "ratings", "age", "token_window_s"}` |
|
|
| `GET /age/receipt` | `{"receipt", "policy"}`: the room's admission receipt so far, signed by its clock |
|
|
| `GET /watch/time` | a watch room: `{"now_us", "t0_us"}`, the service's clock, for a page's estimate of the room clock |
|
|
| `GET /watch/source` | a watch room's source, its bytes whole, of its media type |
|
|
| `GET /watch/challenge` | `{"challenge": <hex>}`: single-use, good for a minute |
|
|
| `POST /watch/join` `{"room", "grant": <hex>, "proof": <hex>}` | the proof (live-rooms §10.5.1) over a challenge, for `POST //<host><public_prefix>/watch/join`; the clock signs the principal's `join`, after its `leave` if the room took it for gone and that is not signed yet: `{"ok", "t"}`; `401` `{"error": <reason>}` for a proof that fails |
|
|
| `POST /watch/post` `{"room", "events": [<hex>]}` (at most 8) | the viewer's events, each checked and carried in order: `{"ok", "carried", "log"}`; `409` `{"error": <reason>, "carried"}` at the first refused |
|
|
| `GET /watch?room=<id>&from=<n>[&wait_ms=<ms>]` | the watch room's log from entry `n`: `{"room", "entries": [{"seq", "event": <hex>}], "next", "end"}`, held up to 8 s while nothing is past `n` |
|
|
| `GET /watch/room` | the room as a co-watch verifier is given it (`descriptor`, `grants`, `chains`, `policies`, `sources`), the latest window's skew statements read, and the refusals by reason |
|
|
| `POST /grant` in a remote desktop room `{"principal", "role": "host" \| "controller" \| "viewer", "invitation"?}` | the grant; `403` `{"error": "not_the_host" \| "invitation_required" \| "invitation_used" \| "invitation_expired" \| "invitation_bad_signature" \| "control_not_allowed"}`; `409` `hosted` while the room's host holds its place. `GET /room`'s `remote` is `{"host", "allow", "control", "max_age_ms", "screen": {"digest", "statement"} or null, "holder", "floor_refused", "inputs", "floor_statements", "cut", "refused", "records", "max_records", "max_records_per_principal", "records_over", "ended"}` |
|
|
| `POST /remote/control` `{"to", "control": 0 to 15}` | the host's grant exchange, its grant and proof presented as a co-performance request presents them: `{"ok", "grant", "digest", "effective"}`; `403` for a grant that is not the host's, `409` `{"error": "not_present"}` |
|
|
| `GET /remote/room` | the session as the clock holds it: `{"room", "descriptor", "grants", "key_log", "chains"}`, what a remote desktop verifier is given. The records hold what controllers typed and pasted, so they are the room's: a request presents a grant this room issued and its proof over the service's challenge, as a co-performance request does; `401` with a challenge otherwise, `403` for a grant of another room. Bounded per client |
|
|
| `GET /remote/state` | that session verified by `wai-rs`, to a grant of this room and its proof as `/remote/room`: the outcome, the screens, each input's fate, the floor's changes and the floor statements §7.2 refused, the latencies, each host's totals, the clock's own floor, and each principal the clock cut and why; bounded per client |
|
|
| `POST /offer` `{"offer", "item", "anchor": {"track", "group", "object"}}` | an offer the clock presents (WAI shoppable-events), for a grant with the direct power, presented and proved as a co-performance request is: the offer's document and the item's signed description, read as a verifier reads them, then signed by the clock as `meta` events of its events track, the description (`WOI1`) and then the offer (`WOF1`) anchored to the object the programme shows it at: `{"ok", "offer": <its id>, "t", "record", "item_record"}` (records in base64url); `400` `{"error": "offer", "reason": <the extension's reason>}` with nothing signed; `403` `power`. The service sells nothing: a viewer's intent goes to the merchant's own endpoint |
|
|
|
|
Every answer is JSON with `Cache-Control: no-store`. The client's address is
|
|
`CF-Connecting-IP`, else the first `X-Forwarded-For`, else the connection's: the service
|
|
listens on loopback behind the front that sets them.
|
|
|
|
`GET /metrics` is the operator's: the service's counters in the text exposition format
|
|
([`wai/wai-metrics`](../../../../wai/wai-metrics)), answered before the routes of the API, so
|
|
`openapi.json` does not describe it and the catalogue does not name it. It holds the requests by
|
|
route, method and status (`wai_room_requests_total`), the grants issued at `POST /grant` by role
|
|
(`wai_room_grants_total`; a call's and a conversation's grants come with their own requests, and
|
|
are not among them), the rooms kept since the start, whether the clock's session at the relay is
|
|
open, and the room now: the principals present (`wai_room_present`: a watch room's viewers who
|
|
joined among them, and in a conversation its members), the publishers holding a place, the audience
|
|
members holding one (a room with co-performance's, or a remote desktop room's controllers and
|
|
viewers), and the viewers' grants, its fan-out (`wai_room_viewer_grants`: a remote desktop room's
|
|
controllers among them; a conversation has none). No room, principal, grant or client is a label.
|
|
It is read at the service's own address: a request that came through a front, one carrying
|
|
`X-Forwarded-For`, `CF-Connecting-IP` or another of the fields a front sets, is answered `404`, and
|
|
a front that sets none of them is configured not to pass the path on.
|
|
|
|
A conversation's requests (each, but `GET /msg` and `GET /msg/challenge`, with a grant the
|
|
conversation issued and its proof, or for `/msg/join` a proof naming no grant):
|
|
|
|
| Request | Answer |
|
|
|---|---|
|
|
| `GET /msg` | the conversation: `room`, `t0_us`, `descriptor`, `record`, `statement`, `clock`, `grants`, `members`, the latest `epoch`, the log's length |
|
|
| `GET /msg/challenge` | `{"challenge": <hex>}`, single-use |
|
|
| `POST /msg/join` `{}` | `{"grant", "digest", "exp", "role": "owner" \| "member", "conversation"}`; `403` `not_listed` or `removed`, `409` `full` |
|
|
| `POST /msg/key-package` `{"key_package"}` | kept for the member's Add |
|
|
| `POST /msg/mls` `{"frames": [<hex>]}` | a GroupInfo, or a commit and its Welcome: `{"ok", "seq", "end"}`, or `409` with the clock's reason |
|
|
| `POST /msg/post` `{"group", "properties", "payload", "receipts"}` | a message, taken after §9.2's checks: `{"ok", "seq"}`, or `409` with the reason |
|
|
| `POST /msg/ack` `{"ack"}` | an acknowledgement, taken after §8's checks |
|
|
| `POST /msg/leave`, `POST /msg/remove` `{"principal"}` | the clock's `leave`, reason 1, or reason 2 by an owner |
|
|
| `GET /msg/log?from=<n>[&wait_ms=<ms>]` | the entries owed from `n` (`{"entries": [{"seq", "event" \| "mls" \| "epoch" \| "message" \| "ack"}], "withheld", "next", "end"}`), held up to 8 s while nothing is new; `403` `not_a_member` |
|
|
|
|
A room with ads (`[ads]`) answers, too: `POST /ads/session` `{"player", "region"}` (the
|
|
session's key and the region it aggregated, or `null`); `POST /ads/binding` (a device binding,
|
|
`WDB1`, held to the room's traffic policy and its two signatures); `POST /ads/claim` (a
|
|
presentation claim, `WPC1`, held to its signature); `GET /ads/list/wai-ad-<digest>.json` and
|
|
`.mpd` with `player=` and `_HLS_start_offset=` (the asset list filled for that session);
|
|
`GET /ads/creative/<hash>.m3u8` and its files; `GET /ads` (the room's ads); and `GET
|
|
/ads/records` (`?seal=all` seals every fill first), what `ads::audit` verifies: the operator's,
|
|
answered at the service's own address and `404` through a front, since its claims and bindings
|
|
link the sessions of one device key. A binding and a claim are the record's bytes, or, as the
|
|
toolchain's command line sends a body, `{"record": <hex>}`. `openapi.json` describes each of these
|
|
paths, the asset list's and the creative's by their templates (`/ads/list/{file}`,
|
|
`/ads/creative/{hash}.m3u8`, `/ads/creative/{hash}/{file}`), and the catalogue names them
|
|
(`ad.show`, `ad.export`, `viewer.join`, `binding.create`, `claim.create`, `asset.list`,
|
|
`creative.show`, `creative.fetch`); `wai asset list --query file=wai-ad-<digest>.json --query
|
|
player=<key>` fills the template from the query. A path of `[ads]` answers `404` in a service
|
|
without it, and `405` to a method it does not take.
|
|
|
|
A call's requests (`/call/ring` with the caller's grant and proof, the rest on a proof naming no
|
|
grant): `GET /call/challenge`; `POST /call/place` `{"invitees": [<64 hex>]}` (the call
|
|
descriptor, its digest and the caller's grant, `409` `busy` while a call has its places);
|
|
`POST /call/ring` `{"invitations": [<hex>]}`; `GET /inbox`; `POST /call/answer` `{"answer"}`
|
|
(an accept answered with the callee's grant); `GET /call`, to the call's parties.
|
|
|
|
## Load
|
|
|
|
`src/bench.rs` is the service's load harness, and `examples/room_bench.rs` runs it with the relay
|
|
and the service each a process of its own on loopback, so that the service's processor time is
|
|
read apart: viewers' grants at a number in flight, publishers entering and leaving at once (the
|
|
clock's `join`, `publish`es and `leave` read off its events track as they come), the room's own
|
|
source with ads stitched for a number of sessions (each session's catalog, records, composed
|
|
video and sound and their receipts), and AI captioners (`joule-moq-agent`'s, its scripted test
|
|
recognizer) beside speakers. Each run is a record signed by its host's key, under the relay
|
|
harness's idle rule, stating each operation done of those owed, its 50th and 99th percentile
|
|
against its bound, and the service's time a unit of its work. `results/` holds one host's dated
|
|
records, what stopped each size past the largest it held, and the guard (`results/guard.sh`)
|
|
that holds later commits to them.
|
|
|
|
The stitcher answers every session's six subscriptions of its stitched tracks on the source's one
|
|
session and holds them open, each a request stream of that session, so the session declares
|
|
`source::SOURCE_REQUESTS` (six for each session a room holds, `ads::MAX_SESSIONS`, and 64 for
|
|
its own); and each session's composed video and sound each hold a data stream open for their
|
|
Group. A relay's default bound on one session's open data streams (`limits.max_uni_streams`,
|
|
1024) is reached at 512 sessions: a deployment that stitches for more raises it for its relay.
|
|
|
|
## Tests
|
|
|
|
`tests/openapi_contract.rs` holds `openapi.json` to the service (above). `tests/mcp_dev_room.rs`
|
|
builds `wai` with the feature `mcp`, in a target directory of its own, and drives
|
|
`wai mcp --toolset core,dev` with an MCP client: the tool `room_grant` and the API's `POST /grant`
|
|
named alike (`room.grant`), a development room started on loopback, grants for two test
|
|
principals, each joining and publishing, the session verified as live-rooms §9 verifies a room,
|
|
a changed record refused, a leave verified, and a grant from a service off the machine refused.
|
|
|
|
`tests/ads_room.rs`, the room's ads through the relay: two breaks the source cues, two sessions
|
|
in two regions under an SCTE 224 document that blacks one of them out over the second break;
|
|
every composed Group held to the stitcher's receipt, each session's records bound to its
|
|
receipts and its composed track to its recipe, each audience decision held to its document, and
|
|
the second break refused by the region rule for one session alone.
|
|
|
|
`tests/offer_room.rs`, an offer through the service and the relay: `POST /offer` refused for a
|
|
viewer's grant, for an unknown stock written as zero, for another item's description and with no
|
|
anchor, with nothing signed for any; the lead's offer signed by the clock, read from the relay by a
|
|
subscriber as WAI shoppable-events reads it, with the description the room carried, the room's
|
|
keys admitted, presented inside its window and not after; the same record refused where only a
|
|
guest's key is admitted, and with one digit of its price changed.
|
|
|
|
`tests/source_room.rs`, the room's own source through the relay, its door (as `wai dev`'s, each
|
|
publisher's video and sound held to its own key) and the HTTP egress gateway: the door passes
|
|
every object on its group's run and refuses nothing, no running receipt refused and no run
|
|
contradicted; a viewer subscribed with Sync Support holds every object to its stamps (the configured types
|
|
inside Immutable Properties, the catalog's delay within its bound, one capture clock across the
|
|
two tracks read from each chunk's own media time, a HopTime of the relay's clock, arrival
|
|
before the instant, every group whole and in order); the gateway serves each part on the run
|
|
served beside it and every part of a closed segment on its group's receipts, nothing withheld or
|
|
refused, its trace checked. And a real-time measurement: thirty seconds of 30 fps video and
|
|
sound through the door with every part served within 500 ms of its capture stamp and none
|
|
missing or withheld, and how long the door held each object against how long after it its
|
|
group's receipts came (what a door that reads no running receipt holds it for), refused (and
|
|
said so) on a host whose one-minute load is above a quarter of its processors.
|
|
|
|
`cargo test` (this crate is a workspace of its own, with its own lock file, since it builds
|
|
on `wai-rs`): the keys file, the grants checked against the descriptor and read by the
|
|
relay's reader, the places, the limiter, and `tests/room_service.rs`, the service against a
|
|
relay on loopback: a grant and its proof open a session and the clock announces the
|
|
publisher, the grant alone opens none, the room verifies, a third publisher is refused,
|
|
clients are bounded, and a new room starts when the last publisher has left; and an end-to-end
|
|
encrypted room on a relay, two members keying through the service as pages do (a GroupInfo,
|
|
the clock's Add, a commit and its Welcome), the posts the clock refuses not carried, each
|
|
opening what the other seals, and a member that leaves removed by the next commit. Admission by
|
|
age: unit tests of `src/age.rs` over published RSA test keys (`tests/common/rfc_test_keys.rs`:
|
|
RFC 7517 A.2's, RFC 9500's `testRSA2048` and RFC 9578 A.2's), whose tokens they sign directly,
|
|
as the blind protocol's output is the same signature: the challenges by window, a token
|
|
admitted once and at the room's age, the window before accepted and the one before that, another
|
|
origin's and another service's challenge refused, the nonces bounded and dropped by window, and
|
|
the receipt's cells the reference's and its signature the clock's; and `tests/age_admission.rs`,
|
|
the service with `[age]` and a relay: the `401` and its challenges, tokens at 16 and 18 admitted
|
|
and their grants' `age` read by the relay, one of 13 refused `below_threshold`, one spent twice
|
|
`token_replayed`, one for another origin `token_wrong_challenge`, one that does not read
|
|
`token_malformed`, a publisher challenged too, the relay serving the viewer at 16 the audio and
|
|
refusing it the video the publisher rates 18, and the receipt read and checked by `wai-rs`. A
|
|
room with co-performance: its policy, the audience-input power and the budgets on its grants, and
|
|
two viewers' votes in a window the clock opens, commits and selects, verified as co-performance §9
|
|
verifies a room. A watch room against a relay on loopback: its source served and named by its
|
|
policy, viewers admitted by a proof for the request they make (a proof for another target, or
|
|
presented again, refused), their controls and statements carried and a control in the past
|
|
refused, the clock's skew statements of each window, each viewer's leave once silent, and the
|
|
room the log carries verified as co-watch §11 verifies it, every viewer in sync. A remote desktop
|
|
room (`tests/remote_room.rs`, with the host agent and headless viewer of `joule-moq-remote`): the
|
|
host reaches the service over `https://` and the relay over `wss://` through TLS fronts, enters by
|
|
an invitation, and a second key presenting it is refused, two viewers make their keys
|
|
and get viewers' grants, the host hands control to one by a grant exchange it proves its key for
|
|
(the grant with no proof, a proof by another key, and a proven request by a principal that is not
|
|
the host, each refused), her click under the floor is applied and the viewer's move refused
|
|
`input_not_permitted`, her typing goes sealed to the host, is injected as typed, and neither a
|
|
viewer following her events nor the session the service serves to a viewer holds a trace of it,
|
|
her text in the clear refused `input_unsealed`, control and the floor pass to the second, a give by a controller that does
|
|
not hold the floor and a release that crosses the host's take are each stated and change nothing
|
|
(the clock, the host and the verifier hold the same floor, and neither author is cut), a record
|
|
out of its chain's order is cut and its author removed by the clock and by the host, the session's
|
|
records are refused with no grant (`401`) and with another room's (`403`) and read with a viewer's,
|
|
a controller's and the host's grant and proof, and the session verified by `wai-rs` as the clock
|
|
serves it and as the host holds it, at the commitment level and at the opened level under the
|
|
host's opening key; a viewer flooding past a bound of 24 records of its own, cut there and removed
|
|
by the clock and by the host while the host and a controller go on and the session verifies at
|
|
both levels; a session the clock ends at a bound of 40 records, every principal removed,
|
|
the host last, the store within the bound, the session whole and verified, and a new room for the
|
|
next host; and unit tests of `src/remote.rs`: the grants'
|
|
attributes checked against the descriptor and read by the relay's reader, an exchange chained to
|
|
the grant it replaces on a tick, invitations, the configuration's rules, and the grant in effect
|
|
from the clock's lifecycle.
|
|
|
|
A conversation (`tests/messaging_room.rs`), its members keying through real MLS groups and
|
|
sealing with `joule-moq`'s secure objects: three members message; one goes offline, the service
|
|
restarts from its history store and the log is as it was, a fourth joins and a member other than
|
|
the lowest commits; the one offline catches up, opening every message of both epochs; a thread,
|
|
reactions, an edit and a retraction verify, the retracted message stated retracted;
|
|
acknowledgements taken and refused; a member removed opens nothing sealed after its removal, is
|
|
served nothing past it (`not_a_member`), and the service takes nothing more from it; every record
|
|
of the log verifies offline, and the store holds no plaintext. And `wai-rs`'s sealing of a message
|
|
object and `joule-moq`'s secure objects open what the other seals. A call (`tests/call_room.rs`)
|
|
through a relay: placed, an invitation with a bad signature and one to a principal not invited
|
|
refused, the callee's inbox read and verified, the call accepted, both parties publishing audio
|
|
and video and each receiving the other's, the clock's `join` and `publish` for each; then a call
|
|
among three in which one declines and the other is missed, signed by the clock and in its inbox,
|
|
and a late accept refused; each call verified as live-messaging §10 verifies it.
|
|
|
|
## Licence
|
|
|
|
Apache-2.0.
|