You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
compression: publisher names the algorithm in the track property
Per implementation feedback, drop the boolean hint + first-intersection
selection. The track property (Publisher Compression / COMPRESSION) now
names the algorithm the publisher used (none/deflate/zstd); SETUP
advertises the decoders each endpoint supports, and the publisher MUST
pick an algorithm its peer advertised (deflate mandatory, so always
safe). Flagless inference is now off the property: a receiver
decompresses iff the property names a non-none algorithm it advertised,
else verbatim. Reverts the per-direction selection and the
"compress and decompress" wording (the list is decode capability again).
Group/subgroup-scoped sliced-stream mechanics, deflate+zstd, and the
RFC 7692 / magicless framing trims are unchanged.
Relays forward the property unchanged and may recompress only with the
same algorithm. Added an explicit "Open issue" note: an immutable
algorithm-naming property means a relay can't transcode (e.g. zstd->
deflate) for a downstream that supports only a different algorithm
without rewriting the property, which moq-transport forbids — flagged
for the working group.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8bLV6vHzucLNvDhPk3bMP
Copy file name to clipboardExpand all lines: draft-lcurley-moq-compression.md
+39-36Lines changed: 39 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,8 +27,8 @@ informative:
27
27
--- abstract
28
28
29
29
This document defines a payload compression extension for MoQ Transport {{moqt}}.
30
-
A track-level Compression property is a boolean hint by which the original publisher marks a track's object payloads as good candidates for compression.
31
-
The algorithm is negotiated independently on each hop, and compression is applied only on a hop where the publisher's hint is set and the sender and receiver share an algorithm; otherwise payloads travel verbatim.
30
+
A track-level Compression property names the algorithm the original publisher used for a track's object payloads.
31
+
Endpoints advertise the algorithms they can decode during SETUP, and a payload is compressed on a hop only when the receiver supports the named algorithm; otherwise it is sent verbatim.
32
32
Compression is scoped to a subgroup: the object payloads of a subgroup form one compressed stream, sliced back into the individual object payloads so the object framing stays in the clear and the decompressed bytes — the actual objects — are unchanged end to end.
33
33
34
34
--- middle
@@ -45,17 +45,16 @@ But MoQ also carries non-media tracks — JSON, text, telemetry, captions, uncom
45
45
For these tracks there is no standard, transport-visible way to compress payloads, so each application reinvents it, and relays cannot help.
46
46
47
47
Like HTTP Transfer-Encoding, the on-wire compression is a hop-by-hop optimization: it does not conceptually change the object payload — the decompressed bytes *are* the object — it only changes how those bytes are carried over a single hop.
48
-
What this extension adds is an end-to-end *signal* — a boolean track property by which the original publisher marks the content as worth compressing — plus a per-hop negotiation of the algorithm.
49
48
50
-
- **Publisher signals, hops apply**: the COMPRESSION track property is a boolean set by the original publisher and carried end to end, but a payload is only compressed on a hop that has negotiated a shared algorithm. Which algorithm is used is decided per hop, not by the publisher; a hop that has not negotiated compression forwards the property unchanged so a further-downstream hop can still act on it.
51
-
- **Per subgroup, sliced into objects**: within a subgroup the object payloads form one compressed stream, flushed at each object boundary so every object still carries its own payload slice, while the object headers and framing stay in the clear. This keeps the subgroup — the unit a receiver already takes as one ordered, reliable stream — as the unit of compression, while letting relays and caches store payloads compressed and re-frame them without recompressing.
49
+
- **Publisher names, hops apply**: the COMPRESSION track property names the algorithm the original publisher used; it is carried end to end and forwarded unchanged. A payload is compressed on a hop only when the receiver advertised that algorithm; otherwise it travels verbatim. Each hop's behavior is fixed by the publisher's algorithm and that hop's negotiation, with no per-object signal.
50
+
- **Per subgroup, sliced into objects**: within a subgroup the object payloads form one compressed stream, flushed at each object boundary so every object still carries its own payload slice, while the object headers and framing stay in the clear. This keeps the subgroup — already one ordered, reliable stream — as the unit of compression, and lets relays and caches store payloads compressed and re-frame them without recompressing.
52
51
53
52
54
53
# Setup Negotiation
55
54
The Payload Compression extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3.
56
55
Unlike a purely additive property, compression MUST be negotiated: a receiver that does not understand the algorithm would otherwise pass the compressed bytes to the application as if they were plaintext.
57
56
58
-
Each endpoint advertises the algorithms it can decompress, in preference order (most-preferred first), by including the following Setup Option:
57
+
Each endpoint advertises the algorithms it can decompress by including the following Setup Option:
59
58
60
59
~~~
61
60
COMPRESSION Setup Option {
@@ -66,60 +65,61 @@ COMPRESSION Setup Option {
66
65
~~~
67
66
68
67
**Algorithm**:
69
-
One or more Algorithm identifiers (see [Compression Algorithms](#compression-algorithms)) that the sender can compress and decompress, each a varint, filling the Option Value, most-preferred first.
68
+
One or more Algorithm identifiers (see [Compression Algorithms](#compression-algorithms)) that the sender can decompress, each a varint, filling the Option Value.
70
69
An endpoint that includes this option MUST list `deflate` (1); the identifier `none` (0) MUST NOT be listed (it requires no negotiation).
71
70
An endpoint that does not support the extension omits the option.
72
71
73
-
Negotiation is per direction and per hop. For a given direction sender-to-receiver, the **selected algorithm** is the first identifier in the receiver's advertised list that also appears in the sender's advertised list; if the lists do not intersect, that direction is verbatim.
74
-
Because each endpoint holds both advertised lists once SETUP has been exchanged, both compute the same selection with no further handshake — the receiver's preference governs its own inbound direction, the two directions are independent and MAY select different algorithms, and a simultaneous SETUP exchange creates no ambiguity.
75
-
Since `deflate` is mandatory for any endpoint that advertises the option, two endpoints that both support the extension always share at least one algorithm.
76
-
Each endpoint lists only algorithms it can both produce and consume, so either side can compute the selection for either direction without a per-object signal.
77
-
A sender MUST NOT compress with an algorithm the receiver did not advertise, and MUST NOT compress at all before it has received the receiver's COMPRESSION option.
72
+
A sender MUST NOT compress with an algorithm the receiver did not advertise, and MUST NOT compress before it has received the receiver's COMPRESSION option.
73
+
This makes the on-wire state unambiguous with no per-object signaling: a receiver decompresses a track's object payloads **if and only if** the COMPRESSION property names a non-`none` algorithm and the receiver advertised that algorithm in its own SETUP.
74
+
In every other case — the property absent or `none`, the extension not negotiated, or the algorithm not advertised by the receiver — the sender was not permitted to compress, so the receiver treats the payloads as verbatim.
78
75
79
76
80
77
# COMPRESSION Track Property
81
-
The COMPRESSION property is the original publisher's end-to-end signal that a track's object payloads are good candidates for compression.
78
+
The COMPRESSION property names the algorithm the original publisher applied to a track's object payloads.
82
79
It is a track-level Key-Value-Pair carried with the track's properties (see {{moqt}} Section 2.5 and Section 12), set by the original publisher and forwarded unchanged by relays.
83
80
Because the value is a single integer, COMPRESSION uses an even Type so the value is a bare varint:
84
81
85
82
~~~
86
83
COMPRESSION Track Property {
87
84
Type (vi64) = 0xC03D0
88
-
Value (vi64) ; boolean hint
85
+
Value (vi64) ; Algorithm identifier
89
86
}
90
87
~~~
91
88
92
89
**Value**:
93
-
A boolean hint: `1`means the track's payloads are good candidates for compression, `0` (or absence of the property) means they are not and are always transmitted verbatim.
94
-
Values greater than `1` are reserved for future use and MUST be treated as `1` by a receiver that does not understand them, so the hint stays additive.
95
-
The property names no algorithm; which algorithm is used, if any, is the per-hop [selected algorithm](#setup-negotiation).
90
+
The Algorithm identifier the publisher used for this track's payloads (see [Compression Algorithms](#compression-algorithms)).
91
+
The absence of the property, or a value of `none` (0), means the track is uncompressed and its payloads are always transmitted verbatim.
92
+
The publisher MUST choose an algorithm that its peer advertised in the [COMPRESSION Setup Option](#setup-negotiation); since `deflate` is mandatory to implement, it is always a safe choice.
96
93
97
94
The property is fixed for the lifetime of the track and MUST NOT change.
98
-
A relay MUST forward it unchanged on every hop, including a hop that has not negotiated the extension: there it is simply an ignored unknown Key-Value-Pair, but forwarding it lets a further-downstream hop that does negotiate the extension still act on the publisher's signal.
95
+
A relay MUST forward it unchanged on every hop, including a hop that has not negotiated the extension: there it is simply an ignored unknown Key-Value-Pair, but forwarding it lets a further-downstream hop that does negotiate the extension still act on the publisher's algorithm.
99
96
100
-
Compression is enabled only by the combination of a non-zero hint and a shared algorithm being negotiated on a hop; there is no per-object or per-subgroup signal on the wire.
101
-
A receiver decompresses a track's object payloads **if and only if** the COMPRESSION hint is non-zero and the hop selected an algorithm for that direction, in which case it uses the selected algorithm.
102
-
In every other case — the hint absent or zero, the extension not negotiated, or the lists not intersecting — payloads are verbatim.
97
+
Whether a payload is actually compressed is decided per hop:
103
98
104
-
A publisher SHOULD set COMPRESSION only for payload types that benefit from it (e.g. JSON, text, uncompressed binary structures).
105
-
Already-compressed media SHOULD omit it (or use `0`).
99
+
- On a hop where the receiver advertised the property's algorithm, each non-empty object payload is compressed with that algorithm, and the receiver decompresses it.
100
+
- On any other hop — the extension not negotiated, or the receiver did not advertise that algorithm — payloads are sent verbatim, and the receiver treats them as such.
101
+
102
+
Compression applies to the object payload only; object headers, properties, and message framing are never compressed.
103
+
An empty payload (size 0) MUST NOT be compressed and remains empty on the wire.
104
+
105
+
A publisher SHOULD set COMPRESSION only for payload types that benefit from it.
106
+
Already-compressed media SHOULD omit it (or use `none`).
106
107
107
108
108
109
# Compression {#compression}
109
-
Compression is applied to object payloads only — object headers, properties, and message framing are never compressed — and is **scoped to a subgroup**.
110
-
Within a subgroup the object payloads form a single compressed stream in the [selected algorithm](#setup-negotiation), reset at each subgroup boundary.
110
+
Compression is **scoped to a subgroup**.
111
+
Within a subgroup the object payloads form a single compressed stream in the algorithm named by the [COMPRESSION property](#compression-track-property), reset at each subgroup boundary.
111
112
The stream's output is partitioned at object boundaries: the compressor flushes at the end of each object so that object's slice is exactly the bytes carried as its payload, and the payload length in the object header gives the on-wire (compressed) slice size.
112
113
Both algorithms provide a window-retaining flush (DEFLATE's sync flush; Zstandard's `ZSTD_e_flush`), so later objects in a subgroup reuse the compression context and retain cross-object redundancy.
113
114
114
115
A receiver maintains a single decoder per subgroup, reset at each subgroup boundary, and feeds each object's payload through it in order: the first object of a subgroup starts the decoder fresh — so a receiver joining at a group boundary needs nothing earlier — while later objects build on it.
115
-
There is no shared state between subgroups; an empty payload (size 0) contributes nothing to the stream and remains empty on the wire.
116
+
There is no shared state between subgroups; an empty payload contributes nothing to the stream.
116
117
An object delivered as a datagram is a single-object stream, compressed on its own.
117
118
118
119
Because the object framing already delimits each slice, an algorithm's own redundant boundary and container bytes are omitted: for `deflate`, the trailing four `00 00 FF FF` bytes a sync flush emits are removed from each payload and the decoder re-inserts them (as in {{RFC7692}}); for `zstd`, the per-subgroup stream uses the magicless frame format and omits the content checksum.
119
120
120
121
Leaving the framing uncompressed is deliberate.
121
122
A relay or cache can hold the object payloads compressed in memory and forward them without inflating, and can re-frame a subgroup — for example to bridge a transport version that changes the subgroup or object headers — without touching the compressed payloads.
122
-
Neither is possible if the framing is buried inside the compressed stream.
@@ -130,29 +130,32 @@ This document defines the following algorithms.
130
130
| 1 | deflate | mandatory | Raw DEFLATE {{RFC1951}}, with no zlib or gzip framing. |
131
131
| 2 | zstd | optional | Zstandard {{RFC8878}}. |
132
132
133
-
Every endpoint that advertises this extension MUST implement `deflate`, so a common algorithm always exists; `zstd` is optional.
133
+
Every endpoint that advertises this extension MUST implement `deflate`, so the publisher always has a safe choice; `zstd` is optional.
134
134
Further algorithms MAY be registered (see [IANA Considerations](#iana-considerations)).
135
135
136
136
137
137
# Relay Behavior
138
-
A relay forwards the boolean COMPRESSION track property unchanged — it is the publisher's end-to-end signal — and applies compression independently on each hop, driven by each hop's negotiation rather than by its own initiative; a relay does not compress a track the publisher did not mark.
138
+
A relay forwards the COMPRESSION track property unchanged — it is the publisher's end-to-end signal — and applies compression independently on each hop, driven by each hop's negotiation rather than by its own initiative; a relay does not compress a track the publisher did not mark.
139
+
140
+
On its upstream subscription the relay receives each subgroup compressed with the property's algorithm (if it advertised that algorithm) or verbatim, and decompresses as needed.
141
+
On a downstream subscription that advertised the property's algorithm, it sends each subgroup compressed with that algorithm (recompressing as needed); on one that did not, it sends the subgroup verbatim.
142
+
A relay MUST NOT recompress with an algorithm other than the one the property names, because the property tells the receiver how to decode and a relay MUST NOT rewrite it.
143
+
In every case the decompressed bytes delivered to the application MUST be identical to what the origin published, and a relay or generic library MUST NOT inspect or modify the decompressed contents unless otherwise negotiated.
139
144
140
-
On its upstream subscription the relay receives each subgroup compressed with that hop's selected algorithm (or verbatim) and decompresses as needed.
141
-
On each downstream subscription it (re)compresses each subgroup with that downstream's selected algorithm, or sends it verbatim when no algorithm is shared.
142
-
A relay MAY transcode between algorithms.
143
-
In every case the decompressed bytes delivered to the application MUST be identical to what the origin published.
144
-
A relay or generic library MUST NOT inspect or modify the decompressed contents unless otherwise negotiated; only recompression that preserves the decompressed bytes exactly is permitted.
145
+
Open issue:
146
+
because the COMPRESSION property both names the algorithm and is immutable, a downstream that supports a *different* algorithm than the publisher chose (for example only `deflate` when the publisher used `zstd`) receives the payloads verbatim rather than transcoded — a relay cannot offer it the algorithm it does support without rewriting the property, which {{moqt}} forbids.
147
+
Whether to relax this — by permitting a relay to rewrite this property for a downstream subscription, or by carrying the per-hop algorithm as transport metadata rather than an end-to-end track property — is an open question for the working group.
145
148
146
149
147
150
# Security Considerations
148
151
Compressing data that mixes attacker-controlled and secret content can leak the secret through compressed size, as in the CRIME and BREACH attacks.
149
-
A publisher MUST NOT set a non-zero COMPRESSION hint on a track whose object payloads combine secret material with attacker-influenced material.
152
+
A publisher MUST NOT set COMPRESSION on a track whose object payloads combine secret material with attacker-influenced material.
150
153
Because compression is scoped to a subgroup, the exposure is bounded to within a single subgroup — which may combine several objects, a wider window than a single object — but it is not eliminated.
151
154
152
155
A malicious sender could emit a small compressed payload that decompresses to a very large buffer (a "decompression bomb").
153
156
Because compression is subgroup-scoped, a receiver MUST bound the cumulative decompressed size of a subgroup — not merely each object's payload, since many small payloads can otherwise accumulate without limit. If the bound is exceeded it MUST reset the affected stream (rather than allocate unbounded memory) and MAY close the session with a PROTOCOL_VIOLATION if it considers the peer abusive; the reset is stream-scoped so a single bad subgroup does not tear down unrelated subscriptions.
154
157
155
-
Compression is orthogonal to {{moqt}} end-to-end encryption: an encrypted payload is effectively incompressible, so a publisher using end-to-end encryption SHOULD omit COMPRESSION (or use `0`).
158
+
Compression is orthogonal to {{moqt}} end-to-end encryption: an encrypted payload is effectively incompressible, so a publisher using end-to-end encryption SHOULD omit COMPRESSION (or use `none`).
0 commit comments