Skip to content

Commit 0d7a1f2

Browse files
committed
compression: negotiate deflate/zstd per hop; rewrite moq-compression to match
Add a second algorithm (zstd) and per-hop algorithm negotiation, now that browsers do zstd natively and its dictionary support suits MoQ's small, repetitive non-media payloads. deflate stays the mandatory baseline. Negotiation (both drafts): each endpoint advertises the algorithms it can de/compress in preference order; for a direction, the algorithm is the first in the receiver's list that the sender also lists. Both ends compute it identically from the two advertised lists, so a simultaneous SETUP can't disagree; the two directions are independent and may differ. deflate is mandatory, so a common algorithm always exists. moq-lite: the SETUP Compression parameter carries the ordered list (was a presence flag); the Compression section gains the algorithm table and per-algorithm framing trims (RFC 7692 for deflate; magicless, checksum-less frames for zstd). Still flagless and payload-only. moq-compression: rewritten to the same model — boolean COMPRESSION track Property (was an algorithm id) + COMPRESSION Setup Option carrying the ordered list + first-intersection selection + flagless inference + subgroup-scoped sliced stream. Drops the per-object override; the algorithms registry gains zstd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8bLV6vHzucLNvDhPk3bMP
1 parent 1052730 commit 0d7a1f2

2 files changed

Lines changed: 90 additions & 64 deletions

File tree

draft-lcurley-moq-compression.md

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ author:
1919
normative:
2020
moqt: I-D.ietf-moq-transport
2121
RFC1951:
22+
RFC8878:
2223

2324
informative:
25+
RFC7692:
2426

2527
--- abstract
2628

2729
This document defines a payload compression extension for MoQ Transport {{moqt}}.
28-
A track-level Compression property lets the original publisher signal that a track's object payloads are worth compressing, and with which algorithm.
29-
Compression is then applied independently on each hop: a payload is compressed only on a hop that has negotiated the extension and whose receiver supports the algorithm, and is sent verbatim otherwise.
30-
Each object is compressed independently so objects remain individually decodable, and the decompressed bytes — the actual object — are unchanged end to end.
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.
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.
3133

3234
--- middle
3335

@@ -43,17 +45,17 @@ But MoQ also carries non-media tracks — JSON, text, telemetry, captions, uncom
4345
For these tracks there is no standard, transport-visible way to compress payloads, so each application reinvents it, and relays cannot help.
4446

4547
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.
46-
What this extension adds on top is an end-to-end *signal*: a track property by which the original publisher marks the content as worth compressing and names the algorithm. The signal travels end-to-end; the compression happens per 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.
4749

48-
- **Publisher signals, hops apply**: the COMPRESSION track property is set by the original publisher and carried end-to-end, but a payload is only compressed on a hop that negotiated the extension and whose receiver supports the algorithm. Where the extension is not negotiated, the same payload travels verbatim.
49-
- **Per object, independently**: each object payload is an independent compressed stream with no shared dictionary or state between objects. This keeps every object individually decodable and avoids head-of-line decoding within a group.
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.
5052

5153

5254
# Setup Negotiation
5355
The Payload Compression extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3.
5456
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.
5557

56-
Each endpoint advertises the algorithms it can decompress by including the following Setup Option:
58+
Each endpoint advertises the algorithms it can decompress, in preference order (most-preferred first), by including the following Setup Option:
5759

5860
~~~
5961
COMPRESSION Setup Option {
@@ -64,88 +66,99 @@ COMPRESSION Setup Option {
6466
~~~
6567

6668
**Algorithm**:
67-
One or more Algorithm identifiers (see [Compression Algorithms](#compression-algorithms)) that the sender can decompress, each a varint, filling the Option Value.
68-
The identifier `none` (0) MUST NOT be listed (it requires no negotiation).
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.
70+
An endpoint that includes this option MUST list `deflate` (1); the identifier `none` (0) MUST NOT be listed (it requires no negotiation).
71+
An endpoint that does not support the extension omits the option.
6972

70-
A sender MUST NOT compress with an algorithm the receiver did not advertise in its SETUP.
71-
This makes the on-wire state unambiguous on every hop without any per-object signaling: a receiver decompresses a track's payloads **if and only if** the COMPRESSION track property is present and the receiver advertised that algorithm in its own SETUP. In every other case — the property absent, 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.
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+
A sender MUST NOT compress with an algorithm the receiver did not advertise.
7277

7378

7479
# COMPRESSION Track Property
75-
The COMPRESSION property is the original publisher's signal that a track's object payloads are worth compressing, and which algorithm to use.
80+
The COMPRESSION property is the original publisher's end-to-end signal that a track's object payloads are good candidates for compression.
7681
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.
7782
Because the value is a single integer, COMPRESSION uses an even Type so the value is a bare varint:
7883

7984
~~~
8085
COMPRESSION Track Property {
8186
Type (vi64) = 0xC03D0
82-
Value (vi64) ; Algorithm identifier
87+
Value (vi64) ; boolean hint
8388
}
8489
~~~
8590

8691
**Value**:
87-
The Algorithm identifier the publisher recommends for this track's payloads.
88-
The absence of the property, or a value of `none` (0), means the track is not marked for compression and its payloads are always transmitted verbatim.
92+
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.
93+
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.
94+
The property names no algorithm; which algorithm is used, if any, is the per-hop [selected algorithm](#setup-negotiation).
8995

9096
The property is fixed for the lifetime of the track and MUST NOT change.
9197
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.
9298

93-
Compression is enabled only by the combination of this track property and the extension being negotiated on a hop.
94-
A publisher MUST NOT compress object payloads on a track that does not carry the COMPRESSION property, and there is no way to enable compression on a per-object basis: the property governs the whole track, and on a compressing hop every non-empty payload is compressed.
99+
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.
100+
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.
101+
In every other case — the hint absent or zero, the extension not negotiated, or the lists not intersecting — payloads are verbatim.
95102

96-
Whether payloads are actually compressed is decided per hop:
103+
A publisher SHOULD set COMPRESSION only for payload types that benefit from it (e.g. JSON, text, uncompressed binary structures).
104+
Already-compressed media SHOULD omit it (or use `0`).
97105

98-
- On a hop where the extension is negotiated and the receiver advertised the property's algorithm, every non-empty object payload MUST be compressed with that algorithm, and the receiver decompresses it.
99-
- On any other hop — the extension not negotiated, or the receiver did not advertise that algorithm — payloads are sent verbatim. The receiver either never sees the property (an ignored unknown Key-Value-Pair) or sees it but knows the sender was not permitted to compress for it, so it treats the payloads as verbatim either way.
100106

101-
Compression applies to the object payload only; object properties and message framing are never compressed.
102-
An empty payload (size 0) MUST NOT be compressed and remains empty on the wire.
107+
# Compression {#compression}
108+
Compression is applied to object payloads only — object headers, properties, and message framing are never compressed — and is **scoped to a subgroup**.
109+
Within a subgroup the object payloads form a single compressed stream in the [selected algorithm](#setup-negotiation), reset at each subgroup boundary.
110+
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.
111+
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.
103112

104-
A publisher SHOULD set COMPRESSION only for payload types that benefit from it.
105-
Already-compressed media SHOULD omit it (or use `none`).
113+
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.
114+
There is no shared state between subgroups; an empty payload (size 0) contributes nothing to the stream and remains empty on the wire.
115+
An object delivered as a datagram is a single-object stream, compressed on its own.
106116

117+
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.
107118

108-
# Compression Algorithms {#compression-algorithms}
119+
Leaving the framing uncompressed is deliberate.
120+
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.
121+
Neither is possible if the framing is buried inside the compressed stream.
122+
123+
## Compression Algorithms {#compression-algorithms}
109124
This document defines the following algorithms.
110-
Further algorithms MAY be registered (see [IANA Considerations](#iana-considerations)).
111125

112-
| ID | Name | Description |
113-
|---:|:--------|:--------------------------------------------------------|
114-
| 0 | none | Payloads are transmitted verbatim. The default. |
115-
| 1 | deflate | Raw DEFLATE {{RFC1951}}, with no zlib or gzip framing. |
126+
| ID | Name | Requirement | Description |
127+
|---:|:--------|:------------|:--------------------------------------------------------|
128+
| 0 | none | — | Verbatim; the absence of compression. Never advertised. |
129+
| 1 | deflate | mandatory | Raw DEFLATE {{RFC1951}}, with no zlib or gzip framing. |
130+
| 2 | zstd | optional | Zstandard {{RFC8878}}. |
116131

117-
For `deflate`, each object payload is an independent raw DEFLATE stream.
118-
There is no shared dictionary or state between objects, so each object decompresses on its own.
132+
Every endpoint that advertises this extension MUST implement `deflate`, so a common algorithm always exists; `zstd` is optional.
133+
Further algorithms MAY be registered (see [IANA Considerations](#iana-considerations)).
119134

120135

121136
# Relay Behavior
122-
A relay forwards the COMPRESSION track property unchanged — it is the publisher's end-to-end signal — and applies compression independently on each hop.
137+
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.
123138

124-
On its upstream subscription, the relay receives payloads compressed if and only if that hop compressed them (the extension negotiated and the relay advertised the algorithm); it decompresses them as needed.
125-
On each downstream subscription the relay serves, it compresses payloads with the track's algorithm when that downstream negotiated the extension and advertised the algorithm, and sends them verbatim otherwise.
126-
127-
Compression is thus driven by the publisher's track property, not by the relay: a relay does not compress a track the publisher did not mark.
139+
On its upstream subscription the relay receives each subgroup compressed with that hop's selected algorithm (or verbatim) and decompresses as needed.
140+
On each downstream subscription it (re)compresses each subgroup with that downstream's selected algorithm, or sends it verbatim when no algorithm is shared.
141+
A relay MAY transcode between algorithms.
128142
In every case the decompressed bytes delivered to the application MUST be identical to what the origin published.
129-
130143
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.
131144

132145

133146
# Security Considerations
134-
Compressing data that mixes attacker-controlled and secret content in the same object can leak the secret through compressed size, as in the CRIME and BREACH attacks.
135-
A publisher MUST NOT set COMPRESSION on a track whose object payloads combine secret material with attacker-influenced material.
136-
Because compression here is per-object with no cross-object dictionary, the exposure is bounded to within a single object, but it is not eliminated.
147+
Compressing data that mixes attacker-controlled and secret content can leak the secret through compressed size, as in the CRIME and BREACH attacks.
148+
A publisher MUST NOT set a non-zero COMPRESSION hint on a track whose object payloads combine secret material with attacker-influenced material.
149+
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.
137150

138151
A malicious sender could emit a small compressed payload that decompresses to a very large buffer (a "decompression bomb").
139-
A receiver MUST bound the size of a decompressed object payload. If the bound is exceeded it MUST reset the affected Subscribe/Fetch 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 object does not tear down unrelated subscriptions.
152+
A receiver MUST bound the size of a decompressed object payload. 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.
140153

141-
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`).
154+
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`).
142155

143156

144157
# IANA Considerations
145158

146159
This document requests the following registrations.
147160
High, distinctive values are requested to avoid the low ranges reserved by {{moqt}} and to minimize collisions with provisional registrations by other extensions; they also avoid the greasing pattern (`0x7f * N + 0x9D`).
148-
The parameter Type is even so that its value is a bare varint with no length prefix (see {{moqt}} Section 2.5).
161+
Each Type is even so that its value is a bare varint with no length prefix (see {{moqt}} Section 2.5).
149162

150163
## MOQT Setup Options
151164

@@ -172,6 +185,7 @@ The initial contents are:
172185
|---:|:--------|:--------------|
173186
| 0 | none | This Document |
174187
| 1 | deflate | This Document |
188+
| 2 | zstd | This Document |
175189

176190

177191
--- back

0 commit comments

Comments
 (0)