# 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" # WebTransport over HTTP/2 (draft-ietf-webtrans-http2-16): TLS with ALPN h2 on a TCP port, the # certificate above. Off unless set, and only in a relay built with the feature webtransport-h2. # webtransport_h2_listen = "127.0.0.1:4443" # 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 # Pause upstream subscriptions of a track while every downstream subscriber of it is paused. pause_upstream = true 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 # MAX_FILTER_RANGES: the Ranges one subscription's or fetch's Range Filters may hold, evaluated here. max_filter_ranges = 32 # 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 (Unix; 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 relay's delivery-gap claims (README, *Receipts*), off unless a directory is named: each # claim is written there as rcpt/.json, its chain's head beside it. # [receipts.gaps] # dir = "/var/lib/joule-moq-relay/gaps" # signer_id = "did:web:relay.example.net#relay-1" # unset: the did:key of the summary key # 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; the log says so at half the bound and past it. None is ever dropped. A # relay with no [control] surface (no socket, listen or flush_interval_ms) and an `out_path` # holds none: it hands each over on `out_path`, written whole and synced (WAI decision D95). [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 = "" # 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 = "" # revoked = [""] # 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 `.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 '{"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" # Namespace prefixes this relay sends to another relay rather than serve (a namespace moved # there): a SUBSCRIBE, FETCH or TRACK_STATUS for a track under `prefix` is answered REDIRECT # naming `uri` and the same Full Track Name, with `retry_interval` (0: do not retry here). # # [[redirect]] # prefix = ["moved-room"] # uri = "moqt://other.example:4443/moq" # retry_interval = 0 # 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", ) (tracks "catalog" # and "6") and its logs under ("logs.moq.arpa", "v1", ) (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, .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"