| Internet-Draft | moq-moqpack | March 2026 |
| Frindell | Expires 3 September 2026 | [Page] |
This document defines an extension to Media over QUIC Transport (MOQT) that enables QPACK compression for control messages. By leveraging QPACK's dynamic table, this extension significantly reduces the overhead of repeated values such as track names and authorization tokens, improving efficiency for sessions with many subscriptions or frequent redundant values.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://afrind.github.io/draft-frindell-moq-moqpack/draft-frindell-moq-moqpack.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-frindell-moq-moqpack/.¶
Discussion of this document takes place on the Media Over QUIC Working Group mailing list (mailto:moq@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/moq/. Subscribe at https://www.ietf.org/mailman/listinfo/moq/.¶
Source for this draft and an issue tracker can be found at https://github.com/afrind/draft-frindell-moq-moqpack.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 3 September 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Media over QUIC Transport (MOQT) [MOQT] control message fields and parameters can contain large values that are repeated across many messages within a session. The base MOQT specification transmits this information in full each time it appears, which can result in significant overhead.¶
This document defines an extension that uses QPACK [QPACK] to compress MOQT message parameters. QPACK provides:¶
Dynamic table for referencing previously transmitted values¶
Static table with pre-defined common values¶
Stream blocking semantics suitable for QUIC¶
By treating MOQT parameters as QPACK field lines, this extension enables efficient compression of repeated values while maintaining compatibility with QPACK's existing infrastructure.¶
Consider a session where a client sends 100 SUBSCRIBE messages, each carrying the same 500-byte authorization token. Without compression, this results in 50,000 bytes of token data. With QPACK compression, the token is transmitted once and subsequent references require only a few bytes, reducing total overhead to approximately 600 bytes.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The terms "endpoint", "session", "publisher", and "subscriber" are defined in [MOQT].¶
This extension is negotiated during MOQT session establishment using Setup Options. CLIENT_SETUP and SERVER_SETUP messages always use standard MOQT encoding and are never MOQPACK compressed.¶
The MOQT_QPACK_MAX_TABLE_CAPACITY setup option (Option Type 0x10) specifies the maximum size in bytes of the QPACK dynamic table the endpoint is willing to maintain for decoding. This corresponds to SETTINGS_QPACK_MAX_TABLE_CAPACITY in HTTP/3.¶
The value is encoded as a variable-length integer. The default value is 0.¶
QPACK compression is enabled when both endpoints send this parameter with a value greater than 0. If either endpoint omits this parameter or sends a value of 0, QPACK compression MUST NOT be used and control messages use the standard MOQT format.¶
The MOQT_QPACK_BLOCKED_STREAMS setup option (Option Type 0x11) specifies the maximum number of streams that can be blocked waiting for dynamic table updates. This corresponds to SETTINGS_QPACK_BLOCKED_STREAMS in HTTP/3.¶
The value is encoded as a variable-length integer. The default value is 0, which prevents any stream from being blocked. When set to 0, encoders MUST NOT reference dynamic table entries that have not been acknowledged.¶
This option is only meaningful when QPACK compression is enabled.¶
The MOQT_QPACK_INDEX_SETUP_AUTH setup option (Option Type 0x12) controls whether AUTHORIZATION TOKEN options from the Setup messages are implicitly inserted into the dynamic table.¶
The value is encoded as a variable-length integer:¶
When either endpoint omits this option or sends 0, implicit insertion does not occur and endpoints that wish to reference auth tokens explicitly insert them via the encoder stream.¶
This allows endpoints to authenticate the connection via setup auth tokens while still using Never-Indexed Literals for subsequent auth token references if desired.¶
When both endpoints send MOQT_QPACK_INDEX_SETUP_AUTH with value 1, any AUTHORIZATION TOKEN options from the Setup messages are implicitly inserted into the dynamic table without requiring encoder stream instructions.¶
Tokens from the Setup message, in the order they appeared, are inserted into the receiver's decoder dynamic table (indices 0, 1, 2, ...)¶
This allows the client to immediately reference its setup auth token in the first SUBSCRIBE message using a dynamic table reference, without resending it on the encoder stream.¶
The implicit entries count against the MOQT_QPACK_MAX_TABLE_CAPACITY limit. If the implicit entries would exceed the peer's advertised capacity, the excess entries (in reverse order) are not inserted and cannot be referenced.¶
Encoder stream insertions after setup use indices starting after the implicit entries. For example, if CLIENT_SETUP contained 2 auth tokens, the first explicit insertion would be at index 2.¶
When QPACK compression is negotiated, each endpoint opens two unidirectional streams for QPACK signaling.¶
This extension defines two new MOQT unidirectional stream types:¶
Carries QPACK encoder instructions from the endpoint that opens the stream. The format and semantics are defined in Section 4.3.1 of [QPACK].¶
Carries QPACK decoder instructions from the endpoint that opens the stream. The format and semantics are defined in Section 4.4.1 of [QPACK].¶
Each endpoint MUST open exactly one QPACK encoder stream and one QPACK decoder stream for QPACK use. These streams MUST be opened before sending any message with a Compressed Block.¶
An endpoint MAY open these streams immediately after sending its Setup message if it included MOQT_QPACK_MAX_TABLE_CAPACITY with a non-zero value. If a receiving endpoint does not enable QPACK (omits the parameter or sends value 0), it MAY send STOP_SENDING on these streams; this is not an error and the streams SHOULD be reset.¶
Note that implicitly-inserted dynamic table entries from Setup auth tokens (see Section 3.3) do not require encoder stream instructions. A Compressed Block MAY reference these implicit entries even if no encoder stream instructions have been sent.¶
If an endpoint receives a Compressed Block that references a dynamic table entry beyond the implicit entries before receiving any encoder stream data, it MUST buffer the message until the required encoder instructions arrive or close the session with PROTOCOL_VIOLATION if buffering limits are exceeded.¶
QPACK encoder and decoder streams MUST remain open for the duration of the MOQT session. If either stream is closed after QPACK is negotiated, the endpoint MUST close the MOQT session with PROTOCOL_VIOLATION.¶
This extension uses a flag bit in the message type to indicate if QPACK is used. Bit 6 (0x40) of the message type indicates MOQPACK format:¶
For example: - SUBSCRIBE standard = 0x03 - SUBSCRIBE MOQPACK = 0x43¶
All MOQPACK message types listed in this document are reserved in the MOQT message type registry and MUST NOT be used for other purposes.¶
When MOQPACK is negotiated, endpoints MUST accept both standard and MOQPACK formats for all applicable messages. An endpoint MAY send either format, but SHOULD prefer MOQPACK format to benefit from compression.¶
When MOQPACK is NOT negotiated, endpoints MUST NOT send MOQPACK format messages and MUST close the session with PROTOCOL_VIOLATION if they receive one.¶
In MOQPACK format, Track Namespace, Track Name, and Parameters are moved into a QPACK Compressed Block. Other message-specific fields including Properties remain unchanged.¶
When the Compressed Block is the last field in the message, it extends to the end of the message payload (as determined by the message Length field) and no explicit Compressed Block Length is needed. When additional fields follow the Compressed Block (such as Properties), an explicit Compressed Block Length field is present to delimit the block.¶
The following pseudo-parameter types are reserved for encoding namespace and track name fields in the Compressed Block:¶
| Type | Name | Description |
|---|---|---|
| 0x0A | TRACK_NAMESPACE_ELEMENT | Single element of a namespace tuple |
| 0x0B | TRACK_NAMESPACE_SET | Full serialized namespace tuple |
| 0x0C | TRACK_NAME | Track Name |
These pseudo-types use the same encoding as regular parameters: Literal with Static Name Reference for new values, or Indexed with Dynamic Table for previously-inserted values.¶
The value of a TRACK_NAMESPACE_SET field uses the standard MOQT Track Namespace serialization as defined in [MOQT]:¶
TRACK_NAMESPACE_SET Value {
Number of Track Namespace Fields (vi64),
Track Namespace Field (..) ...
}
Track Namespace Field {
Track Namespace Field Length (vi64),
Track Namespace Field Value (..)
}
¶
Each Track Namespace Field Value MUST contain at least one byte, consistent with the requirement in [MOQT].¶
A Track Namespace, Track Namespace Prefix or Track Namespace Suffix is reconstructed by assembling consecutive TRACK_NAMESPACE_ELEMENT and TRACK_NAMESPACE_SET field lines, which MUST appear first in the Compressed Block before TRACK_NAME or any parameters.¶
TRACK_NAMESPACE_ELEMENT adds a single element to the namespace tuple. TRACK_NAMESPACE_SET appends all elements from a serialized tuple. These can be intermixed and appear in any combination. For example:¶
Namespace ("conference", "room1", "audio"):
Option A - All elements:
TRACK_NAMESPACE_ELEMENT: "conference"
TRACK_NAMESPACE_ELEMENT: "room1"
TRACK_NAMESPACE_ELEMENT: "audio"
Option B - Full set:
TRACK_NAMESPACE_SET: ("conference", "room1", "audio")
Option C - Mixed:
TRACK_NAMESPACE_ELEMENT: "conference"
TRACK_NAMESPACE_SET: ("room1", "audio")
¶
This allows encoders to maximize compression by inserting commonly-reused elements or partial tuples into the dynamic table.¶
The message type determines the semantics of the assembled namespace:¶
Namespace elements (TRACK_NAMESPACE_ELEMENT and TRACK_NAMESPACE_SET) MUST appear first in the Compressed Block, followed by TRACK_NAME (if present), followed by parameters in increasing order of parameter type.¶
Within the namespace elements section, entries appear in the order they contribute to the namespace tuple and are not required to be in increasing order of type. TRACK_NAMESPACE_ELEMENT (0x0A) and TRACK_NAMESPACE_SET (0x0B) MAY be intermixed.¶
Parameters (excluding pseudo-parameter types) MUST appear in increasing order of their parameter type. If parameters appear out of order, the receiver MUST close the session with PROTOCOL_VIOLATION.¶
When decoding a Compressed Block, the receiver MUST verify that all required fields are present:¶
SUBSCRIBE, PUBLISH, TRACK_STATUS, Standalone FETCH: At least one namespace element (TRACK_NAMESPACE_ELEMENT or TRACK_NAMESPACE_SET) and TRACK_NAME¶
SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE, NAMESPACE, NAMESPACE_DONE: At least one namespace element¶
Joining FETCH, parameter-only messages: No required pseudo-parameters¶
An empty namespace (zero elements) is valid only if explicitly allowed by the message semantics.¶
If a required field is missing, the receiver MUST close the session with PROTOCOL_VIOLATION.¶
SUBSCRIBE Message (MOQPACK) {
Type (vi64) = 0x43,
Length (16),
Request ID (vi64),
Track Alias (vi64),
Compressed Block (..)
}
¶
The Compressed Block contains namespace elements (TRACK_NAMESPACE_ELEMENT and/or TRACK_NAMESPACE_SET), TRACK_NAME (0x0C), and any parameters (AUTHORIZATION_TOKEN, SUBSCRIBER_PRIORITY, etc.).¶
PUBLISH Message (MOQPACK) {
Type (vi64) = 0x5D,
Length (16),
Request ID (vi64),
Track Alias (vi64),
Compressed Block Length (vi64),
Compressed Block (..),
Properties (..)
}
¶
The Compressed Block contains namespace elements, TRACK_NAME, and any parameters. Properties remain outside the compressed block and use standard MOQT encoding, including IMMUTABLE_EXTENSIONS which are not QPACK compressed.¶
Standalone Fetch (MOQPACK) {
Start Location (Location),
End Location (Location),
Compressed Block (..)
}
Joining Fetch (MOQPACK) {
Joining Request ID (vi64),
Join Type (vi64),
Joining Start (vi64),
Compressed Block (..)
}
FETCH Message (MOQPACK) {
Type (vi64) = 0x56,
Length (16),
Request ID (vi64),
Fetch Type (vi64),
[Standalone (Standalone Fetch QPACK),]
[Joining (Joining Fetch QPACK),]
}
¶
For Standalone Fetch, the Compressed Block contains namespace elements, TRACK_NAME, and parameters. For Joining Fetch, the Compressed Block contains only parameters (the track is inherited from the joined subscription).¶
SUBSCRIBE_NAMESPACE Message (MOQPACK) {
Type (vi64) = 0x51,
Length (16),
Request ID (vi64),
Subscribe Options (vi64),
Compressed Block (..)
}
¶
The Compressed Block contains namespace prefix elements and any parameters.¶
PUBLISH_NAMESPACE Message (MOQPACK) {
Type (vi64) = 0x46,
Length (16),
Request ID (vi64),
Compressed Block (..)
}
¶
The Compressed Block contains namespace elements and any parameters.¶
NAMESPACE Message (MOQPACK) {
Type (vi64) = 0x48,
Length (16),
Compressed Block (..)
}
¶
The Compressed Block contains namespace suffix elements. This message has no parameters.¶
NAMESPACE_DONE Message (MOQPACK) {
Type (vi64) = 0x4E,
Length (16),
Compressed Block (..)
}
¶
The Compressed Block contains namespace suffix elements. This message has no parameters.¶
TRACK_STATUS uses the same format as SUBSCRIBE but with type 0x4D. The Compressed Block contains namespace elements, TRACK_NAME, and any applicable parameters.¶
SUBSCRIBE_OK Message (MOQPACK) {
Type (vi64) = 0x44,
Length (16),
Request ID (vi64),
Compressed Block Length (vi64),
Compressed Block (..),
Properties (..)
}
¶
The Compressed Block contains only parameters. Properties remain outside the Compressed Block and use standard MOQT encoding. The Compressed Block Length is required because Properties consume the remainder of the message.¶
FETCH_OK Message (MOQPACK) {
Type (vi64) = 0x58,
Length (16),
Request ID (vi64),
Compressed Block Length (vi64),
Compressed Block (..),
Properties (..)
}
¶
The Compressed Block contains only parameters. Properties remain outside the Compressed Block and use standard MOQT encoding. The Compressed Block Length is required because Properties consume the remainder of the message.¶
The following messages have parameters but no namespace, track name, or trailing properties. When MOQPACK is negotiated, these messages MAY use MOQPACK format with the 0x40 flag bit set. The MOQPACK format replaces the standard Parameters field with a Compressed Block that consumes the remainder of the message:¶
| Standard Type | MOQPACK Type | Message |
|---|---|---|
| 0x02 | 0x42 | REQUEST_UPDATE |
| 0x05 | 0x45 | REQUEST_ERROR |
| 0x07 | 0x47 | REQUEST_OK |
| 0x1E | 0x5E | PUBLISH_OK |
Parameter-Only Message (MOQPACK) {
Type (vi64) = <standard type> | 0x40,
Length (16),
[Message-specific fields...],
Compressed Block (..)
}
¶
The Compressed Block contains only parameters (no pseudo-parameter types). Message-specific fields (Request ID, error codes, etc.) remain unchanged.¶
This extension uses QPACK's wire encoding formats exactly as specified in [QPACK]. The only difference is the interpretation of static table references: instead of indexing into the HTTP static table of string name-value pairs, the static table index IS the MOQT parameter type.¶
This allows existing QPACK encoder/decoder implementations to be reused with minimal modification.¶
Each Compressed Block begins with the standard QPACK encoded field section prefix as defined in Section 4.5.1 of [QPACK]:¶
Compressed Block {
Required Insert Count (8+),
Sign and Delta Base (8+),
Encoded Field Lines (..)
}
¶
Encoded as specified in Section 4.5.1.1 of [QPACK]. Indicates the minimum dynamic table state needed to decode this block. A value of 0 means the block has no dynamic table references.¶
Encoded as a sign bit and Delta Base as specified in Section 4.5.1.2 of [QPACK]. Used to resolve relative indices in field line representations.¶
Dynamic table references in field lines use relative indexing as specified in [QPACK] Section 3.2.5. A relative index of 0 refers to the entry with absolute index equal to Base - 1. Encoders and decoders MUST use relative indices, not absolute indices, in Compressed Blocks.¶
Post-Base indexing (Section 3.2.6 of [QPACK]) MAY be used for entries inserted after the Base. This enables single-pass encoding where the encoder inserts entries while encoding a field section and references them using Post-Base indices.¶
In standard QPACK, a static table index retrieves a predefined (name, value) pair. In this extension, the static table conceptually contains entries where the "name" is the parameter type integer and there is no predefined value.¶
The static table index equals the MOQT parameter type:¶
For example: * Static index 0x02 represents DELIVERY_TIMEOUT * Static index 0x03 represents AUTHORIZATION_TOKEN * Static index 0x0A represents TRACK_NAMESPACE_ELEMENT * Static index 0x0C represents TRACK_NAME * Static index 0x20 represents SUBSCRIBER_PRIORITY¶
This means any valid MOQT parameter type can be referenced by static index without requiring pre-registration in a table.¶
In QPACK field line encodings, the T bit selects between static table (T=1) and dynamic table (T=0). Since MOQT parameter types are integers that map directly to static table indices, the following encodings are used:¶
The Name Index is the MOQT parameter type. The Value field contains the parameter value. Use this to send a parameter value.¶
References the dynamic table using a relative index. The retrieved entry contains a complete MOQT parameter (type and value).¶
References a dynamic table entry inserted after the Base. Used for single-pass encoding. See [QPACK] Section 4.5.3.¶
The following QPACK field line encodings are prohibited:¶
PROHIBITED. The MOQT static table has no predefined values.¶
PROHIBITED. Parameter types are always known integers; there is no need to reference the dynamic table for a parameter type.¶
PROHIBITED. Parameter types are always known integers; there is no need to reference the dynamic table for a parameter type.¶
PROHIBITED. Parameter types are integers, never string literals.¶
PROHIBITED. The CPU cost outweighs the minimal space savings for typical MOQT values. Encoders MUST set the H bit to 0 for all string literals.¶
Receivers MUST treat prohibited encodings as a PROTOCOL_VIOLATION.¶
Dynamic table entries store complete MOQT parameters (type and value). The entry size calculation follows [QPACK] Section 3.2.1: the size of an entry is the sum of its name size, value size, and 32 bytes of overhead. This extension uses a fixed name size of 4 bytes for the parameter type regardless of its encoded length.¶
Sets the dynamic table capacity up to the peer's MOQT_QPACK_MAX_TABLE_CAPACITY. Encoders MAY reduce capacity dynamically. See [QPACK] Section 4.3.1.¶
The Name Index is the MOQT parameter type. Inserts a new dynamic table entry with that parameter type and the provided value.¶
Duplicates an existing dynamic table entry at a new index. Useful when an entry is near eviction but still frequently referenced. See [QPACK] Section 4.3.4.¶
PROHIBITED. Parameter types are always known integers.¶
PROHIBITED. Parameter types are integers, never strings.¶
Receivers MUST close the session with PROTOCOL_VIOLATION if a prohibited encoder instruction is received.¶
QPACK values contain the raw parameter value bytes without any MOQT length prefix. The QPACK value length field serves as the length for binary values.¶
The QPACK value contains the raw binary bytes. The QPACK value length replaces the MOQT Length field. No length prefix is included in the value.¶
The QPACK value contains the varint-encoded integer. Note that this encoding carries redundant length information: the QPACK value length specifies the byte count, while the varint encoding is self-delimiting. If the varint-encoded length does not match the QPACK value length, the receiver MUST close the session with PROTOCOL_VIOLATION.¶
For example, DELIVERY_TIMEOUT with value 200: ~~~ QPACK Value Length: 2 QPACK Value: 0xC8 0x01 (varint encoding of 200) ~~~¶
The receiver decodes the varint and verifies it consumed exactly 2 bytes.¶
When receiving a message with a Compressed Block, the receiver:¶
Parses fixed message fields (Request ID, Track Alias, etc.)¶
If a Compressed Block is present: a. Waits for any referenced dynamic table entries to become available, subject to MOQT_QPACK_BLOCKED_STREAMS limits b. Decodes the QPACK Compressed Block to recover namespace elements, track name, and parameters¶
Processes the fully decoded message¶
If QPACK decoding fails, the receiver MUST close the session with MOQPACK_DECOMPRESSION_FAILED.¶
The total size of the decompressed message fields (the sum of all parameter values, namespace elements, and track name, excluding interior length fields) MUST NOT exceed 65535 bytes. If a decompressed message exceeds this limit, the receiver MUST close the session with MOQPACK_DECOMPRESSION_FAILED.¶
Encoders SHOULD insert frequently-used parameter values into the dynamic table. Authorization tokens, Track Namespace Elements and Track names that will be reused across multiple messages are prime candidates for insertion.¶
Short values like brief track names ("audio", "video") MAY be sent as literals rather than inserted, since the overhead of insertion and indexed reference is similar to sending the literal value. Insertion is more beneficial for: - Long values (large auth tokens, long namespace elements) - Values that will be reused across multiple messages (namespace elements shared by many tracks, auth tokens used for many subscriptions)¶
Encoders MUST respect the peer's MOQT_QPACK_MAX_TABLE_CAPACITY and MOQT_QPACK_BLOCKED_STREAMS limits when making insertion and reference decisions.¶
Encoders SHOULD use the QPACK duplicate instruction when a dynamic table entry is at risk of eviction but is still frequently referenced.¶
Encoders track the Known Received Count as specified in [QPACK] Section 2.1.4 to determine which entries can be referenced without blocking. Encoders MUST only reference dynamic table entries with absolute index less than the Known Received Count when the number of streams that would be blocked by the reference equals MOQT_QPACK_BLOCKED_STREAMS.¶
The 'N' bit in Literal Field Line representations signals that a value MUST NOT be indexed by intermediaries. Encoders MAY set the 'N' bit for sensitive values when: - The value should not be cached by relays - The value has low entropy and is vulnerable to compression attacks¶
Note that when MOQT_QPACK_INDEX_SETUP_AUTH is enabled, setup auth tokens are implicitly inserted into the dynamic table (see Section 3.3). Endpoints that require 'N' bit semantics for auth tokens MUST NOT enable MOQT_QPACK_INDEX_SETUP_AUTH and SHOULD instead send tokens as Never-Indexed Literals in each message.¶
Intermediaries that re-encode MOQT messages MUST preserve the 'N' bit semantics: values encoded with N=1 MUST NOT be inserted into the dynamic table.¶
Writing large encoder instructions can cause deadlocks if the decoder withholds flow control credit until the instruction is complete. To avoid this, encoders SHOULD NOT write an encoder instruction unless sufficient stream and connection flow-control credit is available for the entire instruction. If sufficient credit is not available, encoders SHOULD use literal encodings instead. See [QPACK] Section 2.1.3.¶
Decoders MUST process encoder instructions from the QPACK encoder stream before processing any message that might reference those insertions.¶
Decoders MUST send Section Acknowledgment instructions on the QPACK decoder stream after successfully decoding a Compressed Block that references the dynamic table (Required Insert Count > 0). Compressed Blocks that contain only Literal Field Lines with Static Name References do not require acknowledgment.¶
QPACK decoder instructions that reference streams (Section Acknowledgment, Stream Cancellation) use Request IDs instead of QUIC Stream IDs. This allows MOQT to operate over WebTransport where QUIC Stream IDs are not exposed to the application.¶
Carries a Request ID. Acknowledges successful decoding of a Compressed Block that referenced the dynamic table. For streams with multiple such Compressed Blocks (e.g., SUBSCRIBE_NAMESPACE response stream with multiple NAMESPACE messages referencing the dynamic table), successive Section Acknowledgments for the same Request ID acknowledge successive Compressed Blocks in the order they were sent. Compressed Blocks with no dynamic table references are not counted.¶
Carries a Request ID. Indicates the stream was cancelled before the Compressed Block(s) could be decoded. The encoder MUST NOT count references from that stream when determining eviction eligibility.¶
Unchanged from [QPACK]. Carries an increment value, no Request ID.¶
The encoder tracks how many Compressed Blocks it has sent for each Request ID. When it receives a Section Acknowledgment for a Request ID, it knows which Compressed Block was acknowledged based on the count.¶
This document defines a new MOQT session error code:¶
A QPACK Compressed Block could not be decoded, or the decompressed message exceeded implementation limits. This is always a session error; the endpoint MUST close the MOQT session.¶
QPACK decoding errors (as defined in Section 6 of [QPACK]) result in session termination. The specific mapping is:¶
| QPACK Error | MOQT Session Error |
|---|---|
| QPACK_DECOMPRESSION_FAILED | MOQPACK_DECOMPRESSION_FAILED |
| QPACK_ENCODER_STREAM_ERROR | PROTOCOL_VIOLATION |
| QPACK_DECODER_STREAM_ERROR | PROTOCOL_VIOLATION |
The QPACK dynamic table maintains state across messages. An attacker with knowledge of dynamic table contents could potentially:¶
Determine which authorization tokens have been used¶
Infer subscription patterns from parameter compression ratios¶
Implementations SHOULD consider these privacy implications when deciding which values to insert into the dynamic table.¶
As with HTTP compression, implementers need to take care to avoid compression oracle attacks where an attacker can infer secret values by observing compressed message sizes. Applications SHOULD NOT mix attacker-controlled data with secret authorization tokens in the same field section.¶
Endpoints MUST enforce the negotiated table capacity limits to prevent resource exhaustion attacks. An endpoint that attempts to exceed these limits causes a session error.¶
This document registers the following Setup Option Types in the "MOQT Setup Options" registry:¶
| Parameter Type | Parameter Name | Specification |
|---|---|---|
| 0x10 | MOQT_QPACK_MAX_TABLE_CAPACITY | Section 3 |
| 0x11 | MOQT_QPACK_BLOCKED_STREAMS | Section 3 |
| 0x12 | MOQT_QPACK_INDEX_SETUP_AUTH | Section 3 |
This document registers the following unidirectional stream types in the "MOQT Stream Types" registry:¶
| Stream Type | Name | Specification |
|---|---|---|
| 0x1f107a60 | QPACK_ENCODER_STREAM | Section 4.1 |
| 0x1f107a61 | QPACK_DECODER_STREAM | Section 4.1 |
This document registers the following pseudo-parameter types in the "MOQT Message Parameters" registry. These types are reserved for use in QPACK Compressed Blocks and MUST NOT appear in standard Parameters fields.¶
| Parameter Type | Parameter Name | Specification |
|---|---|---|
| 0x0A | TRACK_NAMESPACE_ELEMENT | Section 5.2 |
| 0x0B | TRACK_NAMESPACE_SET | Section 5.2 |
| 0x0C | TRACK_NAME | Section 5.2 |
This document reserves the following message types in the "MOQT Message Types" registry for use as MOQPACK-format messages:¶
| Message Type | Name | Specification |
|---|---|---|
| 0x42 | MOQPACK REQUEST_UPDATE | Section 5.6 |
| 0x43 | MOQPACK SUBSCRIBE | Section 5.6 |
| 0x44 | MOQPACK SUBSCRIBE_OK | Section 5.6 |
| 0x45 | MOQPACK REQUEST_ERROR | Section 5.6 |
| 0x46 | MOQPACK PUBLISH_NAMESPACE | Section 5.6 |
| 0x47 | MOQPACK REQUEST_OK | Section 5.6 |
| 0x48 | MOQPACK NAMESPACE | Section 5.6 |
| 0x4D | MOQPACK TRACK_STATUS | Section 5.6 |
| 0x4E | MOQPACK NAMESPACE_DONE | Section 5.6 |
| 0x51 | MOQPACK SUBSCRIBE_NAMESPACE | Section 5.6 |
| 0x56 | MOQPACK FETCH | Section 5.6 |
| 0x58 | MOQPACK FETCH_OK | Section 5.6 |
| 0x5D | MOQPACK PUBLISH | Section 5.6 |
| 0x5E | MOQPACK PUBLISH_OK | Section 5.6 |
This document registers the following session error code in the "MOQT Session Error Codes" registry:¶
| Error Code | Name | Specification |
|---|---|---|
| 0xTBD | MOQPACK_DECOMPRESSION_FAILED | Section 8.1 |
The QPACK specification [QPACK] provides the foundation for this work. The design of HTTP/3 header compression informed many decisions in this document.¶
Claude, by Anthropic, assisted in drafting this document.¶
This appendix provides an example of QPACK-compressed MOQT messages, demonstrating the Required Insert Count, Base, and relative indexing.¶
A client sends three SUBSCRIBE messages to the same track with the same authorization token:¶
Track Namespace: ("conference", "room42")¶
Track Name: "audio"¶
Token Type: 1 (e.g., JWT)¶
Token Value: "eyJhbGciOiJIUzI1NiIs..." (500 bytes)¶
The auth token was sent in CLIENT_SETUP and is implicitly inserted at dynamic table absolute index 0 after QPACK negotiation succeeds.¶
The encoder inserts namespace elements on the encoder stream. The short track name "audio" is sent as a literal rather than inserted:¶
Encoder Stream:
Insert With Static Name Reference
Name Index: 0x0A (TRACK_NAMESPACE_ELEMENT)
Value: "conference"
Insert With Static Name Reference
Name Index: 0x0A (TRACK_NAMESPACE_ELEMENT)
Value: "room42"
¶
Dynamic table state after insertions:¶
| Absolute Index | Parameter Type | Value |
|---|---|---|
| 0 | AUTH_TOKEN (implicit) | Token Type 1, "eyJ..." |
| 1 | TRACK_NAMESPACE_ELEMENT | "conference" |
| 2 | TRACK_NAMESPACE_ELEMENT | "room42" |
The encoder sends the SUBSCRIBE with Required Insert Count = 3 and Base = 3:¶
SUBSCRIBE Message:
Type: 0x43
Request ID: 1
Track Alias: 100
Compressed Block:
Required Insert Count: 3 (encoded per RFC 9204 Section 4.5.1.1)
Base: Sign=0, Delta=0 (Base = Required Insert Count = 3)
Indexed Field Line (Dynamic, relative index 1) // abs 1 = "conference"
Indexed Field Line (Dynamic, relative index 0) // abs 2 = "room42"
Literal Field Line (Static Name 0x0C, Value "audio") // TRACK_NAME
Indexed Field Line (Dynamic, relative index 2) // abs 0 = AUTH_TOKEN
¶
Relative index calculation: relative = Base - 1 - absolute¶
"conference": relative = 3 - 1 - 1 = 1¶
"room42": relative = 3 - 1 - 2 = 0¶
AUTH_TOKEN: relative = 3 - 1 - 0 = 2¶
Compressed Block: ~12 bytes (including prefix and literal track name) Uncompressed equivalent: ~526 bytes¶
No encoder stream instructions needed; namespace elements are in the table, track name is sent as literal again:¶
SUBSCRIBE Message:
Type: 0x43
Request ID: 2
Track Alias: 101
Compressed Block:
Required Insert Count: 3
Base: Sign=0, Delta=0
Indexed Field Line (Dynamic, relative index 1) // "conference"
Indexed Field Line (Dynamic, relative index 0) // "room42"
Literal Field Line (Static Name 0x0C, Value "audio") // TRACK_NAME
Indexed Field Line (Dynamic, relative index 2) // AUTH_TOKEN
¶
Each subsequent SUBSCRIBE to the same track: ~12 bytes instead of ~526 bytes.¶
The track name "video" is also short, so we send it as a literal. No encoder stream instructions needed:¶
SUBSCRIBE Message:
Type: 0x43
Request ID: 3
Track Alias: 102
Compressed Block:
Required Insert Count: 3
Base: Sign=0, Delta=0
Indexed Field Line (Dynamic, relative index 1) // "conference"
Indexed Field Line (Dynamic, relative index 0) // "room42"
Literal Field Line (Static Name 0x0C, Value "video") // TRACK_NAME
Indexed Field Line (Dynamic, relative index 2) // AUTH_TOKEN
¶
The namespace elements are reused; the different track name is sent as a literal.¶
This appendix summarizes all code points defined or used by this extension.¶
| Type | Name |
|---|---|
| 0x10 | MOQT_QPACK_MAX_TABLE_CAPACITY |
| 0x11 | MOQT_QPACK_BLOCKED_STREAMS |
| 0x12 | MOQT_QPACK_INDEX_SETUP_AUTH |
| Type | Name |
|---|---|
| 0x1f107a60 | QPACK_ENCODER_STREAM |
| 0x1f107a61 | QPACK_DECODER_STREAM |
| Type | Name |
|---|---|
| 0x0A | TRACK_NAMESPACE_ELEMENT |
| 0x0B | TRACK_NAMESPACE_SET |
| 0x0C | TRACK_NAME |
The MOQPACK flag bit (0x40) is OR'd with standard MOQT message types:¶
| Standard | MOQPACK | Message |
|---|---|---|
| 0x02 | 0x42 | REQUEST_UPDATE |
| 0x03 | 0x43 | SUBSCRIBE |
| 0x04 | 0x44 | SUBSCRIBE_OK |
| 0x05 | 0x45 | REQUEST_ERROR |
| 0x06 | 0x46 | PUBLISH_NAMESPACE |
| 0x07 | 0x47 | REQUEST_OK |
| 0x08 | 0x48 | NAMESPACE |
| 0x0D | 0x4D | TRACK_STATUS |
| 0x0E | 0x4E | NAMESPACE_DONE |
| 0x11 | 0x51 | SUBSCRIBE_NAMESPACE |
| 0x16 | 0x56 | FETCH |
| 0x18 | 0x58 | FETCH_OK |
| 0x1D | 0x5D | PUBLISH |
| 0x1E | 0x5E | PUBLISH_OK |
This appendix summarizes the modifications needed to use a standard QPACK library (designed for HTTP/3) with this extension.¶
Standard QPACK libraries include a static table of 99 predefined HTTP header (name, value) pairs. For MOQPACK, the static table is reinterpreted: the static table index directly represents the MOQT parameter type integer, and there are no predefined values. Implementations need to replace or bypass the HTTP static table. A simple approach is to treat the static table as a mapping from index to a 4-byte big-endian representation of the parameter type, with no predefined value.¶
Standard QPACK supports all field line representations. MOQPACK prohibits several (see Section 6.3.1). Implementations should configure the encoder to only emit:¶
Literal Field Line With Static Name Reference (for new parameter values)¶
Indexed Field Line with Dynamic Table (for previously-inserted values)¶
Indexed Field Line with Post-Base Index¶
And should configure the decoder to reject:¶
Only two insertion forms are used:¶
Insert With Dynamic Name Reference and Insert With Literal Name are not used.¶
Standard QPACK decoder instructions (Section Acknowledgment, Stream Cancellation) carry QUIC stream IDs. In MOQPACK, these carry MOQT Request IDs instead (see Section 7.2.1). The encoder needs to track Compressed Blocks per Request ID rather than per stream ID. This also enables MOQPACK to operate over WebTransport where QUIC stream IDs are not exposed to the application.¶
The name size for all entries is fixed at 4 bytes (rather than the variable-length header name strings used in HTTP). The 32-byte per-entry overhead from [QPACK] Section 3.2.1 still applies.¶