diff --git a/draft-lcurley-moq-lite.md b/draft-lcurley-moq-lite.md index 7f0040f..a8693fb 100644 --- a/draft-lcurley-moq-lite.md +++ b/draft-lcurley-moq-lite.md @@ -87,7 +87,17 @@ When UDP is unavailable, moq-lite-05 MAY also run over reliable byte-stream tran Qmux provides a length-delimited polyfill for QUIC streams on top of TCP/TLS or WebSocket; see [Transports](#transports) for the specific bindings and ALPN negotiation. The session is active immediately after the QUIC/WebTransport connection is established. -Extensions are negotiated via stream probing: an endpoint opens a stream with an unknown type and the peer resets it if unsupported. +Both endpoints SHOULD begin sending and receiving streams right away to avoid an extra round-trip. + +Optional capabilities and extensions are negotiated via a SETUP message (see [SETUP](#setup)). +Each endpoint MUST open a unidirectional Setup Stream at the start of the session, send a single SETUP message advertising what it supports, and immediately close the stream (FIN); an endpoint with no optional capabilities sends a SETUP with an empty parameter list. +The two SETUP messages are independent; neither endpoint waits for the peer's SETUP before opening other streams. +Because a SETUP is always sent, the buffering below is bounded: an endpoint knows the peer's full capability set has arrived once it receives that single SETUP. +An endpoint SHOULD continue to send and process non-Setup streams until a negotiated extension would change the behavior or encoding of a stream, in which case it MUST buffer that stream until the peer's SETUP has been received. +For example, if an extension adds a field to SUBSCRIBE_OK, the subscriber buffers SUBSCRIBE_OK until SETUP arrives so the new field can be parsed. + +As a fallback, an endpoint that opens an extension stream the peer does not support simply sees that stream reset (see [STREAM_TYPE](#stream_type)). +A negotiated capability applies only to this hop; each session is negotiated independently and relays MUST NOT forward SETUP. While moq-lite is a point-to-point protocol, it's intended to work end-to-end via relays. Each client establishes a session with a CDN edge server, ideally the closest one. @@ -303,14 +313,16 @@ The publisher FINs the stream after the last frame, or resets the stream on erro Fetch behaves like HTTP: a single request/response per stream. ### Probe -A subscriber opens a Probe Stream (0x4) to measure the available bitrate of the connection. +A subscriber opens a Probe Stream (0x4) to measure, and optionally increase, the available bitrate of the connection. +The publisher advertises its Probe level in SETUP (see [Probe Parameter](#probe-parameter)): None, Report (measure only), or Increase (measure and actively probe). The subscriber sends a PROBE message with a target bitrate on the bidirectional stream. The subscriber MAY send additional PROBE messages on the same stream to update the target bitrate; the publisher MUST treat each PROBE as a new target to attempt. -The publisher SHOULD pad the connection to achieve the most recent target bitrate. -The publisher periodically replies with PROBE messages on the same bidirectional stream containing the current estimated bitrate and smoothed RTT. +If the publisher advertised the Increase capability, it SHOULD pad the connection (or send redundant data) to achieve the most recent target bitrate, without exceeding the congestion window. +A publisher that advertised Report but not Increase ignores the target and only reports; it MUST NOT pad above its current sending rate. +In either case the publisher periodically replies with PROBE messages on the same bidirectional stream containing the current estimated bitrate and smoothed RTT. -If the publisher does not support PROBE (e.g., congestion controller is not exposed), it MUST reset the stream. +If the publisher advertised no Probe capability (e.g., the congestion controller is not exposed), it MUST reset the stream. ### Goaway Either endpoint can open a Goaway Stream (0x5) to initiate a graceful session shutdown. @@ -420,6 +432,17 @@ Unidirectional streams are used for data transmission. |-------:|:---------|-------------| | 0x0 | Group | Publisher | | ------ | -------- | ----------- | +| 0x1 | Setup | Either | +| ------ | -------- | ----------- | + +### Setup {#setup-stream} +Each endpoint MUST open a Setup Stream (0x1) at the start of the session to advertise the optional capabilities and extensions it supports. + +The opener sends a single SETUP message and immediately closes the stream (FIN). +There is exactly one Setup Stream per direction; an endpoint that receives a second Setup Stream MUST close the session with a PROTOCOL_VIOLATION. +An endpoint with no optional capabilities sends a SETUP with an empty parameter list rather than omitting the stream, giving the peer a deterministic signal that no capabilities are forthcoming. + +See the [Session](#session) section for how an endpoint avoids waiting on the peer's SETUP before exchanging other streams. ### Group A publisher creates Group Streams in response to a Subscribe Stream. @@ -494,7 +517,7 @@ This length field does not include the length of the varint length itself. An implementation SHOULD close the connection with a PROTOCOL_VIOLATION if it receives a message with an unexpected length. The version and extensions should be used to support new fields, not the message length. -## STREAM_TYPE +## STREAM_TYPE {#stream_type} All streams start with a short header indicating the stream type. ~~~ @@ -505,7 +528,65 @@ STREAM_TYPE { The stream ID depends on if it's a bidirectional or unidirectional stream, as indicated in the Streams section. A receiver MUST reset the stream if it receives an unknown stream type. -Unknown stream types MUST NOT be treated as fatal; this enables extension negotiation via stream probing. +Unknown stream types MUST NOT be treated as fatal; this is the fallback when an extension stream is opened against a peer that did not negotiate it. + + +## SETUP {#setup} +A SETUP message advertises the optional capabilities and extensions the sender supports for this session. +It is sent exactly once, as the only message on a [Setup Stream](#setup-stream). + +~~~ +SETUP Message { + Message Length (i) + Parameter Count (i) + Setup Parameter (..) ... +} + +Setup Parameter { + Parameter ID (i) + Parameter Length (i) + Parameter Value (..) +} +~~~ + +**Parameter Count**: +The number of Setup Parameters that follow. + +**Parameter ID**: +Identifies the capability or extension. +A receiver MUST ignore unknown Parameter IDs, allowing new capabilities to be added without breaking older implementations. +A Parameter ID MUST NOT appear more than once; a receiver MUST close the session with a PROTOCOL_VIOLATION if it does. + +**Parameter Length**: +The length of Parameter Value in bytes. + +**Parameter Value**: +The parameter-specific value, interpreted according to Parameter ID. + +A capability is available for the session only if the relevant endpoint advertises it; an absent parameter means the sender does not support that capability. +The following Setup Parameters are defined: + +|------|----------|-------------| +| ID | Name | Value | +|-----:|:---------|:------------| +| 0x1 | Probe | Level (i) | +|------|----------|-------------| + +### Probe Parameter {#probe-parameter} +The Probe Parameter advertises the sender's capability level when acting as a publisher on a [Probe Stream](#probe). +The Parameter Value is a variable-length integer level, where each level includes the one below it: + +- `0` **None**: The publisher does not support probing. Equivalent to omitting the parameter. +- `1` **Report**: The publisher can measure and periodically report its estimated bitrate. +- `2` **Increase**: The publisher can additionally pad the connection (or send redundant data) to probe for bandwidth above its current sending rate, up to the subscriber's target. + +The levels are nested rather than independent: probing for more bandwidth is meaningless without measuring it, so Increase always includes Report. Reporting the current bitrate is far simpler to implement, so a publisher may support Report without Increase. + +A subscriber MUST consult the publisher's advertised level before relying on a Probe Stream: + +- At `None`, the subscriber SHOULD NOT open a Probe Stream; if it does, the publisher MUST reset it. +- At `Report`, the subscriber MAY open a Probe Stream to monitor the estimated bitrate but MUST NOT expect the publisher to pad above its current sending rate. A subscriber that needs to probe for additional bandwidth MUST use an alternative (e.g. speculatively switching to a higher rendition). +- At `Increase`, the subscriber MAY request a target bitrate and expect the publisher to actively probe up to it. ## ANNOUNCE_INTEREST @@ -815,6 +896,7 @@ PROBE Message { **Bitrate**: When sent by the subscriber (stream opener): the target bitrate in bits per second that the publisher should pad up to. +The publisher only honors a target above its current sending rate if it advertised the Increase capability (see [Probe Parameter](#probe-parameter)); otherwise the target is ignored and the publisher only reports. When sent by the publisher (responder): the current estimated bitrate in bits per second. A value of 0 means unknown. @@ -907,6 +989,8 @@ A generic library or relay MUST NOT inspect or modify the decompressed contents # Appendix A: Changelog ## moq-lite-05 +- Added a SETUP message, sent once on a unidirectional Setup Stream (0x1) at the start of the session and FIN'd immediately. It carries a list of Setup Parameters for negotiating optional capabilities and extensions per-hop, replacing the prior stream-probing approach (version is still negotiated via ALPN, not SETUP). Endpoints keep exchanging non-Setup streams without waiting for SETUP, buffering only a stream whose encoding a negotiated extension would change; unknown stream types are still reset as a fallback. +- Added a SETUP `Probe` parameter advertising the publisher's capability level: `None`, `Report` (measure and report the estimated bitrate), or `Increase` (additionally pad to probe for bandwidth above the current sending rate). The levels are nested since probing without measuring is meaningless. A subscriber must not rely on a level the publisher did not advertise. - Added `Frame Start` to FETCH so a subscriber can begin partway through a group instead of always at frame `0`, allowing resumption of a partially-received group. - Renamed `Start Group`/`End Group` to `Group Start`/`Group End` in SUBSCRIBE, SUBSCRIBE_UPDATE, SUBSCRIBE_OK, and SUBSCRIBE_DROP for consistency with the entity-first naming used elsewhere (e.g. `Group Sequence`). Wire format unchanged. - Allowed a duplicate `active` ANNOUNCE to atomically replace the prior advertisement (equivalent to UNANNOUNCE+ANNOUNCE). Used when only the origin or hop path changes (e.g. relay failover) without interrupting the broadcast. No new wire enum value — the existing `active` status carries the new metadata. @@ -956,7 +1040,7 @@ A quick comparison of moq-lite and moq-transport-14: - Streams instead of request IDs. - Pull only: No unsolicited publishing. - FETCH is HTTP-like (single request/response) vs MoqTransport FETCH (multiple groups). -- Extensions negotiated via stream probing instead of parameters. +- Capabilities negotiated via a SETUP message on a unidirectional stream that does not block other streams, instead of MoqTransport's blocking CLIENT_SETUP/SERVER_SETUP handshake on the control stream. - Both moq-lite and MoqTransport use ALPN for version identification. - Names use utf-8 strings instead of byte arrays. - Track Namespace is a string, not an array of any array of bytes. diff --git a/draft-lcurley-moq-probe.md b/draft-lcurley-moq-probe.md index 34201b3..899a5f5 100644 --- a/draft-lcurley-moq-probe.md +++ b/draft-lcurley-moq-probe.md @@ -25,7 +25,7 @@ informative: This document defines a PROBE extension for MoQ Transport {{moqt}}. A subscriber opens a bidirectional PROBE stream to request that the publisher pad the connection up to a target bitrate. -The publisher sends padding as defined in {{moqt}} Section 7.7 and periodically responds with the measured bitrate and an elapsed timestamp, enabling the subscriber to estimate the available bandwidth. +The publisher sends padding as defined in {{moqt}} Section 11.5 and periodically responds with the measured bitrate and an elapsed timestamp, enabling the subscriber to estimate the available bandwidth. --- middle @@ -61,54 +61,68 @@ Using a wire-level extension ensures that PROBE measurements are scoped to a sin A relay terminates the PROBE stream and does not forward it upstream, avoiding incorrect measurements that reflect intermediate link capacity. ## This Extension -{{moqt}} defines padding streams and datagrams (Section 7.7) for probing, but does not define a signaling mechanism for a subscriber to request padding or for a publisher to report measurements. +{{moqt}} defines padding streams and datagrams (Section 11.5) for probing, but does not define a signaling mechanism for a subscriber to request padding or for a publisher to report measurements. This extension fills that gap: the subscriber opens a PROBE stream to request that the publisher pad the connection to a target bitrate using {{moqt}} padding. The publisher responds with periodic measurements, allowing the subscriber to adjust its subscriptions accordingly. # Setup Negotiation -The PROBE extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 9.4. +The PROBE extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3. +Each endpoint advertises the capabilities it supports when acting as a publisher (the responder on a PROBE stream). -Both endpoints indicate support by including the following Setup Option: +An endpoint indicates support by including the following Setup Option: ~~~ PROBE Setup Option { - Option Key (vi64) = TBD1 - Option Value Length (vi64) = 0 + Option Key (vi64) = 0x950BE + Option Value Length (vi64) + Level (vi64) } ~~~ -If both endpoints include this option, the PROBE extension is available for the session. -If a peer receives a PROBE stream without having negotiated the extension, it MUST close the session with a PROTOCOL_VIOLATION. +**Level**: +The publisher capability level the sender supports, where each level includes the one below it: +- `1` **Report**: The publisher can measure and periodically report its estimated bitrate via PROBE_RESPONSE. +- `2` **Increase**: The publisher can additionally send padding (or redundant data) to probe for bandwidth above its current sending rate, up to the subscriber's target. -# PROBE Stream -The PROBE extension uses a new bidirectional stream type. +The levels are nested rather than independent: probing for more bandwidth is meaningless without measuring it, so Increase always includes Report. Reporting the current bitrate is far simpler to implement, so a publisher may support Report without Increase. -~~~ -STREAM_TYPE = TBD2 -~~~ +A subscriber MUST consult the publisher's advertised Level before relying on a PROBE stream: + +- If the publisher omitted the option (or sent `0`), the PROBE extension is unavailable. A subscriber MUST NOT open a PROBE stream; if it does, the publisher MUST close the session with a PROTOCOL_VIOLATION. +- At `Report`, the subscriber MAY open a PROBE stream to monitor the measured bitrate but MUST NOT expect padding above the current sending rate. It MUST set Target Bitrate to 0 and use an alternative if it needs to probe for additional bandwidth. +- At `Increase`, the subscriber MAY request a non-zero Target Bitrate and expect the publisher to actively probe up to it. -The stream type is sent at the beginning of the stream, encoded as a variable-length integer, consistent with {{moqt}} stream type framing. -A subscriber (stream opener) sends PROBE_REQUEST messages on the stream. -The publisher (responder) sends PROBE_RESPONSE messages on the stream. +# PROBE Stream +The PROBE extension defines two new MOQT messages, PROBE_REQUEST and PROBE_RESPONSE, exchanged on a bidirectional request stream. +{{moqt}} no longer assigns dedicated bidirectional stream types; a request stream is identified by its first message type, and per {{moqt}} Section 3.3 a bidirectional stream MUST NOT begin with a message type the peer has not negotiated. +The PROBE Setup Option (see [Setup Negotiation](#setup-negotiation)) performs this negotiation. + +A subscriber opens the stream with a PROBE_REQUEST message, which MUST be the first message on the stream. +The subscriber (stream opener) sends PROBE_REQUEST messages and the publisher (responder) sends PROBE_RESPONSE messages on the same stream. +An endpoint that receives a PROBE_REQUEST without having advertised the PROBE Setup Option MUST close the session with a PROTOCOL_VIOLATION. Either endpoint MAY close or reset the stream at any time. +Both messages use the {{moqt}} Section 10 control-message framing: a `Type` identifying the message, a 16-bit `Length`, and the payload. + ## PROBE_REQUEST A subscriber sends a PROBE_REQUEST to indicate the target the publisher should attempt to reach. ~~~ -PROBE_REQUEST { - Message Length (vi64) +PROBE_REQUEST Message { + Type (vi64) = 0x950BE + Length (16) Target Bitrate (vi64) } ~~~ **Target Bitrate**: The desired bitrate in kilobits per second. -The publisher SHOULD send padding as defined in {{moqt}} Section 7.7 to attempt to reach this rate. +The publisher SHOULD send padding as defined in {{moqt}} Section 11.5 to attempt to reach this rate, but only if it advertised the Increase capability (see [Setup Negotiation](#setup-negotiation)). +A subscriber MUST set this to 0 unless the publisher advertised Increase. A value of 0 indicates no padding is needed; the publisher SHOULD only send media data but MUST continue sending PROBE_RESPONSE messages. This is useful for passively monitoring the current bitrate without actively probing for more bandwidth. Either endpoint MAY close or reset the stream to stop receiving updates entirely. @@ -122,8 +136,9 @@ The publisher MUST use the most recently received target. The publisher periodically sends PROBE_RESPONSE messages containing the measured bitrate and the elapsed time since the last response. ~~~ -PROBE_RESPONSE { - Message Length (vi64) +PROBE_RESPONSE Message { + Type (vi64) = 0x950BF + Length (16) Measured Bitrate (vi64) Elapsed (vi64) } @@ -145,7 +160,7 @@ The interval is implementation-defined but a value between 100ms and 1000ms is R # Padding -The publisher SHOULD send padding using the mechanisms defined in {{moqt}} Section 7.7 (padding streams and/or padding datagrams). +The publisher SHOULD send padding using the mechanisms defined in {{moqt}} Section 11.5 (padding streams and/or padding datagrams). The publisher MUST NOT exceed the target with padding alone. If media traffic already meets or exceeds the target, no additional padding is necessary. @@ -169,21 +184,27 @@ Implementations SHOULD enforce a minimum inter-request interval for PROBE_REQUES This document requests the following registrations: -## MOQT Setup Option Type +## MOQT Setup Options -This document registers the following entry in the "MoQ Setup Option Types" registry: +This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required. +moq-transport defines no private-use range for Setup Options; extensions request a (provisional) codepoint. +A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}} and to minimize collisions with provisional registrations by other extensions; it also avoids the greasing pattern (`0x7f * N + 0x9D`). +This is the same value as the PROBE_REQUEST message type below; Setup Options and Message Types are independent registries, so the shared value is unambiguous. -| Value | Name | Reference | -|:------|:-----|:----------| -| TBD1 | PROBE | This Document | +| Value | Name | Reference | +|:--------|:------|:--------------| +| 0x950BE | PROBE | This Document | -## MOQT Stream Type +## MOQT Message Types -This document registers the following entry in the "MoQ Stream Types" registry: +This document requests registrations in the "MOQT Message Types" registry ({{moqt}} Section 15). +{{moqt}} replaced dedicated bidirectional stream types with request message types, so the PROBE stream is identified by its first message rather than a stream type. +High, distinctive values are chosen to avoid the low ranges reserved by {{moqt}} and to minimize collisions with provisional registrations by other extensions; they also avoid the greasing pattern (`0x7f * N + 0x9D`). -| Value | Name | Reference | -|:------|:-----|:----------| -| TBD2 | PROBE | This Document | +| Value | Name | Stream | Reference | +|:--------|:---------------|:---------------|:--------------| +| 0x950BE | PROBE_REQUEST | Request, First | This Document | +| 0x950BF | PROBE_RESPONSE | Request | This Document | --- back