Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 29s
wai-sink-android native sink library / does this change touch it (push) Successful in 32s
wai-sink-swift native sink package / does this change touch it (push) Successful in 34s
wai-quantum for JavaScript / does this change touch it (push) Successful in 34s
wai-fleet service / does this change touch it (push) Successful in 34s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m29s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m18s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 1m30s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m31s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m20s
WAI operation catalogue / does this change touch it (push) Successful in 1m22s
wai-ledger service / does this change touch it (push) Successful in 1m22s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 13s
WAI Studio / does this change touch it (push) Successful in 2m19s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 3m24s
wai-fleet service / service · linux (push) Successful in 2m46s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 42s
joule-code conformance gate / joule-moq-relay, every target (push) Successful in 7m2s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 5m31s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 6m27s
joule-code conformance gate / joule-moq-ingest, every target (push) Successful in 8m14s
wai-quantum for JavaScript / javascript · linux (push) Successful in 5m37s
wai-audit collector and player integration / collector · linux (push) Successful in 3m29s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 10m5s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 10m3s
wai-ledger service / service · linux (push) Successful in 3m20s
wai-quantum studio and desktop / studio · browser (push) Successful in 7m58s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m54s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 10m7s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m39s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 50s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 13m15s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 11m31s
wai-fleet service / service · macos (push) Successful in 58s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 5m11s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m2s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m5s
wai release binaries build on Windows / the seven release binaries · windows (push) Successful in 17m41s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 9m4s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 3m35s
wai-ledger service / service · macos (push) Successful in 1m8s
WAI Studio / Studio in WebKit · linux (push) Successful in 21m29s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 13m3s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 18m13s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m57s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 12m33s
The deviations from MSF that draft-charlot-moq-wai-packaging-01 listed, and two more the
extended check found, fixed in every writer:
- version: each writer wrote MSF's root version as the number 1; MSF §5.1.1 requires a
string, "draft-01" for draft-ietf-moq-msf-01, which wai-pack, joule-moq-ingest,
joule-moq-remote, joule-moq-interop and joule-moq-web now write. No reader compared it.
The readers of a room's catalogs (the room page, catalogVersionKnown; the gateway's WHEP
session, catalog::version_known) now refuse a version they do not know (MSF §5.1.1),
reading "draft-01" and the number 1, which the room deployed at
wai.transaction.science/room/ (live since 2026-10-19) writes until it is deployed again;
the interop subscriber requires "draft-01", which its own publisher writes.
- the packager's cmaf floor tracks: isLive false and initRef (the floor name), with the
floor's initialization segment inline in initDataList (CMSF §3.1, the field name of
draft-ietf-moq-cmsf-01), after tracks (MSF §5.1.7): moq/catalog.json is RFC 8785
canonical but for that member, written last (packaging §8.4). FloorView carries the
init segment's bytes; pack_verify.py writes the same catalog independently;
catalogs/expected.json names each case's floor by its sequence header OBU and
timescale, from which both write the init segment.
- event timelines: the room page and the remote host wrote "mimetype" for MSF's mimeType,
and no depends (MSF §8.2); they write mimeType and depends [] (the events track is the
only track of its namespace, and MSF §5.2.14 names dependencies in the declaring
track's namespace). live-events §8.1 says so.
- wai-link: the contribution gateway's link track carries WLK1 link records, signed,
chained and verified by live_contribution_verify.py: neither envelopes ("wai") nor JSON
events ("eventtimeline"), and read by a subscriber that verifies a room's tracks. A real
interop surface, so it is defined rather than mapped: live-contribution §5.4 defines
it, moq-streaming-format §3 names it, the registry lists it as a catalog wire
identifier (export regenerated), and the -01 draft defines it in a new §9 (IANA table,
required fields, correspondence). The gateway writes it as before.
The corpus is regenerated by `wai-pack conformance regen` (pack-conformance/ and the
composed package of wai-web/compose); each other writer's test holds a committed sample
(joule-moq-ingest/tests/catalogs/{whip,contribution}.json,
joule-moq-remote/tests/catalogs/host.json, joule-moq-interop/tests/catalogs/wai-receipts.json,
joule-moq-web/e2e/catalogs/room.json), written by its JOULE_MOQ_*_WRITE_CATALOGS=1 run. The
room page's catalog moves into web/room/catalog.js, tested by catalog.test.mjs with the
page's other unit tests.
wai/ietf/catalog_check.py reads every writer's catalogs and, under --strict, holds MSF's
and CMSF's own rules: the version string, a name and isLive on every track, an MSF field
in another case, an event timeline's eventType, mimeType and depends, eventType elsewhere,
a cmaf track's initRef naming an inline initDataList entry that follows tracks; wai-link
tracks are held to the draft's §9. The repository checks run it with --strict. 23
catalogs of 6 writers pass with no note; the self-test plants 43 faults (a numeric
version, a cmaf track without isLive, mimetype and an undefined packaging value among
them) and catches each.
The draft stays -01 (the wire meaning of "wai" is unchanged): Implementation Status says
the deviations are fixed. xml2rfc 3.35.0 renders it with no warning; idnits 2.17.1: 0
errors, 0 flaws, 1 warning (no first-submission date on the datatracker).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
629 lines
40 KiB
Markdown
629 lines
40 KiB
Markdown
# WAI Extension: Live Contribution over SRT and RIST
|
||
|
||
> Status: Draft. Profiles two contribution transports with retransmission onto the tracks
|
||
> of a room, through the ingest gateway of [live-ingest](live-ingest.md): SRT
|
||
> (`draft-sharabayko-srt-01`) as a listener and as a caller, and RIST (VSF TR-06-1:2020,
|
||
> the Simple Profile; VSF TR-06-2:2024, the Main Profile). A publisher obtains a one-time
|
||
> session under its room grant; its encoder sends an MPEG transport stream over the
|
||
> transport; the gateway demultiplexes it and carries each access unit as one envelope on
|
||
> the room's tracks with first-hop receipts, as it carries a WHIP publisher's frames.
|
||
> Registers the record `wai.live.contribution`: the **contribution link record**, `WLK1`,
|
||
> which the gateway signs part by part while the link runs: what became of every sequence
|
||
> number (delivered at the first try, recovered within the receive window, dropped when its
|
||
> time passed, or outstanding when the link ended), the retransmission requests and the
|
||
> retransmissions, the round trips and how long each recovery took, chained part to part
|
||
> and naming the head of every media track's receipt chain. No new transport: WAI defines
|
||
> none by charter. The reference implementation is `joule-contrib` (the transports and the
|
||
> transport stream) and `joule-moq-ingest` (the gateway) in `joule-code/joule-code-rs/crates/`;
|
||
> the corpus is [`live-conformance/links/`](../live-conformance/links/), and its second
|
||
> implementation is `live-conformance/live_contribution_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: `draft-sharabayko-srt-01` (SRT, an Internet-Draft that has
|
||
> expired without publication, and the text SRT implementations are written to); SRT
|
||
> 1.5.7's stream ID guidelines and rejection codes, as live-ingest §13 reads them; VSF
|
||
> TR-06-1:2020 (RIST Simple Profile) and VSF TR-06-2:2024 (RIST Main Profile); RFC 3550,
|
||
> RFC 3551, RFC 4585 §6.2.1, RFC 5761 §4, RFC 8086, RFC 2784; RFC 3394; RFC 8018 §5.2; RFC
|
||
> 3686 §2.1; NIST SP 800-38A §6.5; ISO/IEC 13818-1 (transport streams) and ISO/IEC 13818-7
|
||
> §6.2 (ADTS); RFC 8032. A section number of one of those texts here is that revision's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: how a publisher obtains a contribution session and how the gateway admits its
|
||
link (§3); how the gateway uses each transport, the settings it requires and the defaults
|
||
it takes (§4); how the transport stream becomes the room's tracks (§5); the link record,
|
||
its account, its parts and their chain, how it is read and verified (§6); the refusals
|
||
(§7); the bounds (§8); what is implemented (§9); the patents the RIST texts declare (§10).
|
||
|
||
Out of scope: the transports themselves, which their texts define; SRT's rendezvous mode,
|
||
its file transfer and buffer modes, its packet filter and its socket groups; RIST's
|
||
bonding (TR-06-1 §5.4), multicast, SMPTE 2022-1 FEC, the Main Profile's DTLS (TR-06-2 §6),
|
||
full datagram mode (§5.3.1), EAP-SRP authentication (§7.5), null packet deletion and the
|
||
extended sequence number (§8), and the on-the-fly passphrase change (§7.4); egress over SRT
|
||
or RIST (live-ingest §13's `m=request`, which this revision refuses, §3.2); transcoding.
|
||
|
||
**Why an extension of its own.** live-ingest maps contribution protocols onto a room's
|
||
tracks, and §13 maps SRT. This text profiles the two retransmitting transports as a gateway
|
||
runs them, adds RIST, and defines the record of what each link lost and recovered, which
|
||
WHIP's transport does not need: a WebRTC session's losses are below the frame, and what
|
||
reaches the gateway is whole frames. live-ingest is changed only where it named SRT
|
||
"specified, not implemented" (live-ingest §13, §17).
|
||
|
||
Relationship to the core spec: one record is registered, `wai.live.contribution` (SPEC §5).
|
||
The payload capabilities a contribution track carries are SPEC §5's: `wai.video.avc`,
|
||
`wai.video.hevc` and `wai.audio.aac`.
|
||
|
||
## 2. Terms
|
||
|
||
- **Room**, **room clock**, **grant**, **principal**, **owner**, **gateway**, **first hop**:
|
||
as in live-ingest §2.
|
||
- **Link**: one SRT connection, or one RIST flow (a Simple Profile flow on its port pair,
|
||
or a Main Profile tunnel and the flow in it), that the gateway receives.
|
||
- **Session**: a contribution session (§3.1), the link it admits, and the tracks the
|
||
gateway publishes for it.
|
||
- **Sequence number**: SRT's 31-bit packet sequence number, or RTP's 16-bit one, each
|
||
extended into a 64-bit integer by taking, for every value read, the extension nearest
|
||
the highest one read so far (RFC 3550 §A.1). The first value read is extended to one
|
||
wrap above zero, so a value read before it in sequence can lie below it.
|
||
- **Delivery time**: when the receiver hands a packet on. SRT: its TSBPD time,
|
||
`draft-sharabayko-srt-01` §4.5.1. RIST: the time it was expected, plus the receiver's
|
||
buffer (§4.2).
|
||
- **Receive window**: SRT's negotiated latency (§4.4 of the draft); RIST's receiver
|
||
buffer.
|
||
- µs: microseconds. Integers are little-endian where this text writes bytes.
|
||
|
||
## 3. Admission
|
||
|
||
### 3.1 The session resource
|
||
|
||
A publisher opens a contribution session with an HTTP `POST` to the gateway's `…/srt` or
|
||
`…/rist` resource, with the bearer token and the proof of possession of live-ingest §3.1,
|
||
admitted by the checks of live-ingest §3.2 in their order and refused with their reasons
|
||
and statuses. The grant MUST give the publish power for audio or for video (live-ingest
|
||
§3.3): otherwise `403`, `publish_not_permitted`. The body is a JSON object, empty for the
|
||
defaults (§3.2, §3.4); one that is not a JSON object is `400`, `body_malformed`.
|
||
|
||
The gateway answers **`201`** with `Location` `…/srt/s/<session>` or `…/rist/s/<session>`
|
||
and a JSON body naming the session: its `session`, 32 lowercase hexadecimal digits from 128
|
||
random bits; its `protocol` (`srt`, `rist-simple`, `rist-main`); the `host` and `port`
|
||
to connect to; and the `expires_s`, the time within which the link must connect, after
|
||
which the session is dropped (60 seconds by default). A session is **one-time**: the
|
||
first link that presents it spends it (§3.2), and a link that fails after that (a wrong
|
||
passphrase) does not get it back.
|
||
|
||
The session's resource answers only the token that opened it, with a proof scoped to it
|
||
(live-ingest §3.4): `DELETE` ends the session, or cancels one not yet connected, `200`;
|
||
`GET` and `HEAD` are `204`; another token is `403`, `not_the_session_token`; an unknown or
|
||
ended session is `404`, `session_unknown`. A gateway at its bound of sessions answers a
|
||
`POST` `503` with `Retry-After`, `session_limit` (§8).
|
||
|
||
### 3.2 SRT: the stream ID and the listener's checks
|
||
|
||
With an empty body, the answer to `POST …/srt` carries the `stream_id` the caller presents,
|
||
`#!::r=<room id>/<owner>,m=publish,s=<session>` as live-ingest §13 gives it, the room id
|
||
and the owner in 64 lowercase hexadecimal digits each; the `passphrase`, 32 characters
|
||
of base64url from 192 random bits; the `pbkeylen`, the AES key length in bytes the caller
|
||
SHOULD use; and the listener's `latency_ms`. The gateway's SRT listener checks a caller's
|
||
CONCLUSION handshake in this order, and rejects it with the first code that applies, as a
|
||
handshake type of 1000 plus the code (`draft-sharabayko-srt-01` Table 7, and live-ingest
|
||
§13's codes):
|
||
|
||
1. The HSREQ extension is present and the handshake is version 5: otherwise 1004 (REJ_ROGUE)
|
||
or 1008 (REJ_VERSION). The STREAM flag (buffer mode) is clear: otherwise 1012
|
||
(REJ_MESSAGEAPI).
|
||
2. The stream ID is in the `#!::` key-value syntax: otherwise **1401**.
|
||
3. Its `m` is `publish`: otherwise **1405**. `m=request`, live-ingest §13's play mode, is
|
||
refused 1405 in this revision, which implements no egress.
|
||
4. Its `s` is a session this gateway opened for SRT, not spent and not past its time:
|
||
otherwise **1401**.
|
||
5. Its `r` names a room the gateway holds: otherwise **1404**; and that session's room and
|
||
owner: otherwise **1403**.
|
||
6. The session's grant is good now (live-rooms §4.3): otherwise **1401**.
|
||
7. The gateway holds fewer contribution sessions than its bound: otherwise **1402**.
|
||
8. The owner has no other contribution session running: otherwise **1409**.
|
||
|
||
The session is then spent, and the handshake's key material is checked: a caller that
|
||
sends none, or one of a cipher other than AES-CTR, is refused 1011 (REJ_UNSECURE), since
|
||
every listener session is encrypted; one whose wrap does not unwrap under the session's
|
||
passphrase is refused 1010 (REJ_BADSECRET).
|
||
A listener that holds no passphrase for a caller that sends key material refuses it 1011
|
||
too: encryption is enforced both ways (`draft-sharabayko-srt-01` §6.1.5).
|
||
|
||
### 3.3 SRT: the gateway calling
|
||
|
||
A contribution encoder is often itself the SRT listener. A `POST …/srt` whose body names
|
||
`call`, the encoder's `ip:port`, has the gateway call it: the body MAY name the
|
||
`passphrase`, the `pbkeylen`, the `stream_id` the gateway presents and the `latency_ms`. A
|
||
gateway calls only the addresses its configuration lists (an address and port, or an
|
||
address for any port): another is `403`, `call_not_permitted`, so a grant cannot make the
|
||
gateway send to an address of the requester's choosing. The gateway answers once the
|
||
handshake completes: `201` as §3.1, or `502` with `call_rejected` (the encoder's code in
|
||
the detail) or `call_failed`.
|
||
|
||
### 3.4 RIST: the port, the CNAME, the PSK
|
||
|
||
RIST has no stream ID, so a RIST session is a port of its own. The body of `POST …/rist`
|
||
MAY name the `profile`, `simple` (the default) or `main`; another is `400`,
|
||
`body_malformed`. The answer gives the `port` P (for the Simple Profile an even port,
|
||
whose RTCP is P + 1), the `cname`, which is the session ID, and the receiver's `buffer_ms`;
|
||
for the Main Profile, the PSK `passphrase` and its key length `aes`, 128 or 256 bits.
|
||
|
||
The receiver serves one flow: the first whose RTCP SDES CNAME (TR-06-1 §5.2.5) is the
|
||
session's, and, in the Main Profile, whose tunnel packets decrypt under the passphrase and
|
||
read (§4.3). Until then it takes nothing as the session's; a flow with another CNAME is
|
||
counted and ignored. From then on it takes the flow's packets from that address alone
|
||
(the Simple Profile: the sender's address and its SSRC, the low bit clear; the Main
|
||
Profile: the tunnel's address and port). The Simple Profile has no encryption: on a path
|
||
an attacker sees, its CNAME is visible and its flow can be raced; a deployment that needs
|
||
more uses the Main Profile under PSK, or SRT.
|
||
|
||
## 4. The transports
|
||
|
||
### 4.1 SRT
|
||
|
||
The gateway's listener serves many callers on one UDP port, each connection found by the
|
||
destination socket ID its packets carry (`draft-sharabayko-srt-01` §4.1). It speaks the
|
||
caller-listener handshake of §4.3.1 with the HSv5 extensions; its INDUCTION reply carries a
|
||
SYN cookie made from the caller's address, port and the minute under a secret of the
|
||
listener's, and a CONCLUSION whose cookie is neither this minute's nor the last is
|
||
ignored. It sends its CONCLUSION reply again for a repeated CONCLUSION.
|
||
|
||
Live mode, as the draft's §7.1 gives it, and nothing else: every data packet one message
|
||
(PP = 11); TSBPD and too-late packet drop (the HSREQ flags TSBPDSND, TSBPDRCV, TLPKTDROP);
|
||
periodic NAKs; CRYPT and REXMITFLG set. The connection's latency each way is the larger of
|
||
the two ends' (§4.4); the gateway's is its configured latency (120 ms by default), and a
|
||
deployment SHOULD set it to several round trips of the paths it serves (live-ingest §13).
|
||
|
||
- **Delivery** (§4.5): a packet is delivered at `base + timestamp + latency + drift`, the
|
||
timestamp extended past its 32 bits, `base` taken when the CONCLUSION arrived. A first
|
||
data packet more than 2 s from where `base` puts it re-takes `base` from itself (a peer
|
||
that stamps its handshake on another clock than its data). The drift tracer (§4.7) adds
|
||
the mean, over 1 000 ACKACKs, of how far each arrived (less half the RTT) from where
|
||
`base` put it, once the mean passes 5 ms.
|
||
- **Too-late drop** (§4.6): when the next packet to deliver is missing and the first
|
||
packet after it that has arrived is due, the missing ones are dropped.
|
||
- **ACK** (§4.8.1): a full ACK every 10 ms while the acknowledged position moves, and at
|
||
least every 100 ms; a light ACK every 64 packets.
|
||
- **NAK** (§4.8.2): at once for each gap, and periodically every (RTT + 4 × RTTVar) / 2
|
||
with a 20 ms floor, for every packet still missing.
|
||
- **A stalled tail**, sending: a receiver finds a gap only behind a later arrival, so when
|
||
the acknowledged position has not moved for 2 × RTT + 20 ms and the newest packet held
|
||
went out that long ago, the newest is sent again with the R flag; the wait doubles with
|
||
each such probe, to at most 16 times the first, until an ACK takes a packet.
|
||
- **RTT** (§4.10) from ACK/ACKACK pairs, from 100 ms and 50 ms.
|
||
- **Encryption** (§6): AES-CTR, the SEK of 16, 24 or 32 bytes wrapped (RFC 3394) under a
|
||
KEK from PBKDF2-HMAC-SHA1 over the last 64 bits of the salt with 2 048 iterations; the
|
||
counter block the salt's first 112 bits XOR the packet index at its bytes 10 to 13, the
|
||
block counter in its last two. A refreshed key (§6.1.6, a user-defined control packet of
|
||
subtype KMREQ) is taken and answered with the same message as KMRSP.
|
||
- **The end**: a SHUTDOWN ends the link (the gateway sends three, since none is
|
||
acknowledged), and so does 5 s with nothing received.
|
||
|
||
What the link then holds is delivered at once; what is missing among it is outstanding
|
||
(§6.3).
|
||
|
||
### 4.2 RIST Simple Profile
|
||
|
||
RTP on an even port P and RTCP on P + 1 (TR-06-1 §5.1.1), the payload type 33 (MP2T, RFC
|
||
3551) with a transport stream of seven packets a datagram as a rule. The receiver:
|
||
|
||
- **holds every packet for its buffer** (§5.3.1): a sequence number is delivered `buffer`
|
||
after it was expected, which is its arrival, or, for one found missing, the arrival that
|
||
showed the gap; in order; one still missing then is dropped. The buffer is the
|
||
gateway's configured one (1 000 ms by default, TR-06-1 Appendix B);
|
||
- **requests what is missing** (§5.3.2), as range requests (the RTCP APP packet named
|
||
`RIST`, subtype 0) or bitmask requests (RFC 4585 §6.2.1): first a reorder interval (20 ms)
|
||
after it was found missing, then again every interval, 1.5 × the RTT where the RTT echo has
|
||
measured it, otherwise (buffer − reorder) / 7, at most 7 times, and not once its delivery
|
||
time is closer than half a round trip;
|
||
- **reports** every 100 ms (§5.2.1): an RR with its report block once data has arrived, and
|
||
an SDES with its CNAME; once a second an RTT echo request (§5.2.6); and answers the
|
||
sender's echo requests at once. RTCP goes to the address of the last valid RTCP received
|
||
(§5.1.1 rule 3);
|
||
- **counts as a retransmission** a packet whose SSRC has its low bit set (§5.3.3).
|
||
|
||
A RIST receiver cannot know of packets lost before the first it receives: its account
|
||
begins there (§6.8). A flow ends when nothing of it has arrived for 5 s; RIST has no
|
||
shutdown message.
|
||
|
||
### 4.3 RIST Main Profile
|
||
|
||
One UDP port P, GRE over UDP (RFC 8086) as TR-06-2 §5 constrains it; the gateway is the
|
||
tunnel's server. It reads:
|
||
|
||
- the GRE header's C, K and S bits, to find its size; its Reserved0 bits 10 to 12 as RV
|
||
(000 TR-06-2:2020, 001 TR-06-2:2021, 010 TR-06-2:2022 and later; 011 and 100 read as 010;
|
||
101 to 111 discarded) and bit 9 as H (a 256-bit PSK key where RV ≥ 001), §5.1;
|
||
- the VSF EtherType 0xCCE0 and its packet header (§5.2): type 0x0000, subtype 0x0000 a
|
||
reduced overhead packet, 0x8000 a keep-alive, 0x8001 a future nonce announcement; and the
|
||
legacy EtherTypes 0x88B6 (reduced overhead) and 0x88B5 (keep-alive), §5.3.2, §5.6.3;
|
||
- a reduced overhead packet as its UDP ports and payload (§5.3.2): RTP or RTCP of a Simple
|
||
Profile flow, told apart as RFC 5761 §4 tells them, served as §4.2 serves one;
|
||
- with K set, PSK (§7.2, §7.3): the GRE payload (everything after the GRE header, the VSF
|
||
header among it) in AES-CTR (RFC 3686 §2.1) under PBKDF2-HMAC-SHA256 of the passphrase
|
||
over the four bytes of the GRE nonce, 1 024 iterations, 16 or 32 bytes by H, from the
|
||
counter block of the GRE sequence number and twelve zero bytes. A key is derived once per
|
||
nonce. RV 000's counter block, which §7.2 calls insecure, is refused: counted, never
|
||
decrypted.
|
||
|
||
The server sends a keep-alive (§5.6.3: a MAC address, the capability flags, here V) once
|
||
the client's first message arrives and every second after (§5.6.2), in the client's format
|
||
(the VSF EtherType or the legacy one) and, under PSK, encrypted under a nonce of its own.
|
||
Its RTCP goes back through the tunnel from the virtual port the client's RTCP was sent to,
|
||
to the one it came from. A keep-alive whose D flag is set ends the link (§5.6.5); so does
|
||
5 s with nothing received. Packets of full datagram mode, EAPOL and unknown subtypes are
|
||
discarded, and keep the link alive (§5.1).
|
||
|
||
## 5. The stream onto the tracks
|
||
|
||
### 5.1 Demultiplexing
|
||
|
||
The link delivers a transport stream (ISO/IEC 13818-1) at its delivery times. The gateway
|
||
reads its PAT and the first program's PMT, each section's CRC checked, and reassembles each
|
||
elementary stream's PES packets, resynchronising on the sync byte. It carries the first
|
||
video stream the PMT declares of stream type 0x1B (`wai.video.avc`) or 0x24
|
||
(`wai.video.hevc`), and the first of 0x0F (`wai.audio.aac`), each to the media track of its
|
||
kind (live-ingest §4.1), where the grant gives the publish power for that kind; other
|
||
streams are counted and not carried.
|
||
|
||
A PES is complete at the next packet of its PID that starts one, or when it holds the
|
||
length its header states. A PES of which a packet did not arrive, a jump in its PID's
|
||
continuity counter without the discontinuity indicator, is **dropped whole** and counted:
|
||
the gateway never carries a unit cut short. A packet whose continuity counter repeats the
|
||
one before is a duplicate and is ignored. A lost packet that began a PES also ends the PES
|
||
before it, which can then not be told whole, and is dropped with it.
|
||
|
||
### 5.2 Objects
|
||
|
||
| stream | capability | an object's payload |
|
||
|---|---|---|
|
||
| 0x1B | `wai.video.avc` | the PES payload: one access unit, Annex B, as the encoder wrote it |
|
||
| 0x24 | `wai.video.hevc` | the PES payload: one access unit, Annex B |
|
||
| 0x0F | `wai.audio.aac` | one ADTS frame (ISO/IEC 13818-7 §6.2), whole, its header included |
|
||
|
||
A PES of AAC holding several ADTS frames gives one object for each. Groups are live-ingest
|
||
§5.2's: a video group begins at each IDR access unit (H.264) or IRAP (HEVC, NAL unit types
|
||
16 to 23), an audio group holds one window of `audio_group_ms`.
|
||
|
||
### 5.3 Time
|
||
|
||
A PES's PTS, 33 bits at 90 kHz, is extended past its 33 bits and is the media time of
|
||
live-ingest §7's mapping, with a rate of 90 000, for video and audio alike; the `k`-th ADTS
|
||
frame of a PES whose PTS is `p` at sampling rate `f` is at `p + ⌊k × 1024 × 90 000 / f⌋`. A
|
||
frame's arrival is the time its last TS packet left the link, on the room clock. The
|
||
manifest's `live` member (live-ingest §6) carries the extended PTS as `rtp` and 90 000 as
|
||
`rate`.
|
||
|
||
Because the link delivers each packet at its sender's time plus a constant (SRT's TSBPD,
|
||
RIST's buffer), the arrivals follow the publisher's clock, and §7's bound applies to the
|
||
variation of the path and the encoder; a packet recovered with a margin above 0 (§6.2)
|
||
arrives on time. An SRT packet recovered after its own delivery time but before it would
|
||
be dropped (a margin of 0, §6.1) is delivered late by as much, as is a packet delivered at
|
||
the first try that the receiver took after its delivery time (§6.8). A frame whose time
|
||
lies past `max_skew_ms` from its arrival re-anchors the mapping, as live-ingest §7.1 says.
|
||
|
||
### 5.4 Tracks, receipts and the catalog
|
||
|
||
The tracks, their names and priorities, the groups, the receipts and their chains are
|
||
live-ingest §4, §5 and §8's, under the session ID of §3.1 (live-ingest §8.2), so a session
|
||
signs chains of its own. The gateway publishes the owner's catalog, the media tracks the
|
||
grant lets it publish, and the **link track**, `(room id, owner)` / `link`, with its
|
||
receipts track `link_receipts`, before any frame. The catalog (live-ingest §4.3) declares,
|
||
after the media tracks, the link track with `packaging` `wai-link`, `wai_receipts` `true`,
|
||
its `wai_receipt_alias` and `wai_receipts_track`, then its receipts track.
|
||
|
||
**The packaging value `wai-link`.** This extension defines `"wai-link"` as a packaging value
|
||
of MSF (MSF §5.2.4), for the link track; `draft-charlot-moq-wai-packaging` defines it for MSF
|
||
in the same terms. On a track of packaging `"wai-link"`:
|
||
|
||
- every MOQT object is exactly one link record (§6.2), whole, from `magic` through `sig`,
|
||
with nothing before or after it; each group carries one, a part (§6.4);
|
||
- its catalog entry carries MSF's `namespace`, `name` and `packaging`, `isLive` (MSF §5.2.7),
|
||
and the receipts fields of [moq-streaming-format](moq-streaming-format.md) §4: `wai_receipts`,
|
||
required, and, where it is true, `wai_receipt_alias` and `wai_receipts_track`, which names
|
||
the track of packaging `"wai-receipts"` in its namespace that carries its receipts, with
|
||
moq-streaming-format §4.1's rules for both (an alias and a receipts track of its own). It
|
||
carries no other field: no `role`, no codec field, no `wai_capabilities`;
|
||
- each record's receipt commits to the object as jwp-receipts §2.1's content hash does to
|
||
an envelope, over the record's bytes;
|
||
- a receiver that does not recognise `"wai-link"` does not interpret the objects; and a
|
||
record is not an envelope, so a receiver never dispatches one as a `"wai"` object.
|
||
|
||
A receiver reads and verifies each record by §6.5 and a session's records by §6.6.
|
||
|
||
## 6. The link record
|
||
|
||
### 6.1 What it states
|
||
|
||
The link record is the gateway's statement of what its link carried, as a receiver sees
|
||
it. A receiver passes sequence numbers in ascending order, each at its delivery time, and
|
||
each comes to exactly one end:
|
||
|
||
- **delivered at the first try**: its first send arrived before anything found it missing;
|
||
- **recovered**: it was found missing (a gap behind a later arrival), then arrived, by a
|
||
retransmission or late from reordering, before it was dropped; or its first arrival
|
||
was a retransmission (SRT's R flag, RIST's retransmission SSRC) with nothing behind it
|
||
yet to show the gap, as when a sender probes a stalled tail (§4.1), and it is found
|
||
missing as it arrives (a recovery of 0 µs);
|
||
- **dropped**: its time passed without it: for SRT, the delivery time of the first packet
|
||
after it that has arrived (§4.1's too-late drop); for RIST, its own delivery time (§4.2);
|
||
- **outstanding**: the link ended before it arrived and before its delivery time.
|
||
|
||
A packet **arrives** when the receiver takes it, which on a busy host can be well after it
|
||
reached the receiver's socket. An SRT packet that arrives at or after its own delivery time,
|
||
but before the first packet after it that has arrived is due, has not been dropped: it is
|
||
delivered at once, in order, and is recovered with a margin of 0 (§6.2). One that arrives
|
||
at or after that point is late (§6.3), whether or not the receiver had dropped its sequence
|
||
number before taking it: a receiver resolves what has fallen due before it takes each
|
||
packet. For RIST a packet's own delivery time is where it is dropped, so there is no such
|
||
case.
|
||
|
||
The record is cut into **parts**. A part covers the sequence numbers passed while it was
|
||
open, and the events (requests sent, retransmissions and duplicates received, late
|
||
arrivals, round trips) that happened while it was open, on the room clock from `start` to
|
||
`end`; the next part begins where it ends. The gateway signs a part every
|
||
`record_interval_ms` (10 000 by default) that has anything in it, once the link's first
|
||
sequence number has been passed, and the last part when the session ends; round trips
|
||
measured before the first sequence number fall into the first part.
|
||
|
||
### 6.2 Layout
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `magic` | 4 | `WLK1` (ASCII) |
|
||
| `version` | 1 | 1 |
|
||
| `room` | 32 | the room id |
|
||
| `hop` | 32 | the gateway's key, which signs the record and the tracks' receipts |
|
||
| `principal` | 32 | the owner |
|
||
| `grant` | 32 | the grant's digest (live-rooms §4.2) |
|
||
| `session` | 16 | the session ID (§3.1) |
|
||
| `part` | 4 | u32: the part's index, from 0 |
|
||
| `parent` | 32 | the digest of part `part − 1`; 32 zero bytes for part 0 |
|
||
| `protocol` | 1 | 1 SRT, 2 RIST Simple Profile, 3 RIST Main Profile |
|
||
| `mode` | 1 | 1 the gateway listened, 2 it called |
|
||
| `cipher` | 2 | u16: the AES key length in bits; 0 in the clear |
|
||
| `last` | 1 | 1 for the session's last part, 0 otherwise |
|
||
| `ended` | 1 | the last part: why the session ended (§6.3); 0 otherwise |
|
||
| `start`, `end` | 8 each | u64: the part's span on the room clock, µs, [`start`, `end`) |
|
||
| `window` | 8 | u64: the receive window, µs |
|
||
| `seq_first`, `seq_count` | 8 each | u64: the sequence numbers the part covers, [`seq_first`, `seq_first + seq_count`) |
|
||
| `delivered`, `first_try`, `dropped`, `outstanding` | 8 each | u64 (§6.3) |
|
||
| `late`, `duplicates` | 8 each | u64 (§6.3) |
|
||
| `bytes` | 8 | u64: the payload bytes delivered |
|
||
| `requests`, `requested`, `retransmitted` | 8 each | u64 (§6.3) |
|
||
| `rtt` | 16 | four u32: the round trips' `samples`, `min`, `median`, `max`, µs |
|
||
| `recovery` | 16 | four u32: the recoveries' `samples`, `min`, `median`, `max`, µs |
|
||
| `margin_min` | 4 | u32: the least time, µs, a recovered packet arrived before its delivery time, each such time taken at most `window`, and as 0 for one that arrived at or after it (§6.1); 0 with none recovered |
|
||
| `tracks` | 1 + 80 × *n* | a u8 count *n*, then each track's head (§6.4): the track digest (32; live-events §3.1), its receipt chain's `track_alias` (u64; live-ingest §8.2), the groups signed on it so far (u64), the link hash of the latest of their group receipts (32; jwp-receipts §2.4.1), zero with none |
|
||
| `runs` | 3 + 20 × *k* | a u16 count *k*, a u8 `truncated`, then each loss run: `first` (u64), `count`, `recovered`, `dropped` (u32 each) |
|
||
| `sig` | 64 | Ed25519 by `hop` (§6.5) |
|
||
|
||
A median is the lower middle sample of an even count. A loss run is a maximal run of
|
||
consecutive sequence numbers of the part none of which arrived at the first try; its
|
||
`count − recovered − dropped` are outstanding. A part lists at most 512 runs, in ascending
|
||
order; with more, it lists the first 512 and sets `truncated`.
|
||
|
||
### 6.3 The account
|
||
|
||
- `delivered` counts the part's sequence numbers delivered, `first_try` those of them that
|
||
arrived at the first try, so `delivered − first_try` were **recovered**; `dropped` and
|
||
`outstanding` count the rest. **`delivered + dropped + outstanding = seq_count`**.
|
||
- `late` counts packets that arrived after their sequence number was dropped; `duplicates`
|
||
those that arrived again for a sequence number already held or delivered.
|
||
- `requests` counts the retransmission requests the gateway sent (SRT NAKs, RIST bitmask
|
||
and range requests), `requested` the sequence numbers they named, repeats counted;
|
||
`retransmitted` the packets that arrived marked as retransmissions (SRT's R flag, RIST's
|
||
SSRC low bit).
|
||
- `rtt`: the round trips the gateway measured (SRT: from each ACK to its ACKACK; RIST: from
|
||
each echo request to its response, less the stated processing delay).
|
||
- `recovery`: for each recovered packet, from its being found missing to its arrival;
|
||
`recovery.samples = delivered − first_try`. An SRT recovery is not bounded by `window`: it
|
||
runs from when the packet was found missing, and the window from where its timestamp puts
|
||
it on the receiver's clock (§4.1), which can be later.
|
||
- `ended`: 1 the peer ended the link (SRT SHUTDOWN, a RIST keep-alive with D); 2 nothing
|
||
arrived for the idle time; 3 the session was DELETEd; 4 the gateway stopped; 5 the relay
|
||
was lost; 6 the relay fell behind; 7 another reason.
|
||
|
||
### 6.4 Parts, the chain, the link track
|
||
|
||
Each part names its predecessor by digest (`parent`), so a part withheld, altered or
|
||
reordered breaks the chain (§6.6). Each part's `tracks` names, at its `end`, the head of
|
||
the receipt chain of every media track the session published, in ascending order of track
|
||
digest: the record binds what the link carried to the receipts that cover the objects made
|
||
from it. A verifier that holds a track's receipts checks that the head is the link hash of
|
||
the last group receipt it has, and that `groups` is how many it has.
|
||
|
||
The gateway publishes each part, as signed, as the one object of a group of the link track
|
||
(§5.4), its group IDs following live-ingest §5.3's rule, and its receipts on
|
||
`link_receipts`, signed by the gateway as the media's are: a subscriber that verifies the
|
||
room's tracks verifies the link record with them. The gateway also keeps each part, and
|
||
MAY append it to a file.
|
||
|
||
### 6.5 Reading and verifying a part
|
||
|
||
The **digest** of a record is SHA-256 over its bytes before `sig`; its **signature** is
|
||
Ed25519 by `hop` over the 17 bytes `wai:contrib-link` (ASCII) and 0x01, followed by the bytes
|
||
the digest covers. A verifier takes these steps in order and refuses at the first that
|
||
fails, with its reason:
|
||
|
||
1. The record is at most 11 295 bytes (§6.7): otherwise `record_too_large`.
|
||
2. It reads, field by field in §6.2's order, to exactly its last byte, with `tracks` at most
|
||
8 and runs at most 512: `truncated` when it ends early; `unknown_version` for a version
|
||
other than 1; `malformed` for another magic (live-recording §5.1's clip record, `WCL1`,
|
||
among them), a byte after `sig`, a count past its bound, or a `last` or `truncated` other
|
||
than 0 and 1.
|
||
3. The signature verifies under `hop`, strictly, as SPEC §7 *Signatures* requires: a key
|
||
that fails that rule's steps 1 or 2 is `bad_key`, a signature that fails a later step
|
||
`bad_signature`.
|
||
4. The link, else `link_invalid`: `protocol` is 1, 2 or 3; `mode` 1 or 2; `cipher` 0, 128,
|
||
192 or 256 for SRT, 0 for the Simple Profile, 0, 128 or 256 for the Main Profile;
|
||
`last` is 1 exactly when `ended` is not 0, and `ended` is at most 7; `start` < `end`;
|
||
`window` > 0; `part` is 0 exactly when `parent` is zero; the tracks are in strictly
|
||
ascending order of digest, digests compared byte by byte from the first, none zero, and
|
||
each `head` is zero exactly when its `groups` is 0.
|
||
5. The account, else `account_invalid`: `first_try` ≤ `delivered`; `delivered + dropped +
|
||
outstanding = seq_count`; `outstanding` is 0 unless `last`; `recovery.samples = delivered
|
||
− first_try`; for each of `rtt` and `recovery`, with no sample all four values are 0,
|
||
and with samples `min` ≤ `median` ≤ `max`; `margin_min` is 0 with nothing recovered,
|
||
and at most `window`;
|
||
`requested` ≥ `requests`; `bytes` is 0 when `delivered` is.
|
||
6. The runs, else `runs_invalid`: with `truncated` set there are exactly 512; each run's
|
||
`count` is above 0, its `recovered + dropped` at most its `count`, it lies within
|
||
[`seq_first`, `seq_first + seq_count`), and it begins after the one before ends, with at
|
||
least one sequence number between. Not truncated, the runs' `count`s sum to `seq_count −
|
||
first_try`, their `recovered` to `delivered − first_try`, their `dropped` to `dropped`,
|
||
and their outstanding to `outstanding`; truncated, each sum is at most that.
|
||
|
||
### 6.6 A series
|
||
|
||
A session's parts, in order of `part`, are a **series**. A verifier takes the parts in
|
||
turn and refuses at the first that fails, giving its index: each part verifies (§6.5),
|
||
then, for the first, its `part` is 0, otherwise `chain_broken`; and for each after the
|
||
first:
|
||
|
||
1. `room`, `hop`, `principal`, `grant`, `session`, `protocol`, `mode`, `cipher` and `window`
|
||
are the part before's; the part before is not `last`; `part` is its `part` plus one;
|
||
`parent` is its digest; every track the part before names is named again, with the same
|
||
`track_alias` and either more `groups` or the same `groups` and `head`: otherwise
|
||
`chain_broken`. A part MAY name a track the part before did not.
|
||
2. `start` is the part before's `end`, and `seq_first` its `seq_first + seq_count`:
|
||
otherwise `sequence_gap`.
|
||
|
||
So the parts of a series account for every sequence number from the first part's
|
||
`seq_first` to the last part's end, and every microsecond from its `start` to its end,
|
||
each once. A series that does not end with a `last` part is the record of a session still
|
||
running, or one whose later parts are withheld: a verifier that needs the whole session
|
||
requires the last part. The rule `first_try` ≤ `delivered` of §6.5 step 5 is implied by
|
||
the rule on `recovery.samples`; it is stated so that no subtraction there is negative.
|
||
|
||
### 6.7 The size bound
|
||
|
||
A record is at most 415 + 80 × 8 + 20 × 512 = 11 295 bytes: 415 the fixed fields and the
|
||
signature, eight track heads, 512 runs. The bound does not depend on the stream's rate or
|
||
length: a part's losses beyond 512 runs are counted in its sums and marked `truncated`, and
|
||
a long session has more parts, not larger ones.
|
||
|
||
### 6.8 What the record does not say
|
||
|
||
- It is the receiver's account: what the gateway received, in its window. A packet the
|
||
sender never sent, or dropped from its own buffer, is a loss here like any other.
|
||
- A RIST receiver cannot know of packets lost before the first it received, nor after the
|
||
last: RIST states neither. An SRT receiver knows the first from the handshake's initial
|
||
sequence number.
|
||
- A recovered packet arrived before it was dropped. For SRT that can be after its own
|
||
delivery time (§6.1): such a packet counts a margin of 0 (§6.2), and the record does not
|
||
say how many there were, nor how late each was delivered. A `margin_min` above 0 says that
|
||
every packet recovered in the part arrived before its delivery time.
|
||
- It does not say when a packet delivered at the first try arrived against its delivery
|
||
time. One the receiver took after it (a receiver behind its socket by more than the
|
||
window, or a path slower than the latency), with nothing missing before it, is delivered
|
||
at once and counted at the first try.
|
||
- It does not say whether the unit a recovered packet was part of was later carried (a PES
|
||
can still be cut by a packet dropped elsewhere, §5.1). The session record (live-ingest
|
||
§16) counts the units dropped.
|
||
- The figures are the gateway's clock's. The record states no energy: live-ingest §16's
|
||
session record and live-metering's per-hop record do.
|
||
|
||
## 7. Refusals
|
||
|
||
At the session resource (§3), beside live-ingest §14's:
|
||
|
||
| reason | status | raised at |
|
||
|---|---|---|
|
||
| `publish_not_permitted` | 403 | §3.1, a grant that publishes neither audio nor video |
|
||
| `body_malformed` | 400 | §3.1, §3.3, §3.4 |
|
||
| `call_not_permitted` | 403 | §3.3 |
|
||
| `call_rejected`, `call_failed` | 502 | §3.3 |
|
||
| `session_limit` | 503, with `Retry-After` | §8 |
|
||
| `session_unknown` | 404 | §3.1 |
|
||
| `not_found` | 404 | a gateway without an SRT listener, or serving no RIST |
|
||
|
||
At the SRT listener (§3.2): 1401, 1402, 1403, 1404, 1405, 1409 of live-ingest §13; 1004,
|
||
1008, 1010, 1011 and 1012 of `draft-sharabayko-srt-01` Table 7.
|
||
|
||
Of the link record (§6.5, §6.6): `record_too_large`, `truncated`, `malformed`,
|
||
`unknown_version`, `bad_key`, `bad_signature`, `link_invalid`, `account_invalid`,
|
||
`runs_invalid`, `chain_broken`, `sequence_gap`.
|
||
|
||
## 8. Bounds
|
||
|
||
| bound | default | past it |
|
||
|---|---|---|
|
||
| contribution sessions at once, open or running | 16 | `503`; the listener 1402 |
|
||
| a session's time to connect | 60 s | dropped |
|
||
| a session's rate, all streams, over `bitrate_window_ms` | 80 000 kbit/s | the session is ended, `bitrate_exceeded` |
|
||
| a link's time with nothing received | 5 s | the link is ended, `idle` |
|
||
| the largest frame | live-ingest §10's | dropped and counted |
|
||
| a part's loss runs | 512 | the first 512, `truncated` |
|
||
| the tracks a record names | 8 | — |
|
||
| a RIST session's rate, its buffer holding fewer than 32 768 packets | 32 768 × 1 316 × 8 bits a buffer: 344 981 kbit/s with 1 000 ms | the session is ended, `bitrate_exceeded` |
|
||
|
||
RTP's sequence numbers are 16 bits, and a receiver places each packet within half their
|
||
space of the highest it has received (§2): a packet more than 32 768 behind it is placed a
|
||
wrap ahead. So a RIST buffer must hold fewer than 32 768 packets, and the gateway bounds a
|
||
RIST session's rate by its buffer as the table gives; a deployment that needs more shortens
|
||
the buffer, or uses SRT, whose sequence numbers are 31 bits.
|
||
|
||
## 9. Status and conformance
|
||
|
||
| mapping | status | where |
|
||
|---|---|---|
|
||
| SRT listener (live-ingest §13, §3.2, §4.1) | implemented | `joule-contrib` `srt`, `joule-moq-ingest` `contrib` |
|
||
| SRT caller (§3.3) | implemented | the same |
|
||
| RIST Simple Profile (§3.4, §4.2) | implemented, feature `rist` | `joule-contrib` `rist`, `joule-moq-ingest` `contrib` |
|
||
| RIST Main Profile, reduced overhead, PSK (§4.3) | implemented, feature `rist` | the same |
|
||
| the link record (§6) | implemented | `joule-moq-ingest` `linkrec`; corpus `live-conformance/links/` |
|
||
|
||
The reference implementation's tests run on loopback. Each transport against itself, with
|
||
datagrams dropped each way, reordered and in bursts, and every payload delivered whole and
|
||
in order and every sequence number accounted for; SRT with each key length, a wrong and a
|
||
missing passphrase, and every rejection code of §3.2; RIST with range and bitmask requests,
|
||
the Main Profile in the clear, under PSK of each length, and in the legacy format. Through
|
||
the gateway: an SRT publisher and a RIST publisher of each profile whose access units and
|
||
ADTS frames a relay subscriber receives whole with every receipt verified (and the relay's
|
||
door verifying them too), the link record verifying as a series and naming each track's
|
||
receipt-chain head as the subscriber found it, the record on the link track; the gateway
|
||
calling an encoder's listener and carrying HEVC. Against the reference tools of each
|
||
transport run locally (`joule-contrib`'s `tests/interop.rs`, with the tools named by
|
||
environment variables): their SRT caller and their RIST senders of both profiles into this
|
||
implementation, this implementation into their listener and receivers, byte for byte, in
|
||
the clear and encrypted, and through a lossy path.
|
||
|
||
The corpus `live-conformance/links/` holds accepted parts and series and refused ones,
|
||
each refused for the reason it is meant to show, and every reason of §7's last list
|
||
refuses at least one; `live-conformance/live_contribution_verify.py` verifies it from this
|
||
text alone.
|
||
|
||
Not implemented in this revision, beside §1's exclusions: SRT egress (`m=request`), a RIST
|
||
receiver as the tunnel's client, the Main Profile's virtual port multiplexing of several
|
||
flows in one tunnel, and SRT's key refresh as a sender.
|
||
|
||
## 10. Patents
|
||
|
||
VSF TR-06-1:2020 and TR-06-2:2024 each carry an intellectual property notice: a patent is
|
||
claimed to read on their protocol sections (TR-06-1 §4, §5 but §5.1.2; TR-06-2 §5 but
|
||
§5.6.4, §6, §7), and its holder has assured the forum that it will license the claims to
|
||
anyone implementing the recommendation. The forum takes no position on them. The RIST code
|
||
of the reference implementation is therefore built only with its feature `rist`, which no
|
||
default build turns on; whether a distributed build carries it is a licensing decision for
|
||
the distributor. No such notice was found for `draft-sharabayko-srt-01`.
|
||
|
||
## 11. Security and privacy considerations
|
||
|
||
- A session's stream ID, CNAME and passphrase are its credentials, single-use and short
|
||
lived; the `POST` that gives them is authenticated as WHIP's is, and its answer travels
|
||
inside the gateway's TLS front (RFC 9725 §5).
|
||
- SRT encrypts the payload, not the headers (`draft-sharabayko-srt-01` §8): sequence
|
||
numbers, timestamps and sizes are visible on the path; so is a RIST Simple Profile flow
|
||
entire, and a Main Profile tunnel's GRE header.
|
||
- Neither AES-CTR here authenticates: a packet altered on the path decrypts to other bytes.
|
||
The receipts are signed over what the gateway received (live-ingest §18), so they are no
|
||
stronger than the path; a TS packet that does not read is dropped with its PES (§5.1),
|
||
but a well-formed alteration is carried. A deployment that needs origin authentication of
|
||
the media has the encoder sign its own receipts over MoQ (live-ingest §8.1).
|
||
- The gateway calls only the addresses its configuration lists (§3.3).
|
||
- The link record says how much was lost and when: a viewer of the link track learns the
|
||
path's quality. A deployment that treats that as confidential does not grant the link
|
||
track's subscription.
|
||
- Every bound of §8 limits what one session can make a gateway hold; a RIST session holds
|
||
a port for at most its connect time before its flow arrives.
|