open-standards/joule-code/joule-code-rs/crates/joule-moq-relay/relay.example.toml
Transaction Science 2790e97b4a The edges of a stitched room write each session's origin, and a player its device binding
joule-moq-relay [ads_edge]: for each subscription it admits to a session's
composed track, [room, stitcher, video, stitched, D, K] with Track Name K,
the relay writes a session origin (WAI ad-presentation §14.3) under the
traffic policy D, signed by its edge key: its transport peer's address,
an IPv4-mapped peer stated with family 4, and the User-Agent of the
WebTransport CONNECT or WebSocket upgrade that opened the session. The
operator reads them with "origins" on the control surface.

joule-moq-http Gateway::serve_ads: the gateway is the edge of a room's
asset-list requests: it writes the viewer's origin and passes the request
on to the decider. The origins are its operator's.

joule-moq-web web/ads/traffic.js writes a device binding with WebCrypto's
Ed25519 and a device key kept in IndexedDB; its test writes every binding
of live-conformance/ad-traffic again, byte for byte. The room service's
GET /ads/records is the operator's, as GET /metrics is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 17:23:19 -05:00

300 lines
14 KiB
TOML

# wai-relay (crate joule-moq-relay): an example configuration. Every field is optional; these are the
# defaults unless a comment says otherwise. Nothing secret belongs here: keys are files
# the operator provides, and tokens are named by their SHA-256.
# Native QUIC (ALPN moqt-22) and WebTransport over HTTP/3 (ALPN h3), one UDP port.
quic_listen = "127.0.0.1:4443"
# The WebSocket fallback (plain HTTP upgrade; put TLS in front of it).
websocket_listen = "127.0.0.1:4480"
# The path of the WebTransport CONNECT and of the WebSocket upgrade.
path = "/moq"
# The New Session URI a shutdown GOAWAY names; empty reuses the current one.
goaway_uri = ""
[tls]
# cert_path = "/etc/joule-moq-relay/fullchain.pem"
# key_path = "/etc/joule-moq-relay/key.pem"
# Without files: a development certificate for these names, its SHA-256 printed.
dev_names = ["localhost", "127.0.0.1", "::1"]
[cache]
max_bytes = 268435456
[limits]
max_sessions = 4096
max_requests = 256
max_uni_streams = 1024
max_request_updates = 16
max_auth_token_cache_size = 16384
max_rendezvous_ms = 30000
upstream_timeout_ms = 5000
fill_timeout_ms = 2000
write_concurrency = 8
max_object_bytes = 16777216
unknown_alias_wait_ms = 1000
goaway_timeout_ms = 5000
# A room grant is proved over a challenge the relay's SETUP gave on an earlier session (the
# relay reads a client's SETUP before it sends its own): how long such a challenge is good
# for, and how long a session refused at SETUP is held open for its client to read the
# relay's SETUP before the relay closes it UNAUTHORIZED.
grant_challenge_ms = 60000
setup_refusal_wait_ms = 2000
# Refused sessions held open at once for that wait, outside max_sessions, in all and from one
# address: past either the oldest held is closed at once.
max_held_refusals = 256
max_held_refusals_per_address = 8
# What one session may hold at once and how many requests it may open a second; past it a
# request is refused EXCESSIVE_LOAD. 0: no limit. A hop link from another relay is exempt.
max_subscriptions_per_session = 1024
max_tracks_per_session = 1024
max_namespaces_per_session = 256
max_requests_per_s = 1000
# MAX_REWIND: the most Groups before the current one a Rewind filter may ask for.
max_rewind_groups = 8
# What one subscription may have waiting to be sent (objects and stream ends as they arrived
# from upstream): past it the subscription is ended TOO_FAR_BEHIND. 0: no bound.
max_queued_per_subscription = 8192
# 0 for each of these: no limit. A session at max_session_ms is closed; one sending past
# max_ingress_kbps (averaged over ingress_burst_ms) is closed; the binary exits with status
# 70 past max_resident_mb. A rule with `unlimited = true` exempts its sessions from the first
# two.
max_session_ms = 0
max_ingress_kbps = 0
ingress_burst_ms = 4000
max_resident_mb = 0
# The most relays a request may have passed through (its RELAY_PATH), and the wait before an
# upstream link is dialed again, doubled on each failure to 30 s.
max_relay_hops = 8
upstream_reconnect_ms = 1000
websocket_buffer_bytes = 67108864
idle_timeout_ms = 30000
# A session the relay has a write waiting on, whose peer grants no flow-control credit for
# this long, is closed (0: never). The idle timeout does not end it: its peer still acknowledges.
stalled_session_ms = 60000
quic_socket_buffer_bytes = 4194304 # each UDP socket's buffers; the system caps them (net.core.rmem_max on Linux)
quic_sockets = 0 # UDP sockets on the one port: 0 is one per core up to 16 (Linux; elsewhere one)
quic_send_window_bytes = 1048576
quic_stream_window_bytes = 16777216 # a stream's flow-control window: more than the largest group a door holds
quic_connection_window_bytes = 67108864 # a session's, all its streams together: what bounds the bytes it leaves unread; never below the stream window
[receipts]
receipts_track_suffix = "_receipts"
# Check objects in this namespace against their receipts at the door (example; not a
# default). An empty trust list accepts any key whose signature verifies. A field "*"
# matches any field; `trust_field = 1` with `prefix = ["*", "*", "video"]` would accept, in
# each room, the key its namespace's second field names (WAI live-rooms §6.1).
[[receipts.door]]
prefix = ["example", "live"]
trust = []
hold_ms = 2000
# The signed session summaries (README, *The session summary*): held until the operator
# acknowledges them (`summaries` and `ack` on [control]'s surface), at most `held_max` in memory.
# Past it they are appended to `spill_path` and read back from there; with none, or where a write
# there fails, each is held all the same and the relay admits no new session until the operator
# acknowledges enough. None is ever dropped.
[summary]
# key_path = "/etc/joule-moq-relay/summary-seed"
# out_path = "/var/log/joule-moq-relay/sessions.jsonl"
held_max = 65536
# spill_path = "/var/lib/joule-moq-relay/summaries.jsonl"
# The relay's own energy in its per-hop session records (WAI live-metering §8): "auto"
# reads the counter the host offers (macOS: the relay process's CPU energy; Linux: the CPU
# packages' RAPL energy, only with dedicated_host = true, the statement that this host runs
# the relay alone); "off" reads none, and every record carries the unmetered marker.
[metering]
energy = "auto"
dedicated_host = false
# The room grants tokens carry, for the per-hop session records of WAI rooms (an example;
# not a default): a session whose SETUP presents this token runs under this grant, in the room
# the token states when it is a room grant's record, and otherwise in each room no grant of its
# own names.
[[metering.grants]]
token_sha256 = "1df4b07d05b9b3b1115b4b6b22af319a2f99564a2fa2573a34896063c7513c10"
grant = "0000000000000000000000000000000000000000000000000000000000000001"
joules_ceiling_micro = 5000000
funds_ceiling = 500
currency = "EUR"
[auth]
required = false
# An example grant; the hash below is of the placeholder string "replace-me".
[[auth.grants]]
prefix = ["example"]
token_sha256 = "1df4b07d05b9b3b1115b4b6b22af319a2f99564a2fa2573a34896063c7513c10"
publish = true
subscribe = true
# true: the token is a room grant's record (WAI live-rooms §4.1), honoured only on a
# session whose SETUP proved possession of its principal's key (§10.5.2).
proof = false
# true: a session presenting this token is exempt from max_session_ms and max_ingress_kbps.
unlimited = false
# true: the token may seek back, fetching a track below its live edge (WAI live-rooms
# §10.6). A room grant from an authority below seeks back when its powers hold seek.
seek = false
# The DVR store (an example; not a default): a window of the tracks under each prefix, kept
# on disk with their receipts and served to FETCH below the live edge (WAI live-recording
# §8). Groups go oldest first, whole, past max_age_s (at most 86400, 24 hours) or past
# max_bytes for the window's tracks together. The store survives a restart: on start every
# object is checked against its receipt again, and one that does not verify is dropped,
# counted, and never served. `room_keys` are the room authorities and clocks whose decision
# events withhold what they deny or redact (WAI live-moderation §6.3). `takedown_keys` are the
# services whose signed takedowns the store honours (WAI takedown §10.1): it forgets what each
# covers and signs a purge under purges/ with the summary key.
[dvr]
path = "/var/lib/joule-moq-relay/dvr"
room_keys = []
takedown_keys = []
[[dvr.window]]
prefix = ["*"]
max_age_s = 900
max_bytes = 4294967296
# The floor of WAI rooms (WAI co-performance §6): the relay reads each room's turn schedules
# and handovers from the events tracks it forwards, and does not forward a media object a
# performer publishes out of turn under a schedule that refuses. A schedule is honoured from a
# room key (these, and the keys of `auth.authorities`) or from a principal whose session proved
# a grant with the direct power. Absent: the relay holds no floor. The relay counts each
# author's audience inputs in the order of its chain (co-performance §7.3): an input that arrives
# ahead of a gap in its author's chain waits up to `hold_ms` for the gap to fill, and is refused
# `input_out_of_order` if it has not.
[floor]
room_keys = []
hold_ms = 2000
# A room authority (an example; not a default): every WRG1 grant its key signs is honoured,
# beside its proof and within its window, with the rights live-rooms §10.2 gives it. `rooms`
# narrows it to these room ids; empty, every room it grants.
# [[auth.authorities]]
# key = "<the authority's Ed25519 key, hex>"
# rooms = []
#
# An authority's revocation list (an example; not a default), as its key log's head lists it
# (WAI live-rooms §5): grant digests and viewer tags, 64 hex digits each. A grant either names
# is no grant: its SETUP is closed UNAUTHORIZED, grant_revoked.
# [[auth.revocations]]
# authority = "<the authority's Ed25519 key, hex>"
# revoked = ["<a grant digest or a viewer tag, hex>"]
# The operator's surface (src/control.rs): the relay takes grants, metered grants and
# revocations while it runs, ending only the sessions that presented a grant withdrawn; flushes
# its open sessions' counts into a window (WAI live-metering §3.8), or closes it (`close`) and
# seals it (`seal`) at the cut the relays carrying a room together joined; hands over a sealed
# window's counts by session, signed (`window`); signs a room's per-hop record; and drains its
# signed summaries (`summaries`, `ack`). `socket` is a Unix socket made mode 0600, `listen` a TCP address on loopback (any
# other stops the relay at start). `grants_path` is a file of [[grants]], [[authorities]],
# [[revocations]] and [[metered]] entries, read at start beside this file's and again on SIGHUP.
# `revocations_path` is a file the relay keeps every revocation it takes in, read at start: a
# revocation stays (WAI live-rooms §5). Absent: none of these, and the grants are this file's.
# `flush_interval_ms` has the relay flush and seal by itself that often; absent, nothing seals but
# a `flush` request. `hold_joule_ceilings` has the relay end a grant's sessions once its own share
# of the relay's figure passes the grant's joule ceiling (WAI live-metering §4.6), which it reads at
# a seal, so it needs `flush_interval_ms` and the relay refuses to start without it. Off, the
# ceilings are stated and the deployment's meter holds them through the room's records.
# `checkpoint_path` has the relay write its open window's counts there every
# `checkpoint_interval_ms`, at each seal and at a clean stop, and read them back at start (the last
# sealed window beside it, at `<path>.sealed`): a relay killed outright then loses at most that
# interval of its open sessions' counts, and the next window's statement says what span is
# unmetered. It is signed with the summary key, so it needs [summary] key_path.
# One request from a shell: wai-relay --control <socket> '{"op":"sessions"}'
[control]
# socket = "/run/joule-moq-relay/control.sock"
# listen = "127.0.0.1:4490"
# grants_path = "/etc/joule-moq-relay/grants.toml"
# revocations_path = "/var/lib/joule-moq-relay/revocations.toml"
# flush_interval_ms = 60000
hold_joule_ceilings = false
flush_grace_ms = 2000
# checkpoint_path = "/var/lib/joule-moq-relay/open-window.json"
checkpoint_interval_ms = 1000
# Relays this one dials (relay-to-relay cascading): a SUBSCRIBE, FETCH or TRACK_STATUS for a
# track no session here publishes, under one of `prefixes`, goes to the first whose session is
# open; namespaces published here under them are published there. Trust is one of ca_path,
# cert_sha256 (a pinned certificate) and native_roots; ws:// takes none. The token is read from
# token_path and must hash to token_sha256.
#
# [[upstream]]
# name = "home"
# url = "wss://home.example/moq"
# transport = "websocket"
# prefixes = [["*"]]
# native_roots = true
# token_sha256 = "<64 hex digits>"
# token_path = "/etc/joule-moq-relay/home.token"
# Probe tracks (draft-gurel-moq-probing-00), served under `.session`. Off unless enabled. The
# limits are the draft's Table 5, a direction; relay_concurrent and relay_rate_bps are the
# operator's, and max_objects_per_second is this relay's (0: none). A preset must resolve and
# fit the limits, or the relay does not start.
[probes]
enabled = false
max_duration_ms = 5000
max_bytes = 10000000
max_rate_bps = 50000000
max_object_bytes = 1048576
max_objects_per_second = 1000
session_concurrent = 1
min_gap_ms = 1000
session_bytes = 50000000
session_window_ms = 60000
relay_concurrent = 16
relay_rate_bps = 200000000
uplink_rate_margin_percent = 25
[[probes.presets]]
name = "sd-check"
label = "1 Mbit/s, video-like, 1 s"
probe = "d1000-f30-g30-r1000000"
[[probes.presets]]
name = "burst"
label = "Greedy, 1 MB"
probe = "b1000000"
# The relay's own monitoring (WAI moq-streaming-format 4.8, delivery-telemetry 11). Each part is
# off unless turned on. The relay is the resource whose id is derived from its summary key's
# public half: it serves its metrics under ("metrics.moq.arpa", "v1", <id>) (tracks "catalog"
# and "6") and its logs under ("logs.moq.arpa", "v1", <id>) (a track a severity, "0" to "7"), to
# a session whose grants cover a subscription there. metrics_track: one group every
# metrics_interval_ms, equal to GET /metrics at that instant. qlog_dir needs the relay built
# with the feature "qlog": one file a session, <session id>.sqlog. feedback: the relay declares
# SUBSCRIBE_FEEDBACK and consumes the hop Feedback Tracks its subscribers publish
# (draft-liu-moq-feedback-01); the observations a report comes to are appended to feedback_out
# where it is set.
[monitoring]
metrics_track = false
metrics_interval_ms = 1000
log_track = false
log_messages_per_second = 100
# qlog_dir = "/var/log/wai-relay/qlog"
qlog_max_events = 1000000
feedback = false
# feedback_out = "/var/lib/wai-relay/feedback.jsonl"
feedback_tracks_per_session = 16
feedback_report_bytes = 65536
feedback_objects = 100000
# Playout timing (draft-nandakumar-moq-tempo-00). stamp: "asked" rewrites HopTime for a
# subscription that carried the Sync Support parameter, "always" for every subscription (a
# subscriber at -18 or -16 carries none), "off" never. The host's clock must be synchronized.
# The three property types are placeholders, the draft's being unassigned: set them only to
# meet another implementation's choice.
[tempo]
stamp = "asked"
playout_delay_type = 0x3a71
capture_timestamp_type = 0x3a73
hop_time_type = 0x3a75
# The edge of stitched tracks (WAI ad-presentation 14.3, live-ads 14.8): a session origin (WSO1)
# written for each subscription to a session's composed track, [room, stitcher, video, stitched,
# D, K] with Track Name K, signed by this key, which the room's traffic policy names among its
# edges; read with "origins" on the control surface. Absent: the relay writes none.
# [ads_edge]
# key_path = "/etc/wai-relay/edge.key"