From 3d2a97d448ad1a43cb17e45679b66a01c385805d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 5 Jun 2026 18:41:54 -0700 Subject: [PATCH 1/2] moq-lite-05: add SETUP message and negotiated Probe capability levels Re-introduce a SETUP message for per-hop capability negotiation, sent once on a new unidirectional Setup Stream (0x1) and FIN'd immediately. 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. This replaces stream-probing for negotiation and better matches upstream moq-transport (version is still negotiated via ALPN, not SETUP). Add 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 rather than independent, since probing for more bandwidth is meaningless without measuring it; a subscriber must not rely on a level the publisher did not advertise. moq-probe: mirror the same Report/Increase level negotiation and assign real, high, collision-resistant codepoints in place of TBD placeholders, following draft-ietf-moq-transport-18 conventions. moqt-18 removed bidirectional stream types in favor of negotiated request message types, so PROBE is now a PROBE_REQUEST (Request, First) / PROBE_RESPONSE pair of Message Types rather than a stream type. The Setup Option and PROBE_REQUEST share 0x950BE (independent registries); PROBE_RESPONSE is 0x950BF. Also refresh stale moqt section cross-references. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-lite.md | 99 +++++++++++++++++++++++++++++++++++--- draft-lcurley-moq-probe.md | 85 ++++++++++++++++++++------------ 2 files changed, 144 insertions(+), 40 deletions(-) diff --git a/draft-lcurley-moq-lite.md b/draft-lcurley-moq-lite.md index 7f0040f..60bdd1d 100644 --- a/draft-lcurley-moq-lite.md +++ b/draft-lcurley-moq-lite.md @@ -87,7 +87,16 @@ 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 opens a unidirectional Setup Stream at the start of the session, sends a single SETUP message advertising what it supports, and immediately closes the stream (FIN). +The two SETUP messages are independent; neither endpoint waits for the peer's SETUP before opening other streams. +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 +312,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 +431,17 @@ Unidirectional streams are used for data transmission. |-------:|:---------|-------------| | 0x0 | Group | Publisher | | ------ | -------- | ----------- | +| 0x1 | Setup | Either | +| ------ | -------- | ----------- | + +### Setup {#setup-stream} +Either endpoint MAY 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 at most one Setup Stream per direction; an endpoint that receives a second Setup Stream MUST close the session with a PROTOCOL_VIOLATION. +An endpoint that opens no Setup Stream is assumed to support no optional capabilities. + +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 +516,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 +527,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 +895,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 +988,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 +1039,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 From 42f25ef6e46a5e3579507562319ac0092c83b9b4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 5 Jun 2026 18:47:48 -0700 Subject: [PATCH 2/2] moq-lite-05: make SETUP mandatory with empty parameter list Resolve the conflict between the Session section (which read as mandatory) and the Setup Stream section (which said MAY). SETUP is now required in both: each endpoint sends exactly one SETUP, using an empty parameter list when it has no optional capabilities. A guaranteed SETUP bounds the "buffer until the peer's SETUP arrives" rule, which would otherwise deadlock since QUIC offers no "stream will never open" signal. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-lite.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/draft-lcurley-moq-lite.md b/draft-lcurley-moq-lite.md index 60bdd1d..a8693fb 100644 --- a/draft-lcurley-moq-lite.md +++ b/draft-lcurley-moq-lite.md @@ -90,8 +90,9 @@ The session is active immediately after the QUIC/WebTransport connection is esta 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 opens a unidirectional Setup Stream at the start of the session, sends a single SETUP message advertising what it supports, and immediately closes the stream (FIN). +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. @@ -435,11 +436,11 @@ Unidirectional streams are used for data transmission. | ------ | -------- | ----------- | ### Setup {#setup-stream} -Either endpoint MAY open a Setup Stream (0x1) at the start of the session to advertise the optional capabilities and extensions it supports. +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 at most one Setup Stream per direction; an endpoint that receives a second Setup Stream MUST close the session with a PROTOCOL_VIOLATION. -An endpoint that opens no Setup Stream is assumed to support no optional capabilities. +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.