|
| 1 | +--- |
| 2 | +title: "MoQ Demand Extension" |
| 3 | +abbrev: "moq-demand" |
| 4 | +category: info |
| 5 | + |
| 6 | +docname: draft-lcurley-moq-demand-latest |
| 7 | +submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" |
| 8 | +number: |
| 9 | +date: |
| 10 | +v: 3 |
| 11 | +area: wit |
| 12 | +workgroup: moq |
| 13 | + |
| 14 | +author: |
| 15 | + - |
| 16 | + fullname: Luke Curley |
| 17 | + email: kixelated@gmail.com |
| 18 | + |
| 19 | +normative: |
| 20 | + moqt: I-D.ietf-moq-transport |
| 21 | + |
| 22 | +informative: |
| 23 | + |
| 24 | +--- abstract |
| 25 | + |
| 26 | +This document defines a SUBSCRIBE_DEMAND message for MoQ Transport {{moqt}}: fire-and-forget feedback that a subscriber reports about a subscription describing the downstream demand for it. |
| 27 | +It carries how many subscribers a subscription represents — as a pair of cumulative counts whose difference telescopes up the relay fan-out tree, letting a publisher learn its total audience across any number of hops — and an optional Group Request asking the publisher to produce a new group once the subscriber has fallen behind. |
| 28 | + |
| 29 | +--- middle |
| 30 | + |
| 31 | +# Conventions and Definitions |
| 32 | +{::boilerplate bcp14-tagged} |
| 33 | + |
| 34 | + |
| 35 | +# Introduction |
| 36 | +A publisher in {{moqt}} often wants to know the demand for a Track: how many subscribers are receiving it, and whether any of them needs a fresh group to make progress. |
| 37 | +Both are 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). |
| 38 | +The origin sees one upstream subscription per relay, not the individual subscribers behind it, so it can neither count its true audience nor learn what those subscribers need without out-of-band coordination. |
| 39 | + |
| 40 | +This document defines a SUBSCRIBE_DEMAND message that reports this demand back up the subscription path. |
| 41 | +It carries two kinds of information, each chosen so that it aggregates cheaply up the fan-out tree: |
| 42 | + |
| 43 | +- **Audience size**, as a pair of cumulative counts, `Subscriptions Created` and `Subscriptions Closed`. A relay reports the **sum** of each across the downstream subscriptions it serves, so the difference — the current number of subscribers — telescopes for free. At the origin, the demand on each upstream subscription is the total number of subscribers reachable through that relay, transitively, across any number of hops. |
| 44 | +- A **Group Request**, the minimum group a subscriber wants the publisher to produce. A relay reports the **maximum** across its downstreams (less any it can already satisfy from cache). It is expressed as a *level* — "I want a group at least this new" — not a one-shot trigger, so it is idempotent and deduplicates naturally as it aggregates. |
| 45 | + |
| 46 | +Demand changes as subscribers join, leave, and fall behind, so SUBSCRIBE_DEMAND is sent repeatedly over the life of a subscription. |
| 47 | +It is a dedicated, fire-and-forget message rather than a parameter on 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's delivery range or priority, which is a poor fit for feedback pushed repeatedly that changes neither. |
| 49 | +SUBSCRIBE_DEMAND instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response. |
| 50 | + |
| 51 | + |
| 52 | +# Setup Negotiation |
| 53 | +The Demand extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 9.4. |
| 54 | + |
| 55 | +Both endpoints indicate support by including the following Setup Option: |
| 56 | + |
| 57 | +~~~ |
| 58 | +SUBSCRIBE_DEMAND Setup Option { |
| 59 | + Option Key (vi64) = 0xC0117 |
| 60 | + Option Value Length (vi64) = 0 |
| 61 | +} |
| 62 | +~~~ |
| 63 | + |
| 64 | +The extension is available on a hop only if both endpoints on that hop included this option. |
| 65 | +The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa. |
| 66 | + |
| 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 SUBSCRIBE_DEMAND message cannot be sent speculatively. |
| 69 | +An endpoint MUST NOT send SUBSCRIBE_DEMAND on a hop that did not negotiate this extension. |
| 70 | + |
| 71 | + |
| 72 | +# SUBSCRIBE_DEMAND 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). |
| 74 | + |
| 75 | +~~~ |
| 76 | +SUBSCRIBE_DEMAND Message { |
| 77 | + Type (vi64) = 0xC0117 |
| 78 | + Length (16) |
| 79 | + Subscriptions Created (vi64) |
| 80 | + Subscriptions Closed (vi64) |
| 81 | + Group Request (vi64) |
| 82 | +} |
| 83 | +~~~ |
| 84 | + |
| 85 | +The message MUST NOT be the first message on the request stream; it follows the SUBSCRIBE ({{moqt}} Section 10.7) that opened the stream. |
| 86 | +It consumes no Request ID ({{moqt}} Section 10.1), and the receiver MUST NOT respond to it. |
| 87 | +A subscriber MAY send it any number of times over the life of the subscription to refresh the values. |
| 88 | + |
| 89 | +**Subscriptions Created** and **Subscriptions Closed**: |
| 90 | +Cumulative counts, over the life of this subscription, of the downstream subscriptions it represents that have been created and closed respectively. |
| 91 | +The current demand — the number of subscribers presently receiving the Track through this subscription — is `Subscriptions Created - Subscriptions Closed`. |
| 92 | +A leaf subscriber represents only itself: `Subscriptions Created` is `1` and `Subscriptions Closed` is `0`. |
| 93 | +These are the defaults until a SUBSCRIBE_DEMAND is received, so a subscriber that represents only itself need not send the message. |
| 94 | + |
| 95 | +**Group Request**: |
| 96 | +The minimum group the subscriber wants the publisher to produce. |
| 97 | +A value of `0` means no request: the publisher produces groups at its own cadence. |
| 98 | +A non-zero value `N` requests that the publisher produce a group with Group ID at least `N - 1`; the offset by one keeps `0` available as "no request" while leaving Group ID `0` requestable. |
| 99 | +See [Group Requests](#group-requests) for the semantics. |
| 100 | + |
| 101 | + |
| 102 | +# Semantics |
| 103 | + |
| 104 | +## Audience Size |
| 105 | +The audience size is a reduction up the subscription tree. |
| 106 | + |
| 107 | +A **leaf subscriber** (one that is not a relay) represents itself: `Subscriptions Created` of `1` and `Subscriptions Closed` of `0`, a demand of `1`. |
| 108 | +It need not report this default. |
| 109 | + |
| 110 | +A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription reports, on that upstream subscription, the **sum** of the `Subscriptions Created` of its downstreams and the **sum** of their `Subscriptions Closed`, treating a downstream that has not reported as `1` created and `0` closed. |
| 111 | +It increments `Subscriptions Created` as downstream subscriptions are created and `Subscriptions Closed` as they are closed, and SHOULD keep both counts non-decreasing over the upstream subscription's life — accounting a fully-departed downstream's outstanding demand (its last-reported `Created - Closed`) as newly closed — so that neither count moves backward when a downstream detaches. |
| 112 | +When either sum changes, the relay sends a SUBSCRIBE_DEMAND message upstream with the new totals. |
| 113 | + |
| 114 | +Because each relay reports the sum of its subtree, the difference telescopes: at the origin, `Created - Closed` on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops. |
| 115 | +A publisher reads its total audience for a Track as the sum of `Created - Closed` over the subscriptions it is serving. |
| 116 | + |
| 117 | +Reporting the two counts separately, rather than a single current-demand gauge, lets a publisher distinguish *churn* from a *new arrival*: a rising `Subscriptions Created` means at least one new subscriber has joined, which a publisher MAY treat as an implicit [Group Request](#group-requests), since a newly-joined subscriber generally needs a fresh group (e.g. a keyframe) to begin decoding. |
| 118 | + |
| 119 | +## Group Requests {#group-requests} |
| 120 | +A subscriber raises `Group Request` to ask the publisher to start a new group once it has fallen too far behind the live edge to catch up — for example, after missing the group with ID `5` it requests `6` to jump to the next group rather than wait for it to be produced naturally. |
| 121 | + |
| 122 | +The request is a **level**, not an edge: it names the minimum group the subscriber needs, and is satisfied the moment a group at or beyond it exists. |
| 123 | +A publisher that has already produced a group with Group ID at or above the request takes no action — the request is already met. |
| 124 | +This is the key difference from a one-shot "produce a new group now" signal: because the request is idempotent, it can be retransmitted, coalesced, and aggregated without a publisher producing one redundant group per copy it receives. |
| 125 | + |
| 126 | +`Group Request` fans *in* at a relay as the **maximum** of its downstreams' requests, minus any the relay can already satisfy itself: a relay that holds a group at or beyond a downstream's request serves it from cache and does not propagate it; it forwards a request upstream only when it lacks a group at or beyond the highest value its downstreams want. |
| 127 | +Once the publisher produces a group satisfying the highest request, every lower request is satisfied at once. |
| 128 | + |
| 129 | +A publisher SHOULD honor a `Group Request` by producing a new group as soon as it can (subject to its own encoding constraints, such as a keyframe boundary), but MAY decline or defer it; the request does not override the publisher's control of its own Track. |
| 130 | +`Group Request` is the only field of this message that affects delivery. The audience-size counts MUST NOT influence prioritization, caching, congestion response, or any other distribution decision beyond the optional new-group hint above. |
| 131 | + |
| 132 | + |
| 133 | +# Rate Limiting |
| 134 | +Subscriber churn can change the audience size rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBE_DEMAND message. |
| 135 | + |
| 136 | +A relay SHOULD rate-limit SUBSCRIBE_DEMAND messages per subscription, coalescing audience-size changes that occur within a short window (on the order of a second) and then sending the latest values. |
| 137 | +Because each message carries current values rather than deltas, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all. |
| 138 | + |
| 139 | +A `Group Request` increase is latency-sensitive — the subscriber is stalled waiting for a group it can decode — and SHOULD be forwarded promptly rather than held for the audience-size window. |
| 140 | +Because the message is independent of REQUEST_UPDATE, neither kind of update delays a genuine subscription change: delivery-affecting updates are forwarded according to {{moqt}} without regard to the demand window. |
| 141 | + |
| 142 | + |
| 143 | +# Security Considerations |
| 144 | +**Audience disclosure.** |
| 145 | +`Subscriptions Created` and `Subscriptions Closed` disclose aggregate viewership to the publisher and to every relay on the path toward it. |
| 146 | +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). |
| 147 | +Because the extension is negotiated per hop, an endpoint that considers this sensitive simply does not advertise the SUBSCRIBE_DEMAND Setup Option, and no demand is exchanged on that hop. |
| 148 | + |
| 149 | +**Untrusted values.** |
| 150 | +The values are supplied by the subscriber side and aggregated by intermediaries, none of which the publisher can fully trust. |
| 151 | +A malicious or buggy subscriber can report inflated or deflated counts, and a malicious relay can report any sums regardless of its actual downstream subscriptions. |
| 152 | +The audience-size counts 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. |
| 153 | +A `Group Request` can at most ask the publisher to produce a group it could already have produced for any subscriber, and a publisher MAY decline it; honoring requests at an unbounded rate would let a subscriber drive group production, so a publisher SHOULD bound the rate at which it acts on requests. |
| 154 | + |
| 155 | +**Churn amplification.** |
| 156 | +A subscriber that rapidly joins and leaves, or repeatedly raises its `Group Request`, could attempt to amplify control traffic toward the origin. |
| 157 | +The rate-limiting in [Rate Limiting](#rate-limiting) bounds the audience-size case, and the idempotent, level-based `Group Request` collapses repeated identical requests into a single upstream value and a single produced group. |
| 158 | +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. |
| 159 | + |
| 160 | +This extension introduces no other security considerations beyond those described in {{moqt}}. |
| 161 | + |
| 162 | + |
| 163 | +# IANA Considerations |
| 164 | + |
| 165 | +This document requests the following registrations. |
| 166 | + |
| 167 | +## MOQT Setup Options |
| 168 | + |
| 169 | +This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required. |
| 170 | +moq-transport defines no private-use range for Setup Options; extensions request a (provisional) codepoint. |
| 171 | +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`). |
| 172 | + |
| 173 | +| Value | Name | Reference | |
| 174 | +|:------|:-----|:----------| |
| 175 | +| 0xC0117 | SUBSCRIBE_DEMAND | This Document | |
| 176 | + |
| 177 | +## MOQT Message Types |
| 178 | + |
| 179 | +This document registers a control message type. |
| 180 | +{{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`). |
| 181 | +This is the same value as the SUBSCRIBE_DEMAND Setup Option above; Setup Options and message types are independent namespaces, so the shared value is unambiguous. |
| 182 | + |
| 183 | +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. |
| 184 | + |
| 185 | +| Value | Name | Stream | Reference | |
| 186 | +|:------|:-----|:-------|:----------| |
| 187 | +| 0xC0117 | SUBSCRIBE_DEMAND | Request | This Document | |
| 188 | + |
| 189 | + |
| 190 | +--- back |
| 191 | + |
| 192 | +# Acknowledgments |
| 193 | +{:numbered="false"} |
| 194 | + |
| 195 | +This document was drafted with the assistance of Claude, an AI assistant by Anthropic. |
0 commit comments