Skip to content

Commit 92d4442

Browse files
kixelatedclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 35d8a2e commit 92d4442

1 file changed

Lines changed: 45 additions & 37 deletions

File tree

draft-lcurley-moq-subscriber-count.md

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ informative:
2323

2424
--- abstract
2525

26-
This document defines a Subscriber Count parameter for MoQ Transport {{moqt}}.
27-
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.
26+
This document defines a SUBSCRIBER_COUNT message for MoQ Transport {{moqt}}.
27+
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.
28+
It is a fire-and-forget telemetry message that consumes no Request ID and elicits no response.
2829

2930
--- middle
3031

@@ -37,14 +38,15 @@ A publisher in {{moqt}} often wants to know how many subscribers are receiving a
3738
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).
3839
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.
3940

40-
This document defines a Subscriber Count parameter that rides on the subscription itself.
41-
Each subscription reports how many subscribers it represents.
41+
This document defines a SUBSCRIBER_COUNT message that reports, per subscription, how many subscribers it represents.
4242
A relay sets the count on its single upstream subscription to the **sum** of the counts of the downstream subscriptions it aggregates.
4343
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.
4444

45-
The count is a property of the subscriber side of the subscription.
46-
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.
47-
It changes as subscribers join and leave and is refreshed with REQUEST_UPDATE ({{moqt}} Section 10.9).
45+
The count changes as subscribers join and leave, so it is sent repeatedly over the life of a subscription.
46+
This is pure telemetry: the count never modifies the subscription, and the publisher only observes it.
47+
For that reason it is carried in a dedicated, fire-and-forget message rather than in REQUEST_UPDATE ({{moqt}} Section 10.9).
48+
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.
49+
The SUBSCRIBER_COUNT message instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response.
4850

4951

5052
# Setup Negotiation
@@ -60,63 +62,66 @@ SUBSCRIBER_COUNT Setup Option {
6062
~~~
6163

6264
The extension is available on a hop only if both endpoints on that hop included this option.
63-
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.
65+
The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa.
6466

65-
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.
66-
Ignoring an un-negotiated count simply collapses that subscription's contribution to the default of `1` (see [Semantics](#semantics)); it does not break delivery.
67+
Negotiation is mandatory before the message is sent.
68+
{{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.
69+
An endpoint MUST NOT send SUBSCRIBER_COUNT on a hop that did not negotiate this extension.
6770

6871

69-
# Subscriber Count Parameter
70-
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).
72+
# SUBSCRIBER_COUNT Message
73+
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).
7174

7275
~~~
73-
SUBSCRIBER_COUNT Parameter {
74-
Type (vi64) = 0xC0116
75-
Value (vi64) ; Subscriber Count - 1
76+
SUBSCRIBER_COUNT Message {
77+
Type (vi64) = 0xC0117
78+
Length (16)
79+
Subscriber Count (vi64) ; encoded as count - 1
7680
}
7781
~~~
7882

79-
The Type `0xC0116` is even, so per {{moqt}} Section 1.4.3 the Value is a single varint with no length prefix.
83+
The message MUST NOT be the first message on the request stream; it follows the SUBSCRIBE ({{moqt}} Section 10.7) that opened the stream.
84+
It consumes no Request ID ({{moqt}} Section 10.1), and the receiver MUST NOT respond to it.
85+
A subscriber MAY send it any number of times over the life of the subscription to refresh the count.
8086

81-
**Value**:
87+
**Subscriber Count**:
8288
The number of subscribers this subscription represents, including the subscriber itself, encoded as the count minus one.
8389
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.
8490

8591
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`.
8692
A subscription can never report fewer subscribers than itself.
8793

88-
A subscriber MAY include this parameter in SUBSCRIBE and in any REQUEST_UPDATE.
89-
If the parameter is absent, the wire value is treated as `0`, so the count for that subscription is `1`.
90-
A leaf subscriber therefore reports `1`, whether by sending a wire value of `0` or by omitting the parameter.
94+
Until a SUBSCRIBER_COUNT message is received for a subscription, its count is `1`.
95+
A leaf subscriber that represents only itself therefore need not send the message at all.
9196

9297

9398
# Semantics
9499
The count is a reduction up the subscription tree.
95100

96101
A **leaf subscriber** (one that is not a relay) represents itself: a count of `1`.
97-
It MAY omit the parameter to mean the same thing.
102+
It need not send the message to mean this.
98103

99-
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`.
100-
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.
104+
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`.
105+
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.
101106

102107
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).
103108
The `count - 1` encoding enforces this floor automatically: a sum of `0` still encodes a wire value of `0`, which decodes upstream as `1`.
104109
A held subscription cannot represent fewer subscribers than itself.
105110

106-
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.
111+
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.
107112
A publisher reads its total audience for a Track as the sum of the counts of the subscriptions it is serving.
108113

109-
This parameter alters no delivery behavior.
114+
The message alters no delivery behavior.
110115
It MUST NOT influence prioritization, caching, congestion response, or any other distribution decision; it is informational telemetry carried alongside the subscription.
111116

112117

113118
# Rate Limiting
114-
Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream REQUEST_UPDATE.
119+
Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBER_COUNT message.
115120

116-
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.
117-
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.
121+
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.
122+
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.
118123

119-
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.
124+
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.
120125

121126

122127
# Security Considerations
@@ -133,7 +138,8 @@ A relay aggregates the values it receives but cannot attest to the honesty of it
133138

134139
**Churn amplification.**
135140
A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated count updates.
136-
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.
141+
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.
142+
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.
137143

138144
This extension introduces no other security considerations beyond those described in {{moqt}}.
139145

@@ -152,15 +158,17 @@ A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}}
152158
|:------|:-----|:----------|
153159
| 0xC0117 | SUBSCRIBER_COUNT | This Document |
154160

155-
## MOQT Message Parameters
161+
## MOQT Message Types
156162

157-
This document requests a registration in the "Message Parameters" registry ({{moqt}} Section 15.7).
158-
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.
159-
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`).
163+
This document registers a control message type.
164+
{{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`).
165+
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.
160166

161-
| Value | Name | Reference |
162-
|:------|:-----|:----------|
163-
| 0xC0116 | SUBSCRIBER_COUNT | This Document |
167+
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.
168+
169+
| Value | Name | Stream | Reference |
170+
|:------|:-----|:-------|:----------|
171+
| 0xC0117 | SUBSCRIBER_COUNT | Request | This Document |
164172

165173

166174
--- back

0 commit comments

Comments
 (0)