Skip to content

Commit 88808c6

Browse files
kixelatedclaude
andauthored
Add moqt extension drafts: relay hops, object timestamps, payload compression (#30)
* Add moq-transport extension drafts for relay hops, timestamps, compression moq-lite has grown features with no equivalent in draft-ietf-moq-transport-18. Extract them as standalone moqt extension drafts (modeled on moq-probe) so they can be adopted upstream independently: - relay-hops: Hop ID path list (loop detection + shortest-path tiebreak) and Exclude Hop, via PUBLISH_NAMESPACE / SUBSCRIBE_NAMESPACE parameters. - timestamp: track Timescale + per-object Timestamp/Duration as Key-Value-Pair properties, enabling consistent age-based dropping across relays. - compression: per-object DEFLATE negotiated via Setup Option + track property, with a new algorithm registry and relay transcoding rules. Negotiation uses moqt Setup Options; data rides on the native Key-Value-Pair mechanism. Codepoints are distinctive provisional values to be finalized. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * CLAUDE.md: document that `make` self-bootstraps its toolchain `make` clones the i-d-template submodule and installs xml2rfc (pip venv) and kramdown-rfc (bundler) on first run, so the drafts build without manually installing the toolchain. Note that there is no separate format step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review: hops bridging, empty EXCLUDE_HOP, IANA registry names - relay-hops: bridging from a non-supporting peer now SHOULD synthesize a leading 0 hop (was MAY/omit) so path lengths stay comparable. - relay-hops: an empty EXCLUDE_HOP (Length 0) is now a permitted no-op instead of a PROTOCOL_VIOLATION, matching the empty-HOP_PATH behavior. - IANA: register in the correct moqt-18 registries — message parameters under "MOQT Message Parameters" (Section 15.7) for HOP_PATH/EXCLUDE_HOP, and object/ track properties under "MOQT Properties" (Section 15.8) for the timestamp and compression drafts; align Setup Options naming to "MOQT Setup Options". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review feedback on hops, timestamp, compression relay-hops: - Hop IDs are now random integers; drop the reserved 0 "unknown hop" (a moq-lite-04 legacy). Bridging a non-supporting upstream synthesizes a random stand-in ID so loop detection still works. - HOP_PATH always has >=1 entry; the first is the origin publisher's own ID, enabling broadcast-identity (same first ID = same broadcast). - Relay MUST include HOP_PATH and MUST honor EXCLUDE_HOP (was SHOULD). - EXCLUDE_HOP is now a single Hop ID (even Type, bare varint), matching moq-lite; removes the empty-list and large-list edge cases. - Reorder intro (path selection first), add broadcast-identity use. - Security: topology hiding is now advisory (MAY coalesce/strip, like BGP confederations) not MUST; clarify a relay can only append hops, not shorten a path, so forgery impact is limited to advisory tie-breaks. Drop EXCLUDE_HOP DoS note (single value now). timestamp: - Note the demuxed nature of MoQ makes timestamps near-universal per track. - Note Timescale is required to interpret units, so timing can't be resolved until track properties arrive (SUBSCRIBE_OK / TRACK_STATUS). - Duration MAY now refine age-based dropping (object end = timestamp+duration). compression: - Reframe as hop-by-hop wire optimization (HTTP Transfer-Encoding analogy), not an end-to-end publisher track property; the origin need not opt in. - COMPRESSION moves from a track property to a per-subscription property in SUBSCRIBE_OK / FETCH_OK; register under MOQT Message Parameters (15.7). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * compression: revert COMPRESSION to a publisher track property Per review: COMPRESSION is the original publisher's end-to-end signal that a track's payloads are worth compressing (and which algorithm), not a per-hop choice. Actual on-wire compression stays hop-by-hop and is gated by negotiation: a payload is compressed only on a hop that negotiated the extension and whose receiver advertised the algorithm; otherwise it travels verbatim. The property is forwarded unchanged by relays, making the on-wire state unambiguous without per-object signaling. Re-register under MOQT Properties (Section 15.8, Track scope). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * compression: clarify decompression-bomb reset scope; tighten pronoun Per review: the decompression-bomb guard now explicitly resets only the affected Subscribe/Fetch stream (and MAY close the session with PROTOCOL_VIOLATION only if the peer is abusive), so one bad object doesn't tear down unrelated subscriptions. Also clarify the relay-behavior pronoun. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * compression: clarify the track property is the sole enabler - A relay MUST forward the COMPRESSION track property unchanged even on hops that did not negotiate the extension, so a further-downstream hop that does negotiate it can still act on the publisher's signal. - State explicitly that compression is enabled only by the track property plus a negotiated hop: a publisher MUST NOT compress without the property, and there is no per-object enabling. (The .json relay heuristic was already removed in 6f59a40.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bacaaa2 commit 88808c6

4 files changed

Lines changed: 561 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,24 @@ This is an IETF Internet-Draft repository for Media over QUIC (MOQ) protocol spe
1212

1313
## Build Commands
1414

15+
Run `make` to build/validate drafts. **`make` self-bootstraps its toolchain**
16+
on first run it clones the i-d-template submodule into `lib/`, creates a Python
17+
venv (`lib/.venv`, for xml2rfc), and bundler-installs kramdown-rfc into
18+
`lib/.gems`. No manual install of kramdown-rfc/xml2rfc is needed; you only need
19+
system `python3` (with venv+pip), `ruby` (with `bundle`), a C compiler, and
20+
network access on the first build. Do not claim the drafts can't be built — run
21+
`make`. (A successful kramdown-rfc step also validates a draft's syntax.)
22+
23+
There is no separate format step: prettier is disabled (`.prettierignore` is
24+
`**`), so editing the markdown is the whole workflow.
25+
1526
```bash
1627
# Build all drafts (generates HTML and text versions)
1728
make
1829

30+
# Build/validate a single draft (fast feedback while editing)
31+
make draft-lcurley-moq-lite.txt
32+
1933
# Clean build artifacts
2034
make clean
2135

draft-lcurley-moq-compression.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
title: "MoQ Payload Compression Extension"
3+
abbrev: "moq-compression"
4+
category: info
5+
6+
docname: draft-lcurley-moq-compression-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+
RFC1951:
22+
23+
informative:
24+
25+
--- abstract
26+
27+
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.
31+
32+
--- middle
33+
34+
# Conventions and Definitions
35+
{::boilerplate bcp14-tagged}
36+
37+
38+
# Introduction
39+
{{moqt}} makes the original publisher "solely responsible for the content of the object payload ... including the underlying encoding, compression, any end-to-end encryption, or authentication" ({{moqt}} Section 2.1).
40+
For media this is the right layering: already-compressed codecs (H.264, Opus, AV1) gain nothing from a second compression pass.
41+
42+
But MoQ also carries non-media tracks — JSON, text, telemetry, captions, uncompressed binary structures — where the payloads are highly compressible and where end-to-end encryption is often not in use.
43+
For these tracks there is no standard, transport-visible way to compress payloads, so each application reinvents it, and relays cannot help.
44+
45+
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.
47+
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+
51+
52+
# Setup Negotiation
53+
The Payload Compression extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3.
54+
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.
55+
56+
Each endpoint advertises the algorithms it can decompress by including the following Setup Option:
57+
58+
~~~
59+
COMPRESSION Setup Option {
60+
Option Key (vi64) = 0xC03DE
61+
Option Value Length (vi64)
62+
Algorithm (vi64) ...
63+
}
64+
~~~
65+
66+
**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+
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.
72+
73+
74+
# 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.
76+
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.
77+
Because the value is a single integer, COMPRESSION uses an even Type so the value is a bare varint:
78+
79+
~~~
80+
COMPRESSION Track Property {
81+
Type (vi64) = 0xC03D0
82+
Value (vi64) ; Algorithm identifier
83+
}
84+
~~~
85+
86+
**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.
89+
90+
The property is fixed for the lifetime of the track and MUST NOT change.
91+
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.
92+
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.
95+
96+
Whether payloads are actually compressed is decided per hop:
97+
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.
100+
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.
103+
104+
A publisher SHOULD set COMPRESSION only for payload types that benefit from it.
105+
Already-compressed media SHOULD omit it (or use `none`).
106+
107+
108+
# Compression Algorithms {#compression-algorithms}
109+
This document defines the following algorithms.
110+
Further algorithms MAY be registered (see [IANA Considerations](#iana-considerations)).
111+
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. |
116+
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.
119+
120+
121+
# 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.
123+
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.
128+
In every case the decompressed bytes delivered to the application MUST be identical to what the origin published.
129+
130+
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.
131+
132+
133+
# 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.
137+
138+
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.
140+
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`).
142+
143+
144+
# IANA Considerations
145+
146+
This document requests the following registrations.
147+
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).
149+
150+
## MOQT Setup Options
151+
152+
This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required.
153+
154+
| Value | Name | Reference |
155+
|:--------|:------------|:--------------|
156+
| 0xC03DE | COMPRESSION | This Document |
157+
158+
## MOQT Properties
159+
160+
This document requests a registration in the "MOQT Properties" registry ({{moqt}} Section 15.8), used for object and track properties.
161+
162+
| Value | Name | Scope | Reference |
163+
|:--------|:------------|:------|:--------------|
164+
| 0xC03D0 | COMPRESSION | Track | This Document |
165+
166+
## MOQT Compression Algorithms
167+
168+
This document requests a new "MOQT Compression Algorithms" registry, with a registration policy of Specification Required.
169+
The initial contents are:
170+
171+
| ID | Name | Reference |
172+
|---:|:--------|:--------------|
173+
| 0 | none | This Document |
174+
| 1 | deflate | This Document |
175+
176+
177+
--- back
178+
179+
# Acknowledgments
180+
{:numbered="false"}
181+
182+
This document was drafted with the assistance of Claude, an AI assistant by Anthropic.

0 commit comments

Comments
 (0)