From 281f83b72b3c69551f2d1678aa7f27d1e6c09b13 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Jun 2026 15:11:03 -0700 Subject: [PATCH 1/4] moq-lite-05: add Subscriber Count for telescoping viewer count Add a Subscriber Count field to SUBSCRIBE and SUBSCRIBE_UPDATE: a fan-in subscriber property encoding the number of downstream subscribers a subscription represents. Encoded as count-minus-one (a leaf sends 0); a relay sums the counts of the downstream subscriptions it merges, so the value telescopes up the fan-out tree and a publisher reads its total audience from its single upstream subscription. Relays SHOULD rate-limit count-only updates to absorb subscriber churn. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-lite.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/draft-lcurley-moq-lite.md b/draft-lcurley-moq-lite.md index a956c96..7442d40 100644 --- a/draft-lcurley-moq-lite.md +++ b/draft-lcurley-moq-lite.md @@ -732,6 +732,7 @@ SUBSCRIBE Message { Subscriber Stale (i) Group Start (i) Group End (i) + Subscriber Count (i) } ~~~ @@ -767,6 +768,14 @@ The last group to deliver (inclusive). A value of 0 means unbounded (default). A non-zero value is the absolute group sequence + 1. +**Subscriber Count**: +The number of subscribers this subscription represents, encoded as the count minus one: the subscription is the implicit `1`, so a leaf subscriber sends `0`. +This is a [subscriber property](#track-info) that fans *in* at a relay: a relay merging multiple downstream subscriptions into one upstream subscription sets this to the **sum** of their counts, so the value telescopes up the fan-out tree. +A publisher therefore learns its total number of downstream subscribers across any number of relay hops by reading the count on its single upstream subscription, without any per-hop coordination. +The count changes as subscribers join and leave; a relay refreshes it via SUBSCRIBE_UPDATE. +A relay holding a subscription open with no live downstream subscribers (e.g. briefly retaining it for reuse) MUST still report at least `1`, since the held subscription cannot represent fewer subscribers than itself. +The count is advisory: a subscriber MAY misreport it, and a relay MUST NOT use it for delivery decisions. + ## SUBSCRIBE_UPDATE A subscriber can modify a subscription with a SUBSCRIBE_UPDATE message. @@ -781,11 +790,15 @@ SUBSCRIBE_UPDATE Message { Subscriber Stale (i) Group Start (i) Group End (i) + Subscriber Count (i) } ~~~ See [SUBSCRIBE](#subscribe) for information about each field. +A relay SHOULD rate-limit SUBSCRIBE_UPDATE messages sent purely to refresh the `Subscriber Count` (for example, coalescing changes within a short window of roughly a second), so that rapid subscriber churn does not flood the upstream with control messages. +Because the count is the latest aggregate rather than a delta, a change that reverts within the window requires no message at all. + ## TRACK TRACK is sent by a subscriber to request a Track's immutable publisher properties. @@ -1084,6 +1097,7 @@ A generic library or relay MUST NOT inspect or modify the decompressed contents # Appendix A: Changelog ## moq-lite-05 +- Added `Subscriber Count` to SUBSCRIBE and SUBSCRIBE_UPDATE, a telescoping count of downstream subscribers. Encoded as the count minus one (a leaf sends `0`); a relay sums the counts of the downstream subscriptions it merges, so a publisher reads its total audience across any number of hops from its single upstream subscription. It is a subscriber property that fans *in* like the others, so it refreshes via SUBSCRIBE_UPDATE; relays SHOULD rate-limit count-only updates to absorb subscriber churn. - 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. From 35d8a2e7c51619556b2198c5a42ee554c3d4885f Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Jun 2026 15:14:38 -0700 Subject: [PATCH 2/4] Add MoQ Subscriber Count Extension for moq-transport Define a Subscriber Count parameter for moq-transport: an optional, even-typed Key-Value-Pair on SUBSCRIBE and REQUEST_UPDATE carrying the number of subscribers a subscription represents, encoded as count - 1 (a leaf, and an absent parameter, decode to 1). The count - 1 encoding makes a count of 0 impossible to represent: a subscription can never report fewer subscribers than itself. A relay sums the counts of the downstream subscriptions it aggregates and forwards the sum upstream, so the value telescopes up the fan-out tree and the origin learns its total audience across any number of hops. Negotiated per hop via a SUBSCRIBER_COUNT Setup Option; rate-limited and treated as untrusted advisory telemetry. This is the moq-transport-extension counterpart to the Subscriber Count field added to moq-lite. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-subscriber-count.md | 171 ++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 draft-lcurley-moq-subscriber-count.md diff --git a/draft-lcurley-moq-subscriber-count.md b/draft-lcurley-moq-subscriber-count.md new file mode 100644 index 0000000..c4f41a7 --- /dev/null +++ b/draft-lcurley-moq-subscriber-count.md @@ -0,0 +1,171 @@ +--- +title: "MoQ Subscriber Count Extension" +abbrev: "moq-subscriber-count" +category: info + +docname: draft-lcurley-moq-subscriber-count-latest +submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" +number: +date: +v: 3 +area: wit +workgroup: moq + +author: + - + fullname: Luke Curley + email: kixelated@gmail.com + +normative: + moqt: I-D.ietf-moq-transport + +informative: + +--- abstract + +This document defines a Subscriber Count parameter for MoQ Transport {{moqt}}. +The parameter carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription. + +--- middle + +# Conventions and Definitions +{::boilerplate bcp14-tagged} + + +# Introduction +A publisher in {{moqt}} often wants to know how many subscribers are receiving a Track. +This is straightforward when a subscriber connects directly, but {{moqt}} is designed around relays: a relay aggregates many downstream subscriptions for the same Track into a single upstream subscription toward the origin (its "fan-out" tree). +The origin sees one upstream subscription per relay, not the individual subscribers behind it, so it cannot count its true audience without out-of-band coordination. + +This document defines a Subscriber Count parameter that rides on the subscription itself. +Each subscription reports how many subscribers it represents. +A relay sets the count on its single upstream subscription to the **sum** of the counts of the downstream subscriptions it aggregates. +Because the count is a sum reduced up the existing subscription tree, it telescopes for free: at the origin, the count on each upstream subscription is the total number of subscribers reachable through that relay, transitively, across any number of hops. + +The count is a property of the subscriber side of the subscription. +In {{moqt}} a relay already merges subscriber-supplied parameters when it aggregates downstream subscriptions (subscriber properties fan *in*), so this parameter follows the existing aggregation path rather than introducing a new one. +It changes as subscribers join and leave and is refreshed with REQUEST_UPDATE ({{moqt}} Section 10.9). + + +# Setup Negotiation +The Subscriber Count extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 9.4. + +Both endpoints indicate support by including the following Setup Option: + +~~~ +SUBSCRIBER_COUNT Setup Option { + Option Key (vi64) = 0xC0117 + Option Value Length (vi64) = 0 +} +~~~ + +The extension is available on a hop only if both endpoints on that hop included this option. +The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa, and MUST NOT forward the parameter onto a hop that did not negotiate it. + +This extension only adds an optional parameter to existing messages, so a peer that receives the parameter without having negotiated the extension MUST ignore it rather than treating it as an error. +Ignoring an un-negotiated count simply collapses that subscription's contribution to the default of `1` (see [Semantics](#semantics)); it does not break delivery. + + +# Subscriber Count Parameter +This document defines a new parameter for use in the Parameters field of the SUBSCRIBE ({{moqt}} Section 10.7) and REQUEST_UPDATE ({{moqt}} Section 10.9) messages, encoded as a Key-Value-Pair ({{moqt}} Section 1.4.3). + +~~~ +SUBSCRIBER_COUNT Parameter { + Type (vi64) = 0xC0116 + Value (vi64) ; Subscriber Count - 1 +} +~~~ + +The Type `0xC0116` is even, so per {{moqt}} Section 1.4.3 the Value is a single varint with no length prefix. + +**Value**: +The number of subscribers this subscription represents, including the subscriber itself, encoded as the count minus one. +The subscription is the implicit `1`, so the wire value is `Subscriber Count - 1`: a leaf encodes `0`, and a relay encodes its summed total minus one. + +Encoding `count - 1` rather than the count itself makes a count of `0` impossible to represent on the wire: the minimum encodable value is `0`, which decodes to a count of `1`. +A subscription can never report fewer subscribers than itself. + +A subscriber MAY include this parameter in SUBSCRIBE and in any REQUEST_UPDATE. +If the parameter is absent, the wire value is treated as `0`, so the count for that subscription is `1`. +A leaf subscriber therefore reports `1`, whether by sending a wire value of `0` or by omitting the parameter. + + +# Semantics +The count is a reduction up the subscription tree. + +A **leaf subscriber** (one that is not a relay) represents itself: a count of `1`. +It MAY omit the parameter to mean the same thing. + +A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the SUBSCRIBER_COUNT on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating an absent downstream parameter as `1`. +When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, refreshes the upstream value with REQUEST_UPDATE. + +A relay MUST report at least `1` for any upstream subscription it is holding open, even if it currently has no live downstream subscribers (for example, a subscription briefly retained for reuse). +The `count - 1` encoding enforces this floor automatically: a sum of `0` still encodes a wire value of `0`, which decodes upstream as `1`. +A held subscription cannot represent fewer subscribers than itself. + +Because each relay reports the sum of its subtree, the value telescopes: at the origin, the SUBSCRIBER_COUNT on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops. +A publisher reads its total audience for a Track as the sum of the counts of the subscriptions it is serving. + +This parameter alters no delivery behavior. +It MUST NOT influence prioritization, caching, congestion response, or any other distribution decision; it is informational telemetry carried alongside the subscription. + + +# Rate Limiting +Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream REQUEST_UPDATE. + +A relay SHOULD rate-limit REQUEST_UPDATE messages whose only change is the SUBSCRIBER_COUNT, coalescing changes that occur within a short window (on the order of a second) and then forwarding the latest sum. +Because the parameter carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. + +A relay MUST NOT coalesce a SUBSCRIBER_COUNT change with, or delay it behind, a REQUEST_UPDATE that also changes delivery-affecting fields; those are forwarded according to {{moqt}} without additional delay. + + +# Security Considerations +**Audience disclosure.** +The Subscriber Count discloses aggregate viewership to the publisher and to every relay on the path toward it. +For some applications the size of an audience is sensitive (for example, it can reveal the popularity or reach of content, or that an audience has dropped to zero). +Because the extension is negotiated per hop, an endpoint that considers this information sensitive simply does not advertise the SUBSCRIBER_COUNT Setup Option, and no count is exchanged on that hop. + +**Untrusted values.** +The count is supplied by the subscriber side and aggregated by intermediaries, none of which the publisher can fully trust. +A malicious or buggy subscriber can report an inflated or deflated count, and a malicious relay can report any sum regardless of its actual downstream subscriptions. +The value is therefore advisory: an endpoint MUST NOT use it for any security-sensitive purpose — such as billing, admission control, rate limiting, or capacity planning that affects other subscribers — without independent verification. +A relay aggregates the values it receives but cannot attest to the honesty of its downstream peers. + +**Churn amplification.** +A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated count updates. +The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream message rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely. + +This extension introduces no other security considerations beyond those described in {{moqt}}. + + +# IANA Considerations + +This document requests the following registrations. + +## MOQT Setup Options + +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`). + +| Value | Name | Reference | +|:------|:-----|:----------| +| 0xC0117 | SUBSCRIBER_COUNT | This Document | + +## MOQT Message Parameters + +This document requests a registration in the "Message Parameters" registry ({{moqt}} Section 15.7). +The value is **even** so that, per the Key-Value-Pair encoding ({{moqt}} Section 1.4.3), the count is carried as a single varint with no length prefix. +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`). + +| Value | Name | Reference | +|:------|:-----|:----------| +| 0xC0116 | SUBSCRIBER_COUNT | This Document | + + +--- back + +# Acknowledgments +{:numbered="false"} + +This document was drafted with the assistance of Claude, an AI assistant by Anthropic. From 92d4442f9aa1ef8deaee0aa33ad55ef6f93a5635 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Jun 2026 20:19:59 -0700 Subject: [PATCH 3/4] moq-subscriber-count: use a fire-and-forget message, not a parameter Restructure the moq-transport extension from a Message Parameter on SUBSCRIBE/REQUEST_UPDATE to a dedicated SUBSCRIBER_COUNT control message on the subscription's request stream. In moq-transport a REQUEST_UPDATE consumes a Request ID (Section 10.1) and obliges the receiver to answer with REQUEST_OK/REQUEST_ERROR (Section 10.9). Refreshing a once-per-second telemetry value through it would be a request/response transaction whose stated purpose is to modify the subscription -- a poor fit. The dedicated message rides the existing request stream, consumes no Request ID, and elicits no response. (Request ID exhaustion is not the concern: MAX_REQUEST_ID / REQUESTS_BLOCKED were removed in -18.) The asymmetry with moq-lite is deliberate: moq-lite's SUBSCRIBE_UPDATE is already fire-and-forget (no response, no ID), so the field-based design there is fine and is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-subscriber-count.md | 82 +++++++++++++++------------ 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/draft-lcurley-moq-subscriber-count.md b/draft-lcurley-moq-subscriber-count.md index c4f41a7..c988dc0 100644 --- a/draft-lcurley-moq-subscriber-count.md +++ b/draft-lcurley-moq-subscriber-count.md @@ -23,8 +23,9 @@ informative: --- abstract -This document defines a Subscriber Count parameter for MoQ Transport {{moqt}}. -The parameter carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription. +This document defines a SUBSCRIBER_COUNT message for MoQ Transport {{moqt}}. +The message carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription. +It is a fire-and-forget telemetry message that consumes no Request ID and elicits no response. --- middle @@ -37,14 +38,15 @@ A publisher in {{moqt}} often wants to know how many subscribers are receiving a This is straightforward when a subscriber connects directly, but {{moqt}} is designed around relays: a relay aggregates many downstream subscriptions for the same Track into a single upstream subscription toward the origin (its "fan-out" tree). The origin sees one upstream subscription per relay, not the individual subscribers behind it, so it cannot count its true audience without out-of-band coordination. -This document defines a Subscriber Count parameter that rides on the subscription itself. -Each subscription reports how many subscribers it represents. +This document defines a SUBSCRIBER_COUNT message that reports, per subscription, how many subscribers it represents. A relay sets the count on its single upstream subscription to the **sum** of the counts of the downstream subscriptions it aggregates. Because the count is a sum reduced up the existing subscription tree, it telescopes for free: at the origin, the count on each upstream subscription is the total number of subscribers reachable through that relay, transitively, across any number of hops. -The count is a property of the subscriber side of the subscription. -In {{moqt}} a relay already merges subscriber-supplied parameters when it aggregates downstream subscriptions (subscriber properties fan *in*), so this parameter follows the existing aggregation path rather than introducing a new one. -It changes as subscribers join and leave and is refreshed with REQUEST_UPDATE ({{moqt}} Section 10.9). +The count changes as subscribers join and leave, so it is sent repeatedly over the life of a subscription. +This is pure telemetry: the count never modifies the subscription, and the publisher only observes it. +For that reason it is carried in a dedicated, fire-and-forget message rather than in REQUEST_UPDATE ({{moqt}} Section 10.9). +A REQUEST_UPDATE consumes a Request ID ({{moqt}} Section 10.1) and obliges the receiver to answer with a REQUEST_OK or REQUEST_ERROR ({{moqt}} Section 10.9) — a request/response transaction whose purpose is to *modify* the subscription, which is a poor fit for a value pushed once per second that changes nothing. +The SUBSCRIBER_COUNT message instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response. # Setup Negotiation @@ -60,63 +62,66 @@ SUBSCRIBER_COUNT Setup Option { ~~~ The extension is available on a hop only if both endpoints on that hop included this option. -The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa, and MUST NOT forward the parameter onto a hop that did not negotiate it. +The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa. -This extension only adds an optional parameter to existing messages, so a peer that receives the parameter without having negotiated the extension MUST ignore it rather than treating it as an error. -Ignoring an un-negotiated count simply collapses that subscription's contribution to the default of `1` (see [Semantics](#semantics)); it does not break delivery. +Negotiation is mandatory before the message is sent. +{{moqt}} (Section 10) requires an endpoint that receives an unknown control message type to close the session, so — unlike an optional parameter, which can be ignored — a SUBSCRIBER_COUNT message cannot be sent speculatively. +An endpoint MUST NOT send SUBSCRIBER_COUNT on a hop that did not negotiate this extension. -# Subscriber Count Parameter -This document defines a new parameter for use in the Parameters field of the SUBSCRIBE ({{moqt}} Section 10.7) and REQUEST_UPDATE ({{moqt}} Section 10.9) messages, encoded as a Key-Value-Pair ({{moqt}} Section 1.4.3). +# SUBSCRIBER_COUNT Message +This document defines a new control message, sent on a subscription's request stream ({{moqt}} Section 3.3) by the endpoint that opened it (the subscriber, which for an upstream subscription is the relay). ~~~ -SUBSCRIBER_COUNT Parameter { - Type (vi64) = 0xC0116 - Value (vi64) ; Subscriber Count - 1 +SUBSCRIBER_COUNT Message { + Type (vi64) = 0xC0117 + Length (16) + Subscriber Count (vi64) ; encoded as count - 1 } ~~~ -The Type `0xC0116` is even, so per {{moqt}} Section 1.4.3 the Value is a single varint with no length prefix. +The message MUST NOT be the first message on the request stream; it follows the SUBSCRIBE ({{moqt}} Section 10.7) that opened the stream. +It consumes no Request ID ({{moqt}} Section 10.1), and the receiver MUST NOT respond to it. +A subscriber MAY send it any number of times over the life of the subscription to refresh the count. -**Value**: +**Subscriber Count**: The number of subscribers this subscription represents, including the subscriber itself, encoded as the count minus one. The subscription is the implicit `1`, so the wire value is `Subscriber Count - 1`: a leaf encodes `0`, and a relay encodes its summed total minus one. Encoding `count - 1` rather than the count itself makes a count of `0` impossible to represent on the wire: the minimum encodable value is `0`, which decodes to a count of `1`. A subscription can never report fewer subscribers than itself. -A subscriber MAY include this parameter in SUBSCRIBE and in any REQUEST_UPDATE. -If the parameter is absent, the wire value is treated as `0`, so the count for that subscription is `1`. -A leaf subscriber therefore reports `1`, whether by sending a wire value of `0` or by omitting the parameter. +Until a SUBSCRIBER_COUNT message is received for a subscription, its count is `1`. +A leaf subscriber that represents only itself therefore need not send the message at all. # Semantics The count is a reduction up the subscription tree. A **leaf subscriber** (one that is not a relay) represents itself: a count of `1`. -It MAY omit the parameter to mean the same thing. +It need not send the message to mean this. -A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the SUBSCRIBER_COUNT on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating an absent downstream parameter as `1`. -When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, refreshes the upstream value with REQUEST_UPDATE. +A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the count it reports on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating a downstream subscription that has not reported as `1`. +When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, sends a SUBSCRIBER_COUNT message upstream with the new total. A relay MUST report at least `1` for any upstream subscription it is holding open, even if it currently has no live downstream subscribers (for example, a subscription briefly retained for reuse). The `count - 1` encoding enforces this floor automatically: a sum of `0` still encodes a wire value of `0`, which decodes upstream as `1`. A held subscription cannot represent fewer subscribers than itself. -Because each relay reports the sum of its subtree, the value telescopes: at the origin, the SUBSCRIBER_COUNT on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops. +Because each relay reports the sum of its subtree, the value telescopes: at the origin, the count on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops. A publisher reads its total audience for a Track as the sum of the counts of the subscriptions it is serving. -This parameter alters no delivery behavior. +The message alters no delivery behavior. It MUST NOT influence prioritization, caching, congestion response, or any other distribution decision; it is informational telemetry carried alongside the subscription. # Rate Limiting -Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream REQUEST_UPDATE. +Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBER_COUNT message. -A relay SHOULD rate-limit REQUEST_UPDATE messages whose only change is the SUBSCRIBER_COUNT, coalescing changes that occur within a short window (on the order of a second) and then forwarding the latest sum. -Because the parameter carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. +A relay SHOULD rate-limit SUBSCRIBER_COUNT messages per subscription, coalescing changes that occur within a short window (on the order of a second) and then sending the latest sum. +Because the message carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. -A relay MUST NOT coalesce a SUBSCRIBER_COUNT change with, or delay it behind, a REQUEST_UPDATE that also changes delivery-affecting fields; those are forwarded according to {{moqt}} without additional delay. +Because the message is independent of REQUEST_UPDATE, this rate limiting never delays a genuine subscription change: delivery-affecting updates are forwarded according to {{moqt}} without regard to the count's window. # Security Considerations @@ -133,7 +138,8 @@ A relay aggregates the values it receives but cannot attest to the honesty of it **Churn amplification.** A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated count updates. -The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream message rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely. +The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream SUBSCRIBER_COUNT rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely. +Because the message consumes no Request ID and elicits no response, this churn cannot exhaust an identifier space or force the origin into matching replies. This extension introduces no other security considerations beyond those described in {{moqt}}. @@ -152,15 +158,17 @@ A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}} |:------|:-----|:----------| | 0xC0117 | SUBSCRIBER_COUNT | This Document | -## MOQT Message Parameters +## MOQT Message Types -This document requests a registration in the "Message Parameters" registry ({{moqt}} Section 15.7). -The value is **even** so that, per the Key-Value-Pair encoding ({{moqt}} Section 1.4.3), the count is carried as a single varint with no length prefix. -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 document registers a control message type. +{{moqt}} does not yet establish an IANA registry for message types, so this is a provisional codepoint pending such a registry; the value is chosen to be high and distinctive to avoid the low ranges {{moqt}} assigns and to minimize collisions with provisional registrations by other extensions, and it avoids the greasing pattern (`0x7f * N + 0x9D`). +This is the same value as the SUBSCRIBER_COUNT Setup Option above; Setup Options and message types are independent namespaces, so the shared value is unambiguous. -| Value | Name | Reference | -|:------|:-----|:----------| -| 0xC0116 | SUBSCRIBER_COUNT | This Document | +The Stream column has the meaning defined by {{moqt}} Section 10: "Request" indicates the message is carried on a bidirectional request stream. The message is not marked "First": it never opens a request stream. + +| Value | Name | Stream | Reference | +|:------|:-----|:-------|:----------| +| 0xC0117 | SUBSCRIBER_COUNT | Request | This Document | --- back From b40032c8aca5955ec6a7da59fb7052f579830f37 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Jun 2026 20:34:07 -0700 Subject: [PATCH 4/4] Split viewer count into a SUBSCRIBE_STATS message Move the telescoping subscriber count out of the subscription's update path into a dedicated SUBSCRIBE_STATS telemetry message, named generically so further cheaply-aggregatable stats can be added later. moq-lite: SUBSCRIBE_UPDATE re-echoes all five subscription parameters on every send, so bumping a counter through it is wasteful. Carry the count in a separate SUBSCRIBE_STATS message instead. This introduces a Type tag on the subscriber's post-SUBSCRIBE messages (0x0 SUBSCRIBE_UPDATE, 0x1 SUBSCRIBE_STATS) to distinguish them, mirroring the publisher's already-typed responses; the count field is removed from SUBSCRIBE and SUBSCRIBE_UPDATE. moq-transport: rename the SUBSCRIBER_COUNT message to SUBSCRIBE_STATS and make its body a list of stats encoded as Message Parameters, so new stats are added without a new message type or a wire break. Subscriber Count becomes one such parameter (even type, count - 1). Draft renamed to draft-lcurley-moq-subscribe-stats. Co-Authored-By: Claude Opus 4.8 (1M context) --- draft-lcurley-moq-lite.md | 48 +++++--- ...md => draft-lcurley-moq-subscribe-stats.md | 110 +++++++++++------- 2 files changed, 106 insertions(+), 52 deletions(-) rename draft-lcurley-moq-subscriber-count.md => draft-lcurley-moq-subscribe-stats.md (58%) diff --git a/draft-lcurley-moq-lite.md b/draft-lcurley-moq-lite.md index 7442d40..f6dcee7 100644 --- a/draft-lcurley-moq-lite.md +++ b/draft-lcurley-moq-lite.md @@ -300,7 +300,8 @@ There MAY be multiple Announce Streams, potentially containing overlapping prefi ### Subscribe A subscriber opens Subscribe Streams to request a Track. -The subscriber MUST start a Subscribe Stream with a SUBSCRIBE message followed by any number of SUBSCRIBE_UPDATE messages. +The subscriber MUST start a Subscribe Stream with a SUBSCRIBE message followed by any number of SUBSCRIBE_UPDATE and SUBSCRIBE_STATS messages. +The opening SUBSCRIBE is identified by the stream type (0x2) and carries no message Type; every subsequent message from the subscriber begins with a Type that distinguishes SUBSCRIBE_UPDATE from SUBSCRIBE_STATS, mirroring how the publisher's messages on this stream are typed. When a start group can be resolved, the publisher replies with a SUBSCRIBE_OK message (confirming the subscription and resolving its start group), followed by any number of SUBSCRIBE_END and SUBSCRIBE_DROP messages. When the accepted track has already ended with no matching groups there is no start group to resolve, so the publisher sends SUBSCRIBE_END with no preceding SUBSCRIBE_OK. A rejection is a stream reset: if the publisher cannot serve the subscription — the track does not exist, or it otherwise refuses — it MUST reset the stream rather than leave it pending, and SHOULD do so promptly (within roughly a round trip) so the subscriber is not left waiting. @@ -732,7 +733,6 @@ SUBSCRIBE Message { Subscriber Stale (i) Group Start (i) Group End (i) - Subscriber Count (i) } ~~~ @@ -768,14 +768,6 @@ The last group to deliver (inclusive). A value of 0 means unbounded (default). A non-zero value is the absolute group sequence + 1. -**Subscriber Count**: -The number of subscribers this subscription represents, encoded as the count minus one: the subscription is the implicit `1`, so a leaf subscriber sends `0`. -This is a [subscriber property](#track-info) that fans *in* at a relay: a relay merging multiple downstream subscriptions into one upstream subscription sets this to the **sum** of their counts, so the value telescopes up the fan-out tree. -A publisher therefore learns its total number of downstream subscribers across any number of relay hops by reading the count on its single upstream subscription, without any per-hop coordination. -The count changes as subscribers join and leave; a relay refreshes it via SUBSCRIBE_UPDATE. -A relay holding a subscription open with no live downstream subscribers (e.g. briefly retaining it for reuse) MUST still report at least `1`, since the held subscription cannot represent fewer subscribers than itself. -The count is advisory: a subscriber MAY misreport it, and a relay MUST NOT use it for delivery decisions. - ## SUBSCRIBE_UPDATE A subscriber can modify a subscription with a SUBSCRIBE_UPDATE message. @@ -784,21 +776,51 @@ The start and end group can be changed in either direction (growing or shrinking ~~~ SUBSCRIBE_UPDATE Message { + Type (i) = 0x0 Message Length (i) Subscriber Priority (8) Subscriber Ordered (8) Subscriber Stale (i) Group Start (i) Group End (i) +} +~~~ + +**Type**: +Set to 0x0 to indicate a SUBSCRIBE_UPDATE message. + +See [SUBSCRIBE](#subscribe) for information about each remaining field. + + +## SUBSCRIBE_STATS +A subscriber sends a SUBSCRIBE_STATS message to report telemetry about a subscription. +Unlike SUBSCRIBE_UPDATE, it does not modify the subscription and the publisher only observes it; it is kept separate so that refreshing telemetry does not re-echo the subscription's delivery parameters on every change. +A subscriber MAY send multiple SUBSCRIBE_STATS messages over the life of the subscription to refresh the values. + +~~~ +SUBSCRIBE_STATS Message { + Type (i) = 0x1 + Message Length (i) Subscriber Count (i) } ~~~ -See [SUBSCRIBE](#subscribe) for information about each field. +**Type**: +Set to 0x1 to indicate a SUBSCRIBE_STATS message. -A relay SHOULD rate-limit SUBSCRIBE_UPDATE messages sent purely to refresh the `Subscriber Count` (for example, coalescing changes within a short window of roughly a second), so that rapid subscriber churn does not flood the upstream with control messages. +**Subscriber Count**: +The number of subscribers this subscription represents, encoded as the count minus one: the subscription is the implicit `1`, so a leaf subscriber sends `0`. +This is a subscriber-side value that fans *in* at a relay: a relay merging multiple downstream subscriptions into one upstream subscription sets this to the **sum** of their counts, so the value telescopes up the fan-out tree. +A publisher therefore learns its total number of downstream subscribers across any number of relay hops by reading the count on its single upstream subscription, without any per-hop coordination. +Until a SUBSCRIBE_STATS is received, the count is `1`; a leaf subscriber that represents only itself need not send the message. +A relay holding a subscription open with no live downstream subscribers (e.g. briefly retaining it for reuse) MUST still report at least `1`, since the held subscription cannot represent fewer subscribers than itself. +The count is advisory: a subscriber MAY misreport it, and a relay MUST NOT use it for delivery decisions. + +A relay SHOULD rate-limit SUBSCRIBE_STATS messages (for example, coalescing changes within a short window of roughly a second), so that rapid subscriber churn does not flood the upstream with control messages. Because the count is the latest aggregate rather than a delta, a change that reverts within the window requires no message at all. +Future revisions MAY append additional stats to this message; the `Message Length` bounds the message so a receiver can stop after the fields it understands. + ## TRACK TRACK is sent by a subscriber to request a Track's immutable publisher properties. @@ -1097,7 +1119,7 @@ A generic library or relay MUST NOT inspect or modify the decompressed contents # Appendix A: Changelog ## moq-lite-05 -- Added `Subscriber Count` to SUBSCRIBE and SUBSCRIBE_UPDATE, a telescoping count of downstream subscribers. Encoded as the count minus one (a leaf sends `0`); a relay sums the counts of the downstream subscriptions it merges, so a publisher reads its total audience across any number of hops from its single upstream subscription. It is a subscriber property that fans *in* like the others, so it refreshes via SUBSCRIBE_UPDATE; relays SHOULD rate-limit count-only updates to absorb subscriber churn. +- Added a SUBSCRIBE_STATS message carrying a telescoping `Subscriber Count` (count of downstream subscribers, encoded as the count minus one so a leaf sends `0`); a relay sums the counts of the downstream subscriptions it merges, so a publisher reads its total audience across any number of hops from its single upstream subscription. It is kept separate from SUBSCRIBE_UPDATE so that refreshing telemetry does not re-echo the subscription's delivery parameters; relays SHOULD rate-limit it to absorb subscriber churn. This also introduced a `Type` tag on the subscriber's post-SUBSCRIBE messages (`0x0` SUBSCRIBE_UPDATE, `0x1` SUBSCRIBE_STATS) to distinguish them, mirroring the publisher's typed responses. - 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. diff --git a/draft-lcurley-moq-subscriber-count.md b/draft-lcurley-moq-subscribe-stats.md similarity index 58% rename from draft-lcurley-moq-subscriber-count.md rename to draft-lcurley-moq-subscribe-stats.md index c988dc0..8120eae 100644 --- a/draft-lcurley-moq-subscriber-count.md +++ b/draft-lcurley-moq-subscribe-stats.md @@ -1,9 +1,9 @@ --- -title: "MoQ Subscriber Count Extension" -abbrev: "moq-subscriber-count" +title: "MoQ Subscribe Stats Extension" +abbrev: "moq-subscribe-stats" category: info -docname: draft-lcurley-moq-subscriber-count-latest +docname: draft-lcurley-moq-subscribe-stats-latest submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" number: date: @@ -23,9 +23,9 @@ informative: --- abstract -This document defines a SUBSCRIBER_COUNT message for MoQ Transport {{moqt}}. -The message carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription. -It is a fire-and-forget telemetry message that consumes no Request ID and elicits no response. +This document defines a SUBSCRIBE_STATS message for MoQ Transport {{moqt}}: fire-and-forget telemetry that a subscriber reports about a subscription without modifying it. +It carries one or more stats that aggregate cheaply up the relay fan-out tree. +This document defines a single stat, Subscriber Count, which telescopes so a publisher can learn its total downstream audience across any number of relay hops by reading one value on its upstream subscription. --- middle @@ -38,24 +38,25 @@ A publisher in {{moqt}} often wants to know how many subscribers are receiving a This is straightforward when a subscriber connects directly, but {{moqt}} is designed around relays: a relay aggregates many downstream subscriptions for the same Track into a single upstream subscription toward the origin (its "fan-out" tree). The origin sees one upstream subscription per relay, not the individual subscribers behind it, so it cannot count its true audience without out-of-band coordination. -This document defines a SUBSCRIBER_COUNT message that reports, per subscription, how many subscribers it represents. -A relay sets the count on its single upstream subscription to the **sum** of the counts of the downstream subscriptions it aggregates. +This document defines a SUBSCRIBE_STATS message that reports telemetry about a subscription. +A stat is useful here only if it aggregates cheaply up the fan-out tree: a relay merges the stats of the downstream subscriptions it serves into one value on its single upstream subscription. +This document defines a single such stat, **Subscriber Count**, which a relay reports as the **sum** of its downstream counts. Because the count is a sum reduced up the existing subscription tree, it telescopes for free: at the origin, the count on each upstream subscription is the total number of subscribers reachable through that relay, transitively, across any number of hops. -The count changes as subscribers join and leave, so it is sent repeatedly over the life of a subscription. -This is pure telemetry: the count never modifies the subscription, and the publisher only observes it. -For that reason it is carried in a dedicated, fire-and-forget message rather than in REQUEST_UPDATE ({{moqt}} Section 10.9). +Stats change as subscribers join and leave, so SUBSCRIBE_STATS is sent repeatedly over the life of a subscription. +This is pure telemetry: it never modifies the subscription, and the publisher only observes it. +For that reason it is a dedicated, fire-and-forget message rather than a parameter on REQUEST_UPDATE ({{moqt}} Section 10.9). A REQUEST_UPDATE consumes a Request ID ({{moqt}} Section 10.1) and obliges the receiver to answer with a REQUEST_OK or REQUEST_ERROR ({{moqt}} Section 10.9) — a request/response transaction whose purpose is to *modify* the subscription, which is a poor fit for a value pushed once per second that changes nothing. -The SUBSCRIBER_COUNT message instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response. +SUBSCRIBE_STATS instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response. # Setup Negotiation -The Subscriber Count extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 9.4. +The Subscribe Stats extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 9.4. Both endpoints indicate support by including the following Setup Option: ~~~ -SUBSCRIBER_COUNT Setup Option { +SUBSCRIBE_STATS Setup Option { Option Key (vi64) = 0xC0117 Option Value Length (vi64) = 0 } @@ -65,44 +66,65 @@ The extension is available on a hop only if both endpoints on that hop included The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa. Negotiation is mandatory before the message is sent. -{{moqt}} (Section 10) requires an endpoint that receives an unknown control message type to close the session, so — unlike an optional parameter, which can be ignored — a SUBSCRIBER_COUNT message cannot be sent speculatively. -An endpoint MUST NOT send SUBSCRIBER_COUNT on a hop that did not negotiate this extension. +{{moqt}} (Section 10) requires an endpoint that receives an unknown control message type to close the session, so — unlike an optional parameter, which can be ignored — a SUBSCRIBE_STATS message cannot be sent speculatively. +An endpoint MUST NOT send SUBSCRIBE_STATS on a hop that did not negotiate this extension. -# SUBSCRIBER_COUNT Message +# SUBSCRIBE_STATS Message This document defines a new control message, sent on a subscription's request stream ({{moqt}} Section 3.3) by the endpoint that opened it (the subscriber, which for an upstream subscription is the relay). ~~~ -SUBSCRIBER_COUNT Message { +SUBSCRIBE_STATS Message { Type (vi64) = 0xC0117 Length (16) - Subscriber Count (vi64) ; encoded as count - 1 + Number of Stats (vi64) + Stats (..) ... } ~~~ The message MUST NOT be the first message on the request stream; it follows the SUBSCRIBE ({{moqt}} Section 10.7) that opened the stream. It consumes no Request ID ({{moqt}} Section 10.1), and the receiver MUST NOT respond to it. -A subscriber MAY send it any number of times over the life of the subscription to refresh the count. +A subscriber MAY send it any number of times over the life of the subscription to refresh the stats. -**Subscriber Count**: +**Number of Stats**: +The number of stats that follow. + +**Stats**: +Each stat is encoded as a Message Parameter ({{moqt}} Section 10.2): a `Type` (drawn from the same "Message Parameters" registry) and a value, where an even `Type` carries a single varint and an odd `Type` is length-prefixed ({{moqt}} Section 1.4.3). +A receiver MUST ignore a stat whose `Type` it does not recognize, so future stats can be added without a new message type or a wire break. +This document defines one stat, [Subscriber Count](#subscriber-count). + + +# Subscriber Count {#subscriber-count} + +~~~ +SUBSCRIBER_COUNT Stat { + Type (vi64) = 0xC0116 + Value (vi64) ; Subscriber Count - 1 +} +~~~ + +The `Type` `0xC0116` is even, so per {{moqt}} Section 1.4.3 the value is a single varint with no length prefix. + +**Value**: The number of subscribers this subscription represents, including the subscriber itself, encoded as the count minus one. The subscription is the implicit `1`, so the wire value is `Subscriber Count - 1`: a leaf encodes `0`, and a relay encodes its summed total minus one. Encoding `count - 1` rather than the count itself makes a count of `0` impossible to represent on the wire: the minimum encodable value is `0`, which decodes to a count of `1`. A subscription can never report fewer subscribers than itself. -Until a SUBSCRIBER_COUNT message is received for a subscription, its count is `1`. -A leaf subscriber that represents only itself therefore need not send the message at all. +Until a SUBSCRIBE_STATS carrying this stat is received for a subscription, its count is `1`. +A leaf subscriber that represents only itself therefore need not send it at all. # Semantics -The count is a reduction up the subscription tree. +The Subscriber Count is a reduction up the subscription tree. A **leaf subscriber** (one that is not a relay) represents itself: a count of `1`. -It need not send the message to mean this. +It need not report the stat to mean this. A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the count it reports on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating a downstream subscription that has not reported as `1`. -When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, sends a SUBSCRIBER_COUNT message upstream with the new total. +When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, sends a SUBSCRIBE_STATS message upstream with the new total. A relay MUST report at least `1` for any upstream subscription it is holding open, even if it currently has no live downstream subscribers (for example, a subscription briefly retained for reuse). The `count - 1` encoding enforces this floor automatically: a sum of `0` still encodes a wire value of `0`, which decodes upstream as `1`. @@ -111,34 +133,34 @@ A held subscription cannot represent fewer subscribers than itself. Because each relay reports the sum of its subtree, the value telescopes: at the origin, the count on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops. A publisher reads its total audience for a Track as the sum of the counts of the subscriptions it is serving. -The message alters no delivery behavior. +SUBSCRIBE_STATS alters no delivery behavior. It MUST NOT influence prioritization, caching, congestion response, or any other distribution decision; it is informational telemetry carried alongside the subscription. # Rate Limiting -Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBER_COUNT message. +Subscriber churn can change a stat rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBE_STATS message. -A relay SHOULD rate-limit SUBSCRIBER_COUNT messages per subscription, coalescing changes that occur within a short window (on the order of a second) and then sending the latest sum. -Because the message carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. +A relay SHOULD rate-limit SUBSCRIBE_STATS messages per subscription, coalescing changes that occur within a short window (on the order of a second) and then sending the latest values. +Because each stat carries its current value rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. -Because the message is independent of REQUEST_UPDATE, this rate limiting never delays a genuine subscription change: delivery-affecting updates are forwarded according to {{moqt}} without regard to the count's window. +Because the message is independent of REQUEST_UPDATE, this rate limiting never delays a genuine subscription change: delivery-affecting updates are forwarded according to {{moqt}} without regard to the stats window. # Security Considerations **Audience disclosure.** The Subscriber Count discloses aggregate viewership to the publisher and to every relay on the path toward it. For some applications the size of an audience is sensitive (for example, it can reveal the popularity or reach of content, or that an audience has dropped to zero). -Because the extension is negotiated per hop, an endpoint that considers this information sensitive simply does not advertise the SUBSCRIBER_COUNT Setup Option, and no count is exchanged on that hop. +Because the extension is negotiated per hop, an endpoint that considers a stat sensitive simply does not advertise the SUBSCRIBE_STATS Setup Option, and no stats are exchanged on that hop. **Untrusted values.** -The count is supplied by the subscriber side and aggregated by intermediaries, none of which the publisher can fully trust. +Stats are supplied by the subscriber side and aggregated by intermediaries, none of which the publisher can fully trust. A malicious or buggy subscriber can report an inflated or deflated count, and a malicious relay can report any sum regardless of its actual downstream subscriptions. -The value is therefore advisory: an endpoint MUST NOT use it for any security-sensitive purpose — such as billing, admission control, rate limiting, or capacity planning that affects other subscribers — without independent verification. +Stats are therefore advisory: an endpoint MUST NOT use one for any security-sensitive purpose — such as billing, admission control, rate limiting, or capacity planning that affects other subscribers — without independent verification. A relay aggregates the values it receives but cannot attest to the honesty of its downstream peers. **Churn amplification.** -A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated count updates. -The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream SUBSCRIBER_COUNT rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely. +A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated stat updates. +The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream SUBSCRIBE_STATS rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely. Because the message consumes no Request ID and elicits no response, this churn cannot exhaust an identifier space or force the origin into matching replies. This extension introduces no other security considerations beyond those described in {{moqt}}. @@ -156,19 +178,29 @@ A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}} | Value | Name | Reference | |:------|:-----|:----------| -| 0xC0117 | SUBSCRIBER_COUNT | This Document | +| 0xC0117 | SUBSCRIBE_STATS | This Document | ## MOQT Message Types This document registers a control message type. {{moqt}} does not yet establish an IANA registry for message types, so this is a provisional codepoint pending such a registry; the value is chosen to be high and distinctive to avoid the low ranges {{moqt}} assigns and to minimize collisions with provisional registrations by other extensions, and it avoids the greasing pattern (`0x7f * N + 0x9D`). -This is the same value as the SUBSCRIBER_COUNT Setup Option above; Setup Options and message types are independent namespaces, so the shared value is unambiguous. +This is the same value as the SUBSCRIBE_STATS Setup Option above; Setup Options and message types are independent namespaces, so the shared value is unambiguous. The Stream column has the meaning defined by {{moqt}} Section 10: "Request" indicates the message is carried on a bidirectional request stream. The message is not marked "First": it never opens a request stream. | Value | Name | Stream | Reference | |:------|:-----|:-------|:----------| -| 0xC0117 | SUBSCRIBER_COUNT | Request | This Document | +| 0xC0117 | SUBSCRIBE_STATS | Request | This Document | + +## MOQT Message Parameters + +This document requests a registration in the "Message Parameters" registry ({{moqt}} Section 15.7), used here to carry stats inside SUBSCRIBE_STATS. +The value is **even** so that, per the Key-Value-Pair encoding ({{moqt}} Section 1.4.3), the count is carried as a single varint with no length prefix. +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`). + +| Value | Name | Reference | +|:------|:-----|:----------| +| 0xC0116 | SUBSCRIBER_COUNT | This Document | --- back