diff --git a/CLAUDE.md b/CLAUDE.md index 11b5d95..9cdc566 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,10 +12,24 @@ This is an IETF Internet-Draft repository for Media over QUIC (MOQ) protocol spe ## Build Commands +Run `make` to build/validate drafts. **`make` self-bootstraps its toolchain** — +on first run it clones the i-d-template submodule into `lib/`, creates a Python +venv (`lib/.venv`, for xml2rfc), and bundler-installs kramdown-rfc into +`lib/.gems`. No manual install of kramdown-rfc/xml2rfc is needed; you only need +system `python3` (with venv+pip), `ruby` (with `bundle`), a C compiler, and +network access on the first build. Do not claim the drafts can't be built — run +`make`. (A successful kramdown-rfc step also validates a draft's syntax.) + +There is no separate format step: prettier is disabled (`.prettierignore` is +`**`), so editing the markdown is the whole workflow. + ```bash # Build all drafts (generates HTML and text versions) make +# Build/validate a single draft (fast feedback while editing) +make draft-lcurley-moq-lite.txt + # Clean build artifacts make clean diff --git a/draft-lcurley-moq-compression.md b/draft-lcurley-moq-compression.md new file mode 100644 index 0000000..13f7302 --- /dev/null +++ b/draft-lcurley-moq-compression.md @@ -0,0 +1,182 @@ +--- +title: "MoQ Payload Compression Extension" +abbrev: "moq-compression" +category: info + +docname: draft-lcurley-moq-compression-latest +submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" +number: +date: +v: 3 +area: wit +workgroup: moq + +author: + - + fullname: Luke Curley + email: kixelated@gmail.com + +normative: + moqt: I-D.ietf-moq-transport + RFC1951: + +informative: + +--- abstract + +This document defines a payload compression extension for MoQ Transport {{moqt}}. +A track-level Compression property lets the original publisher signal that a track's object payloads are worth compressing, and with which algorithm. +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. +Each object is compressed independently so objects remain individually decodable, and the decompressed bytes — the actual object — are unchanged end to end. + +--- middle + +# Conventions and Definitions +{::boilerplate bcp14-tagged} + + +# Introduction +{{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). +For media this is the right layering: already-compressed codecs (H.264, Opus, AV1) gain nothing from a second compression pass. + +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. +For these tracks there is no standard, transport-visible way to compress payloads, so each application reinvents it, and relays cannot help. + +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. +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. + +- **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. +- **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. + + +# Setup Negotiation +The Payload Compression extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3. +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. + +Each endpoint advertises the algorithms it can decompress by including the following Setup Option: + +~~~ +COMPRESSION Setup Option { + Option Key (vi64) = 0xC03DE + Option Value Length (vi64) + Algorithm (vi64) ... +} +~~~ + +**Algorithm**: +One or more Algorithm identifiers (see [Compression Algorithms](#compression-algorithms)) that the sender can decompress, each a varint, filling the Option Value. +The identifier `none` (0) MUST NOT be listed (it requires no negotiation). + +A sender MUST NOT compress with an algorithm the receiver did not advertise in its SETUP. +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. + + +# COMPRESSION Track Property +The COMPRESSION property is the original publisher's signal that a track's object payloads are worth compressing, and which algorithm to use. +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. +Because the value is a single integer, COMPRESSION uses an even Type so the value is a bare varint: + +~~~ +COMPRESSION Track Property { + Type (vi64) = 0xC03D0 + Value (vi64) ; Algorithm identifier +} +~~~ + +**Value**: +The Algorithm identifier the publisher recommends for this track's payloads. +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. + +The property is fixed for the lifetime of the track and MUST NOT change. +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. + +Compression is enabled only by the combination of this track property and the extension being negotiated on a hop. +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. + +Whether payloads are actually compressed is decided per hop: + +- 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. +- 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. + +Compression applies to the object payload only; object properties and message framing are never compressed. +An empty payload (size 0) MUST NOT be compressed and remains empty on the wire. + +A publisher SHOULD set COMPRESSION only for payload types that benefit from it. +Already-compressed media SHOULD omit it (or use `none`). + + +# Compression Algorithms {#compression-algorithms} +This document defines the following algorithms. +Further algorithms MAY be registered (see [IANA Considerations](#iana-considerations)). + +| ID | Name | Description | +|---:|:--------|:--------------------------------------------------------| +| 0 | none | Payloads are transmitted verbatim. The default. | +| 1 | deflate | Raw DEFLATE {{RFC1951}}, with no zlib or gzip framing. | + +For `deflate`, each object payload is an independent raw DEFLATE stream. +There is no shared dictionary or state between objects, so each object decompresses on its own. + + +# Relay Behavior +A relay forwards the COMPRESSION track property unchanged — it is the publisher's end-to-end signal — and applies compression independently on each hop. + +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. +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. + +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. +In every case the decompressed bytes delivered to the application MUST be identical to what the origin published. + +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. + + +# Security Considerations +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. +A publisher MUST NOT set COMPRESSION on a track whose object payloads combine secret material with attacker-influenced material. +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. + +A malicious sender could emit a small compressed payload that decompresses to a very large buffer (a "decompression bomb"). +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. + +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`). + + +# IANA Considerations + +This document requests the following registrations. +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`). +The parameter Type is even so that its value is a bare varint with no length prefix (see {{moqt}} Section 2.5). + +## MOQT Setup Options + +This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required. + +| Value | Name | Reference | +|:--------|:------------|:--------------| +| 0xC03DE | COMPRESSION | This Document | + +## MOQT Properties + +This document requests a registration in the "MOQT Properties" registry ({{moqt}} Section 15.8), used for object and track properties. + +| Value | Name | Scope | Reference | +|:--------|:------------|:------|:--------------| +| 0xC03D0 | COMPRESSION | Track | This Document | + +## MOQT Compression Algorithms + +This document requests a new "MOQT Compression Algorithms" registry, with a registration policy of Specification Required. +The initial contents are: + +| ID | Name | Reference | +|---:|:--------|:--------------| +| 0 | none | This Document | +| 1 | deflate | This Document | + + +--- back + +# Acknowledgments +{:numbered="false"} + +This document was drafted with the assistance of Claude, an AI assistant by Anthropic. diff --git a/draft-lcurley-moq-relay-hops.md b/draft-lcurley-moq-relay-hops.md new file mode 100644 index 0000000..fc5b754 --- /dev/null +++ b/draft-lcurley-moq-relay-hops.md @@ -0,0 +1,189 @@ +--- +title: "MoQ Relay Hops Extension" +abbrev: "moq-relay-hops" +category: info + +docname: draft-lcurley-moq-relay-hops-latest +submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" +number: +date: +v: 3 +area: wit +workgroup: moq + +author: + - + fullname: Luke Curley + email: kixelated@gmail.com + +normative: + moqt: I-D.ietf-moq-transport + +informative: + +--- abstract + +This document defines a Relay Hops extension for MoQ Transport {{moqt}}. +Each namespace advertisement carries an ordered list of Hop IDs identifying the relays it has traversed, starting with the origin publisher. +This lets a subscriber prefer the shortest of several paths to the same namespace, identify which advertisements refer to the same broadcast (same origin), and lets a relay cluster detect and avoid routing loops. +A namespace subscription MAY carry a single Hop ID to exclude, which a relay uses to suppress advertisements that have already passed through that hop. + +--- middle + +# Conventions and Definitions +{::boilerplate bcp14-tagged} + + +# Introduction +{{moqt}} is designed to deliver content end-to-end through a mesh of relays. +A namespace advertisement (PUBLISH_NAMESPACE, {{moqt}} Section 10.15) originates at a publisher and propagates downstream through one or more relays toward interested subscribers, which express interest with SUBSCRIBE_NAMESPACE ({{moqt}} Section 10.18). + +In a redundant deployment, relays are interconnected so that the same namespace can reach a given relay over more than one path. +This redundancy is desirable for failover, but it leaves a receiver with no information that {{moqt}} does not address: + +- **Path selection**: when the same namespace arrives over multiple paths, a relay or subscriber has no information with which to prefer one path over another (e.g. the shorter, and usually lower-latency, one). +- **Broadcast identity**: two advertisements for the same namespace may refer to the same broadcast or to two distinct origins reusing a namespace. With no origin identity a receiver cannot tell them apart, nor deduplicate redundant paths to one broadcast. +- **Routing loops**: relay A advertises a namespace to relay B, which advertises it back to A (directly or through a cycle). Without a way to recognize an advertisement it has already seen, a relay will re-advertise it indefinitely. + +This extension solves all three with a single mechanism: an ordered list of **Hop IDs** that records the path an advertisement has taken, starting with the origin publisher and with one entry appended per relay. +The first entry identifies the origin (broadcast identity); the list length gives the path length (path selection); a relay finding its own Hop ID already in the list detects a loop. + +## Why per-hop, not end-to-end +The Hop ID list is rewritten at every relay: a relay appends its own Hop ID before forwarding an advertisement downstream. +A relay therefore detects a loop by finding its own Hop ID already present in an incoming advertisement, and a subscriber compares path lengths using the list length. +Hop IDs are chosen randomly (see [Hop IDs](#hop-ids)) so they are unique with overwhelming probability without any central coordination, even across independently operated relays. + + +# Setup Negotiation +The Relay Hops extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3. +An endpoint indicates support by including the following Setup Option: + +~~~ +RELAY_HOPS Setup Option { + Option Key (vi64) = 0x40B55 + Option Value Length (vi64) = 0 +} +~~~ + +The extension applies to a single hop (one MOQT session) and is negotiated independently for each session; a relay MUST NOT assume that because one of its sessions negotiated Relay Hops, another did. + +A relay that negotiated this extension on a downstream session MUST include the HOP_PATH parameter on every PUBLISH_NAMESPACE it sends on that session, and MUST honor an EXCLUDE_HOP parameter it receives in SUBSCRIBE_NAMESPACE. +An endpoint that did not negotiate the extension simply omits these parameters; per {{moqt}} an unknown Key-Value-Pair Type is ignored, so an advertisement forwarded into a non-supporting session loses its hop information gracefully. + + +# Hop IDs +A **Hop ID** is a variable-length integer that identifies a single relay (or the origin publisher) within the path of an advertisement. + +Each relay and each origin publisher chooses its Hop ID **randomly**. +An endpoint SHOULD draw a full-width random value (up to the 62-bit varint maximum) so that the probability of two endpoints choosing the same Hop ID is negligible. +Random assignment means there is no registry, no coordination, and no reserved values: a Hop ID is simply an opaque identifier that is, with overwhelming probability, unique. + +An endpoint SHOULD keep its Hop ID stable for the lifetime of a session (and MAY reuse it across sessions) so that loop detection and path comparison are consistent. + +When a relay bridges an advertisement from an upstream peer that did **not** negotiate this extension, the upstream carries no HOP_PATH. The relay MUST synthesize one (see [Relay Behavior](#relay-behavior)) by assigning a random Hop ID to stand in for the unknown upstream, so that loop detection and path length still work within the cooperating region of the mesh. + + +# HOP_PATH Parameter +The HOP_PATH parameter carries the ordered list of Hop IDs that an advertisement has traversed, from the origin publisher toward the receiver. +It is a Key-Value-Pair (see {{moqt}} Section 2.5) carried in the Parameters of a PUBLISH_NAMESPACE message ({{moqt}} Section 10.15). + +Because the value is a variable-length list, HOP_PATH uses an odd Type so that it is length-prefixed: + +~~~ +HOP_PATH Parameter { + Type (vi64) = 0x40B57 + Length (vi64) + Hop ID (vi64) ... +} +~~~ + +**Hop ID**: +One or more Hop IDs, ordered from the origin publisher (first entry) to the relay immediately upstream of the receiver (last entry). +The number of entries is determined by consuming Hop IDs until `Length` bytes have been read; a receiver MUST close the session with a PROTOCOL_VIOLATION if the entries do not exactly fill `Length`, or if the list is empty (`Length` 0). +HOP_PATH always contains at least one entry: the first entry is the Hop ID of the origin publisher, even before the advertisement has traversed any relay. + +## Relay Behavior +When a relay forwards a namespace advertisement downstream on a session that negotiated this extension, it MUST append its own Hop ID to the HOP_PATH it received. +The relay's own Hop ID is therefore always the last entry of the list it sends. +If the advertisement arrived from an upstream that did not negotiate this extension (and so carried no HOP_PATH), the relay MUST first create a HOP_PATH whose single initial entry is a random Hop ID it assigns to stand in for that unknown upstream, then append its own Hop ID. + +When a relay receives a namespace advertisement on a session that negotiated this extension, it MUST inspect the HOP_PATH: + +- If its own Hop ID already appears in the list, the advertisement has looped. The relay MUST NOT forward it and SHOULD drop it. +- Otherwise the relay MAY forward it downstream, appending its own Hop ID as described above. + +## Path Selection +A relay or subscriber that receives advertisements for the same namespace over multiple sessions MAY use the length of the HOP_PATH list as a tiebreaker, preferring the advertisement with the fewest hops (usually the lowest-latency path). +This is advisory: the receiver MAY apply additional local policy (e.g. measured RTT or administrative preference) and is not required to prefer the shortest path. + +Two advertisements for the same namespace whose HOP_PATH begins with the same Hop ID share an origin and therefore refer to the same broadcast; a receiver MAY treat them as redundant paths and keep only the best one. +If the first Hop IDs differ, the advertisements come from distinct origins that happen to reuse a namespace, and a receiver MUST NOT treat them as interchangeable. + +A publisher (or relay acting as one) SHOULD advertise only the single best path it currently knows for each namespace. +If the best path changes — for example after a relay failover — the publisher MAY re-advertise the namespace; the new advertisement, carrying an updated HOP_PATH, replaces the prior one per the namespace-advertisement semantics of {{moqt}}. + + +# EXCLUDE_HOP Parameter +The EXCLUDE_HOP parameter lets a downstream subscriber tell an upstream relay to suppress advertisements that have already passed through a given hop. +A relay in a cluster uses it to prevent the upstream from sending back an advertisement that the downstream originated, the most common source of a two-hop loop. + +It is a Key-Value-Pair carried in the Parameters of a SUBSCRIBE_NAMESPACE message ({{moqt}} Section 10.18). +A single Hop ID is excluded, so EXCLUDE_HOP uses an even Type and its value is a bare varint with no length prefix: + +~~~ +EXCLUDE_HOP Parameter { + Type (vi64) = 0x40B58 + Hop ID (vi64) +} +~~~ + +**Hop ID**: +The single Hop ID to exclude. +To exclude nothing, a subscriber simply omits the parameter; there is no reserved "exclude nothing" value. + +A relay that receives a SUBSCRIBE_NAMESPACE carrying EXCLUDE_HOP MUST NOT send, on that session, any PUBLISH_NAMESPACE whose HOP_PATH contains the excluded Hop ID (including the entry the relay would itself append). +The exclusion is scoped to the namespace subscription it accompanies. + +A relay that receives EXCLUDE_HOP without having negotiated the Relay Hops extension ignores it as an unknown parameter, which is the safe default (it simply does not perform the exclusion). + + +# Security Considerations +Hop IDs are opaque random integers, so an individual value reveals nothing about a relay's identity or location. +A HOP_PATH list does, however, expose the number of hops an advertisement traversed, which can hint at the size and shape of a relay deployment. +A relay that wishes to hide its internal topology MAY coalesce the hops within its own administrative domain into a single Hop ID, or strip HOP_PATH entirely, before forwarding across a trust boundary (for example, to a subscriber outside the operator's own relay cluster). +This is analogous to how BGP confederations hide internal AS topology while preserving loop detection; it is a deployment choice, not a requirement. + +Because a relay only ever appends to HOP_PATH, it cannot make a competing path appear shorter than it is; the worst a misbehaving relay can do is under-report the upstream portion of its own path to win an advisory tie-break. Since path selection is advisory, the impact is limited to a suboptimal path choice. A receiver MUST NOT make security decisions based on Hop IDs, and SHOULD corroborate path selection with locally measured signals (e.g. RTT) when it matters. + + +# IANA Considerations + +This document requests the following registrations. +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`). +HOP_PATH carries a list, so its Type is odd (length-prefixed); EXCLUDE_HOP carries a single Hop ID, so its Type is even (a bare varint). See {{moqt}} Section 2.5. + +## MOQT Setup Options + +This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required. + +| Value | Name | Reference | +|:--------|:-----------|:--------------| +| 0x40B55 | RELAY_HOPS | This Document | + +## MOQT Message Parameters + +This document requests registrations in the "MOQT Message Parameters" registry ({{moqt}} Section 15.7). +HOP_PATH and EXCLUDE_HOP are message parameters carried in PUBLISH_NAMESPACE and SUBSCRIBE_NAMESPACE respectively. + +| Value | Name | Carried In | Reference | +|:--------|:------------|:--------------------|:--------------| +| 0x40B57 | HOP_PATH | PUBLISH_NAMESPACE | This Document | +| 0x40B58 | EXCLUDE_HOP | SUBSCRIBE_NAMESPACE | This Document | + + +--- back + +# Acknowledgments +{:numbered="false"} + +This document was drafted with the assistance of Claude, an AI assistant by Anthropic. diff --git a/draft-lcurley-moq-timestamp.md b/draft-lcurley-moq-timestamp.md new file mode 100644 index 0000000..8d0834e --- /dev/null +++ b/draft-lcurley-moq-timestamp.md @@ -0,0 +1,176 @@ +--- +title: "MoQ Object Timestamp Extension" +abbrev: "moq-timestamp" +category: info + +docname: draft-lcurley-moq-timestamp-latest +submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF" +number: +date: +v: 3 +area: wit +workgroup: moq + +author: + - + fullname: Luke Curley + email: kixelated@gmail.com + +normative: + moqt: I-D.ietf-moq-transport + +informative: + +--- abstract + +This document defines an extension for MoQ Transport {{moqt}} that attaches a media presentation timestamp and duration to each object. +A track-level Timescale property establishes the units, an object-level Timestamp property carries the presentation time of each object, and an optional Duration property carries its presentation duration. +Exposing media time to the transport lets relays make consistent age-based decisions (e.g. dropping stale objects) without parsing the media container, and it remains consistent across hops regardless of buffering or jitter. + +--- middle + +# Conventions and Definitions +{::boilerplate bcp14-tagged} + + +# Introduction +{{moqt}} treats object payloads as opaque: "the amount of time elapsed between publishing an Object in Group ID N and in a Group ID > N ... is not defined by this specification" ({{moqt}} Section 2.3.1), and timing is left to the application's container format. + +This works for endpoints that parse the media, but not for relays. +A relay frequently needs a notion of *when* an object is meant to be presented: + +- **Age-based dropping**: a relay serving a live, latency-sensitive subscription wants to drop objects that are too old to be useful, keeping the freshest content flowing under congestion. Without a timestamp it can only approximate age from wall-clock arrival time, which drifts across hops and is corrupted by buffering and jitter. +- **Consistent expiration across hops**: every relay on a path should make the same drop decision for the same object. A timestamp embedded in the object is identical at every hop; a wall-clock arrival time is not. +- **Synchronization hints**: a subscriber can align objects from multiple tracks (e.g. audio and video) using a shared media timeline without first decoding each container. + +MoQ also demultiplexes media into many independent tracks — audio, video, captions, metadata, and more — so a timestamp is needed on nearly every track. +Re-implementing per-object timestamping inside each application's container format, for every track, is repetitive and error-prone; standardizing it at the transport lets one implementation serve every track and lets relays use it directly. + +This extension exposes media time to the transport with three Key-Value-Pairs ({{moqt}} Section 2.5): a track-level **Timescale**, an object-level **Timestamp**, and an optional object-level **Duration**. +The transport does not interpret the *meaning* of the timeline (it is still the application's clock); it only uses the timestamp for relative age comparisons. + + +# Setup Negotiation +The Object Timestamp extension is negotiated during the SETUP exchange as defined in {{moqt}} Section 10.3. +An endpoint indicates support by including the following Setup Option: + +~~~ +TIMESTAMP Setup Option { + Option Key (vi64) = 0x915C1 + Option Value Length (vi64) = 0 +} +~~~ + +The properties defined below are ordinary Key-Value-Pairs and a receiver that does not understand them ignores them per {{moqt}}. +Negotiation is therefore not required for correctness, but a publisher SHOULD send the Setup Option so that a relay knows it can rely on object timestamps for age-based decisions rather than falling back to wall-clock arrival time. +A relay MAY perform timestamp-based dropping for a track only if the upstream publisher advertised this option (or the track carries a non-zero Timescale). + + +# TIMESCALE Track Property +The TIMESCALE property establishes the units for every Timestamp and Duration on a track. +It is a track-level Key-Value-Pair, carried with the track's properties (see {{moqt}} Section 2.5 and Section 12). +Because the value is a single integer, TIMESCALE uses an even Type so the value is a bare varint with no length prefix: + +~~~ +TIMESCALE Track Property { + Type (vi64) = 0x915C0 + Value (vi64) ; units per second +} +~~~ + +**Value**: +The number of timestamp units per second. +Common values include `1000` (milliseconds), `1000000` (microseconds), `48000` (a typical audio sample rate), and `90000` (the RTP video clock). +A value of `0`, or the absence of the property, means the track has no media timeline: Timestamp and Duration properties, if present, MUST be ignored, and a relay MUST fall back to wall-clock arrival time for any age-based decision. + +The Timescale is fixed for the lifetime of the track and MUST NOT change. + +The Timescale is required to interpret the units of every Timestamp and Duration, so a receiver cannot resolve an object's timing until it has the track's properties. +Those properties are delivered in SUBSCRIBE_OK or TRACK_STATUS ({{moqt}} Section 12), so a receiver that begins receiving objects before it has them MUST buffer the timing (or treat it as unknown) until the Timescale arrives. +A relay that has not yet learned the Timescale MUST fall back to wall-clock arrival time for any age-based decision. + + +# TIMESTAMP Object Property +The TIMESTAMP property carries the presentation time of an object, in the track's Timescale. +It is an object-level Key-Value-Pair carried in the object's properties ({{moqt}} Section 2.5, 11.2.1.2). +It uses an even Type so the value is a bare varint: + +~~~ +TIMESTAMP Object Property { + Type (vi64) = 0x915C2 + Value (vi64) ; absolute presentation time, in Timescale units +} +~~~ + +**Value**: +The absolute presentation timestamp of the object, expressed in the track's Timescale. +Any value (including 0) is valid. + +A publisher SHOULD attach TIMESTAMP to every object on a track whose Timescale is non-zero. +An object with no TIMESTAMP on such a track has no media time; for age comparisons a receiver MUST treat its effective time as the wall-clock arrival time of the object, which avoids stalling expiration on objects that intentionally carry no timestamp (e.g. keep-alives or gap markers). + +## Age-Based Dropping +Given two objects on the same track, both with TIMESTAMP and a non-zero Timescale, a relay computes their relative age as the difference of their timestamps divided by the Timescale. +A relay serving a live subscription MAY drop an object whose age relative to the most recent object on the track exceeds a locally configured or application-supplied threshold, resetting the corresponding stream per {{moqt}}. +This decision is identical at every hop because it depends only on values embedded in the objects, not on arrival time. + +A relay MUST NOT use timestamps to reorder delivery beyond what {{moqt}} already permits; this property informs *dropping*, not transmission order. + + +# DURATION Object Property +The DURATION property carries the presentation duration of an object, in the track's Timescale. +It is optional and is an object-level Key-Value-Pair with an even Type: + +~~~ +DURATION Object Property { + Type (vi64) = 0x915C4 + Value (vi64) ; presentation duration, in Timescale units +} +~~~ + +**Value**: +The presentation duration of the object, expressed in the track's Timescale. +A value of `0`, or the absence of the property, means the duration is unknown; the object is presented until the next object begins. + +Duration is primarily an application-level presentation hint, but a relay MAY also use it to refine age-based dropping: an object's Timestamp plus its Duration marks the end of its presentation interval, which is a more precise "this object is now in the past" signal than the Timestamp alone (for example, the last object of a group has no following object to bound it). A relay MUST NOT rely on Duration being present; when it is absent, the relay falls back to comparing Timestamps as in [Age-Based Dropping](#age-based-dropping). + + +# Security Considerations +Timestamps expose the media timeline to relays, which is the point of the extension, but a relay still treats payloads as opaque and gains no access to media content. + +A malicious publisher could supply misleading timestamps (e.g. always claiming an object is fresh) to defeat age-based dropping, or wildly out-of-range timestamps to cause a receiver to mis-estimate age. +A receiver SHOULD bound the age it computes and SHOULD NOT make security decisions based on timestamps. +Because age-based dropping only affects which objects a live subscription receives, the worst case is degraded delivery for that subscription, not a cross-subscription effect. + + +# IANA Considerations + +This document requests the following registrations. +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`). +The three property Types are even so that each value is a bare varint with no length prefix (see {{moqt}} Section 2.5). + +## MOQT Setup Options + +This document requests a registration in the "MOQT Setup Options" registry ({{moqt}} Section 15.4), whose policy is Specification Required. + +| Value | Name | Reference | +|:--------|:----------|:--------------| +| 0x915C1 | TIMESTAMP | This Document | + +## MOQT Properties + +This document requests registrations in the "MOQT Properties" registry ({{moqt}} Section 15.8), used for object and track properties. + +| Value | Name | Scope | Reference | +|:--------|:----------|:-------|:--------------| +| 0x915C0 | TIMESCALE | Track | This Document | +| 0x915C2 | TIMESTAMP | Object | This Document | +| 0x915C4 | DURATION | Object | This Document | + + +--- back + +# Acknowledgments +{:numbered="false"} + +This document was drafted with the assistance of Claude, an AI assistant by Anthropic.