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]>
300 lines
14 KiB
TOML
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"
|