The data hierarchy in moq-transport, with a focus on how the on-wire encoding evolved across draft-14 → draft-16 → draft-17.

Hierarchy

Track
  └── Group (identified by Group ID)
        └── Subgroup (identified by Subgroup ID)
              └── Object (identified by Object ID)

Track

A named stream of related data. Identified by (Track Namespace, Track Name). Has associated track-properties.

  • draft-14: Track Namespace is “an ordered N-tuple of bytes where N can be between 1 and 32” — no on-wire structure given.
  • draft-16: Adds an explicit Track Namespace { Number of Fields, Field... } and Track Namespace Field { Length, Value } wire structure. Each field value MUST be at least 1 byte. Range still 1–32.
  • draft-17: Lower bound relaxed to 0–32 fields. New §1.5.1 “Parsing Serialized Names” defines bijective canonicalisation rules (lowercase hex, no redundant percent-encoding).

Group

A collection of related objects, typically a temporal unit (e.g., a GoP). Group IDs are monotonically increasing within a Track. The conceptual definition is identical across 14/16/17.

  • 14 had a one-line note “the increase in time between two groups is not defined by the protocol”; 16/17 expanded this to a fuller statement that ordering and inter-Group spacing is application-defined.

Subgroup

A subdivision within a Group. Subgroups map 1:1 to QUIC streams, enabling independent delivery and prioritisation. The conceptual definition is stable across 14/16/17.

  • 14 said forwarding preference was a Track-level setting; 16/17 made it a per-Object property. 16 adds the rule “Every Object within a Group belongs to exactly one Subgroup or Datagram”, and tightens “Objects from two subgroups MUST NOT be sent on the same stream” (was a soft “cannot” in 14).

Object

The atomic unit of data, uniquely identified by (Track Namespace, Track Name, Group ID, Object ID). The four-line core definition is identical across 14/16/17.

  • draft-16 introduced an explicit “three-state” existence model: an Object is known to exist, known not to exist (permanent), or unknown. Non-existent ranges are now expressed via Properties + the FETCH End-of-Range markers (see below) rather than a dedicated Object Status code.
  • draft-16 removed the ObjectDoesNotExist (0x1) Object Status value. Remaining statuses: Normal (0x0), EndOfGroup (0x3), EndOfTrack (0x4). 17 unchanged.

Wire Format — version-by-version

The biggest churn in the data plane happened at draft-15/16. draft-17 is mostly a rename: Extension HeadersProperties everywhere, plus a self-contained varint definition ((i) annotation becomes (vi64)).

Subgroup-stream Object encoding

The first byte(s) on a unidirectional Subgroup stream is a SUBGROUP_HEADER, then a sequence of per-Object records.

draft-14 (enumerated Type)

SUBGROUP_HEADER {
  Type (i) = 0x10..0x1D,
  Track Alias (i),
  Group ID (i),
  [Subgroup ID (i),]
  Publisher Priority (8),
}

Object {
  Object ID Delta (i),
  [Extension Headers Length (i), Extension Headers (...)],
  Object Payload Length (i),
  [Object Status (i)],
  Object Payload (..),
}
  • Type space is a flat lookup of 12 enumerated values across three orthogonal axes (Subgroup ID Field Present / Subgroup ID Value 0 vs first Object ID / Extensions Present / Contains End-of-Group).
  • Publisher Priority is always present.
  • Object ID Delta + 1 is added to the previous Object ID; first Object uses Delta as absolute.
  • Object Status only present when payload length is zero.

draft-16 (bit-flag Type)

SUBGROUP_HEADER {
  Type (i) = 0x10..0x15 / 0x18..0x1D / 0x30..0x35 / 0x38..0x3D,
  Track Alias (i),
  Group ID (i),
  [Subgroup ID (i),]
  [Publisher Priority (8),]
}

Object {
  Object ID Delta (i),
  [Extensions (..),]
  Object Payload Length (i),
  [Object Status (i),]
  [Object Payload (..),]
}

Type bits (0b00X1XXXX):

BitNameMeaning
0x01EXTENSIONSExtensions present in all Objects in this Subgroup
0x06SUBGROUP_ID_MODE (2 bits)0b00=zero, 0b01=first Object ID, 0b10=Subgroup ID field present, 0b11=reserved (PROTOCOL_VIOLATION)
0x08END_OF_GROUPSubgroup contains the last Object of the Group
0x20DEFAULT_PRIORITYNEW. Publisher Priority field omitted; inherit from subscription

Extensions is now a named struct: { Extension Headers Length (i), Extension Headers (..) }.

draft-17 (rename + self-contained varint)

Bytes-on-the-wire are identical to draft-16. Only differences:

  • ExtensionsProperties, EXTENSIONS bit (0x01) → PROPERTIES bit. Field becomes Object Properties { Properties Length (vi64), Properties (..) }.
  • All field annotations switch from (i) (RFC 9000 varint reference) to (vi64) (MoQ’s own self-contained varint, see moq-transport §1.4.1). Encoded byte ranges differ (no 7-byte length, the 11111100 prefix is invalid).

Datagram Object encoding

draft-14

OBJECT_DATAGRAM {
  Type (i) = 0x00..0x07, 0x20..0x21,
  Track Alias (i), Group ID (i),
  [Object ID (i),]
  Publisher Priority (8),
  [Extension Headers Length (i), Extension Headers (...)],
  [Object Status (i),]
  [Object Payload (..),]
}

10 enumerated Type values cover (End-of-Group / Extensions / Object-ID-present / payload-vs-status). Publisher Priority always present.

draft-16

Same field set; Type becomes a bit-flag (0b00X0XXXX) over a wider range (0x00..0x0F, 0x20..0x21, 0x24..0x25, 0x28..0x29, 0x2C..0x2D):

BitNameEffect
0x01EXTENSIONSExtensions block present
0x02END_OF_GROUPLast Object of the Group
0x04ZERO_OBJECT_IDObject ID field omitted; Object ID = 0
0x08DEFAULT_PRIORITYNEW. Publisher Priority omitted; inherit from subscription
0x20STATUSCarries Object Status instead of payload

STATUS + END_OF_GROUP together is forbidden.

draft-17

Bit layout unchanged. Renames: EXTENSIONS bit → PROPERTIES bit, Extensions (..)Properties (..). New normative rule: STATUS bit + PROPERTIES bit set together with non-Normal status → PROTOCOL_VIOLATION (only Normal Objects may have Properties).

FETCH-stream Object encoding

This is where PR #1586 lives — the FETCH per-Object header was completely redesigned at draft-16.

draft-14 — fixed layout, every field absolute

FETCH_HEADER { Type (i) = 0x5, Request ID (i) }

Object {
  Group ID (i),
  Subgroup ID (i),
  Object ID (i),
  Publisher Priority (8),
  Extension Headers Length (i),
  [Extension Headers (...)],
  Object Payload Length (i),
  [Object Status (i)],
  Object Payload (..),
}

No delta encoding. Object Status carries non-existence in-line. Datagram-preference Objects encoded as Subgroup ID = Object ID.

draft-16 — flag-gated, delta-encoded

Object {
  Serialization Flags (i),
  [Group ID (i),]
  [Subgroup ID (i),]
  [Object ID (i),]
  [Publisher Priority (8),]
  [Extensions (..),]
  Object Payload Length (i),
  [Object Payload (..),]
}

A single varint Serialization Flags controls every field:

Subgroup ID mode (lower 2 bits, mask 0x03):

BitsMeaning
0x00Subgroup ID = 0
0x01Subgroup ID = prior Object’s Subgroup ID
0x02Subgroup ID = prior Object’s Subgroup ID + 1
0x03Subgroup ID field is present

Other bits:

BitSet (1)Cleared (0)
0x04Object ID field presentObject ID = prior Object ID + 1
0x08Group ID field presentGroup ID = prior Object’s Group ID
0x10Priority field presentPriority = prior Object’s Priority
0x20Extensions presentExtensions absent
0x40Datagram-preference Object (ignore Subgroup ID mode bits)Use Subgroup ID mode

Reserved Serialization Flags values for non-existence ranges:

  • 0x8C — End of Non-Existent Range
  • 0x10C — End of Unknown Range

For these markers, only Group ID and Object ID are present; they describe the closing endpoint of a contiguous range.

If the first Object in the FETCH response uses a “prior Object” reference, the receiver MUST treat it as a PROTOCOL_VIOLATION.

Object Status was removed from FETCH responses in draft-16 — non-existence is signalled exclusively via the End-of-Range flag values.

draft-17 — clarification of “prior Object” across End-of-Range markers

Same wire-byte layout as draft-16. Two clarifications:

  • Bit 0x40 wording rephrased (“Decode the Subgroup ID as indicated by the two least significant bits” when cleared).
  • New explicit semantics for “prior” fields after an End-of-Range marker (transport-17.txt §10.4 ff.):
    • Prior Group ID / Object ID = the values in the End-of-Range indicator.
    • Prior Subgroup ID = the Subgroup ID from the last actual Object before the marker. Referencing prior Subgroup ID with no prior Object is a PROTOCOL_VIOLATION.
  • Plus the global ExtensionsProperties rename and (i)(vi64) annotation.

Delta Encoding Summary

FieldSubgroup streamDatagramFETCH stream (16+)FETCH (14)
Group IDheader field; absoluteabsolutedelta from prior, gated by flag 0x08absolute
Subgroup IDheader field; mode-encoded (16+)n/a (Object ID may be elided to 0)4-mode encoding (zero / prior / prior+1 / present)absolute
Object IDObject ID Delta + 1 from prior[Object ID] or 0 (16+)gated by flag 0x04: present, or prior+1absolute
Publisher Priorityheader field; optional via DEFAULT_PRIORITY (16+)optional via DEFAULT_PRIORITY (16+)gated by flag 0x10always present
Properties / Extensionsgated by EXTENSIONS bit (16+); previously a length field in 14gated by EXTENSIONS bit (16+)gated by flag 0x20always present (length may be 0)

Beyond per-Object delta encoding, the Key-Value-Pair Type itself became delta-encoded inside each Properties/Extensions list at draft-16: Delta Type (i) carries the difference from the previous Type in the same list, starting from 0. This compresses lists of monotonically-numbered KVP types.

Other notable encoding changes

  • Self-contained varint (vi64) in draft-17. draft-14/16 reference RFC 9000 §16 varints, which max out at 2⁶²−1 (4 valid lengths: 1, 2, 4, 8 bytes). Draft-17 defines its own encoding inline (§1.4.1) using a unary-style length prefix, with eight valid encoded lengths (1, 2, 3, 4, 5, 6, 8, 9 bytes) and a new range of 0 to 2⁶⁴−1 via the 9-byte form (prefix 11111111, 64 usable value bits). Extending the range to the full unsigned-64 was a primary motivator for the new encoding. The 7-byte length code (prefix 11111100) is invalid and MUST close the session with PROTOCOL_VIOLATION. Wire impact of PR #1595.
  • Subgroup ID = first Object ID rule. Stable across 14/16/17. In 14 it’s a Type lookup value; in 16/17 it’s SUBGROUP_ID_MODE = 0b01. PR #1608 (Apr 23, 2026) tightens the publisher normative rule: “Original publishers SHOULD assign each Subgroup a Subgroup ID equal to the Object ID.”
  • Object ID delta + 1 rule (subgroup stream) is identical in all three drafts.
  • Stream Cancellation reset codes grew over time: 14 had INTERNAL_ERROR / CANCELLED / DELIVERY_TIMEOUT / SESSION_CLOSED. 16 added UNKNOWN_OBJECT_STATUS (0x4) and MALFORMED_TRACK (0x12). 17 added TOO_FAR_BEHIND (0x5) and EXCESSIVE_LOAD (0x9), and PR #1606 (Apr 23 2026) generalised the codes (GOING_AWAY, EXPIRED_AUTH_TOKEN, SESSION_CLOSED).
  • Malformed Tracks list went from 4 items (14) to 10 (16); 17 expanded item 7 to mention the END_OF_GROUP bit.

Active Issues (April 2026)

  • PR #1586Make Object ID and Group ID delta encoded in Fetch responsesmerged Apr 27 2026 (closes Martin’s long-running #877 “Pack the bits”). This is the work captured above for draft-16/17 FETCH framing.
  • PR #1608Make Subgroup ID identical to first Object Id in the Subgroup (fixes #1405, closes #1593) — on the Apr 27 interim agenda.
  • PR #1593 — Allow framing single Objects without Subgroup ID — to be closed by #1608.
  • #1550 — Properties Type collision between moq-transport-16 and loc-01.

Related

  • moq-transport — Full protocol specification
  • track-properties — KVP / Properties metadata system (renamed from Extension Headers in draft-17)
  • streams-and-framing — Control, request, subgroup, and FETCH stream types and how they changed across versions
  • moq-loc — Container format for object payloads