§5.1.

Subscriptions

All subscriptions begin in the Idle state. A subscription can be initiated and moved to the Pending state by either a publisher or a subscriber. A publisher initiates a subscription to a track by sending the PUBLISH message. The subscriber either accepts or rejects the subscription using PUBLISH_OK (Section 10.5) or REQUEST_ERROR. A subscriber initiates a subscription to a track by sending the SUBSCRIBE message. The publisher either accepts or rejects the subscription using SUBSCRIBE_OK or REQUEST_ERROR. Once either of these sequences is successful, the subscription moves to the Established state and can be updated by the subscriber using REQUEST_UPDATE. Either endpoint can terminate an Established subscription, moving it to the Terminated state. The subscriber terminates a subscription in the Pending (Subscriber) or Established states by sending STOP_SENDING. The publisher terminates a subscription in the Pending (Publisher) or Established states by sending PUBLISH_DONE and closing the stream.

This diagram shows the subscription state machine:

                              +--------+
                              |  Idle  |
                              +--------+
                                |    |
                      SUBSCRIBE |    | PUBLISH
                    (subscriber)|    | (publisher)
                                V    V
                   +--------------+ +--------------+
                   | Pending      | | Pending      |
              +----| (Subscriber) | | (Publisher)  |----+
              |    +--------------+ +--------------+    |
              |                 |    |                  |
REQUEST_ERROR |    SUBSCRIBE_OK |    | PUBLISH_OK       | REQUEST_ERROR
(publisher)   |      (publisher)|    | (subscriber)     | (subscriber)
              |                 V    V                  |
              |            +-------------+              |
              |            | Established | ------+
              |            |             |       | REQUEST_UPDATE
              |            +-------------+ <-----+
              |                 |    |                  |
              +--- STOP_SENDING |    | PUBLISH_DONE ----+
              |     (subscriber)|    | (publisher)      |
              |                 V    V                  |
              |            +-------------+              |
              +----------->| Terminated  | <------------+
                           +-------------+

A publisher MUST send exactly one SUBSCRIBE_OK or REQUEST_ERROR in response to a SUBSCRIBE. A subscriber MUST send exactly one PUBLISH_OK (Section 10.5) or REQUEST_ERROR in response to a PUBLISH. The peer SHOULD close the session with a protocol error if it receives more than one.

All Established subscriptions have a Forward State which is either 0 or 1. The publisher does not send Objects if the Forward State is 0, and does send them if the Forward State is 1. The initiator of the subscription sets the initial Forward State in either PUBLISH or SUBSCRIBE. The subscriber can send REQUEST_UPDATE to update the Forward State. Control messages, such as PUBLISH_DONE (Section 10.12) are sent regardless of the forward state.

Either endpoint can initiate a subscription to a track without exchanging any prior messages other than SETUP. Relays MUST NOT send any PUBLISH messages without knowing the client is interested in and authorized to receive the content. The communication of intent and authorization can be accomplished by the client sending SUBSCRIBE_NAMESPACE, or conveyed in other mechanisms out of band.

An endpoint MAY SUBSCRIBE to a Track it is publishing, though only Relays are required to handle such a SUBSCRIBE. Such self-subscriptions are identical to subscriptions initiated by other endpoints, and all published Objects will be forwarded back to the endpoint, subject to priority and congestion response rules.

An endpoint MAY have multiple concurrent subscriptions to the same Track, each identified by a unique Request ID. A publisher MAY assign the same or different Track Aliases to these subscriptions.

When an Object matches the filters of multiple subscriptions to the same Track, the publisher MUST send the Object once for each matching subscription, even when those subscriptions share the same Track Alias. Because subscriptions can share a Track Alias, the subscriber re-applies each subscription's filter to determine which subscription a received Object belongs to. Subscribers SHOULD avoid overlapping filters across subscriptions to the same Track, as they are responsible for deduplicating any resulting duplicate Objects.

A publisher SHOULD begin sending incomplete objects when available to avoid incurring additional latency.

Publishers MAY start sending Objects on PUBLISH-initiated subscriptions before receiving a PUBLISH_OK response to reduce latency. Doing so can consume unnecessary resources in cases where the Subscriber rejects the subscription with REQUEST_ERROR or sets Forward=0 in REQUEST_UPDATE. It can also result in the Subscriber dropping Objects if its buffering limits are exceeded (see Section 11.3 and Section 11.4.2).

5.1.1. Subscription State Management

A subscriber keeps subscription state until it cancels the request (see Section 3.3.3), or until receipt of a PUBLISH_DONE or REQUEST_ERROR. Note that PUBLISH_DONE does not usually indicate that state can immediately be removed, see Section 10.12.

The Publisher can remove subscription state as soon as it has received STOP_SENDING. It MUST reset any open streams associated with the SUBSCRIBE.

The Publisher can also immediately delete subscription state after sending PUBLISH_DONE, but MUST NOT send it until it has closed all related streams.

A REQUEST_ERROR indicates no objects will be delivered, and both endpoints can immediately remove relevant state. Objects MUST NOT be sent for requests that end with an error.

5.1.2. Location Filters

Subscribers can specify a Location filter on a subscription indicating to the publisher which Objects to send. Subscriptions without a filter pass all Objects published or received via upstream subscriptions.

Fetch requests can also specify a Location filter. Fetch requests without a filter include all Locations from {0, 0} up to Largest Object (defined below).

A Location filter specifies an inclusive range of Locations. Only objects with Locations within the inclusive range pass the filter.

An object published or received in a subgroup or datagram is subscription-delivered. Objects delivered via a fill fetch stream (see Section 5.1.3) are fill-delivered.

Some Location filters are defined to be relative to the Largest Object. The Largest Object is the Object with the largest Location (Section 1.4.2) in the Track from the perspective of the publisher processing the message. Largest Object updates when the first byte of an Object with a Location larger than the previous value is published or received through a subscription.

A Location filter parameter has the following length-prefixed structure:

LOCATION_FILTER Parameter {
  Parameter Type (vi64) = 0x21,
  Length (vi64),
  [StartGroup (vi64),]
  [StartObject (vi64),]
  [EndGroupDelta (vi64),]
  [EndObject (vi64),]
}

Length (in bytes) determines how many optional vi64 fields are present. A length of 0 indicates no filter, for example to remove the filter in REQUEST_UPDATE. * If only one field is present, it is StartGroup. * If only two fields are present, they are StartGroup and StartObject. * If only three fields are present, they are StartGroup, StartObject, and EndGroupDelta.

If only StartGroup is present, it is a relative number of groups prior to the Next Group, hence the start Location is {Largest Object.Group + 1 - StartGroup, 0}. For example: * StartGroup=0 will start at the Next Group * StartGroup=1 will start at the current group * StartGroup=2 will start at 1 group prior to the current group * StartGroup=N will start at N-1 groups prior to the current group

If only StartGroup and StartObject are present and both 0, the start Location is the Next Object which is {Largest Object.Group, Largest Object.Object + 1}, or {0, 0} if no content has been delivered yet. An open-ended filter that starts at absolute Location {0, 0} is equivalent to unfiltered, so the subscriber need not include a Location filter. Note that due to network reordering or prioritization, relays can receive Objects with Locations smaller than Largest Object after the SUBSCRIBE is processed, but these Objects do not pass this filter.

If a relative start group results in a computed absolute group less than 0, the computed value is set to 0; if greater than 2^64 - 1, it is set to 2^64 - 1.

Otherwise, all fields are absolute. EndGroupDelta is delta encoded from StartGroup, but both the start and end groups are absolute, not relative to Largest Object. If StartGroup + EndGroupDelta exceeds 2^64 - 1, the endpoint MUST close the session with a PROTOCOL_VIOLATION.

When EndGroupDelta and EndObject are omitted from a subscription filter, the subscription is open-ended. When they are omitted from a Fetch, the EndGroup and EndObject are Largest Object.

When EndObject is omitted, the filter includes all objects in the End Group.

A Location Filter on a subscription is always valid, even if it specifies a range entirely before Largest Object.

A publisher MUST NOT send subscription-delivered objects from outside the requested range. Because updating filters is asynchronous, subscribers can receive objects outside the current filter.

A publisher does not end a subscription solely because the Largest Object advances past the end of the current Location Filter.

Fill-delivered objects are governed by the Location filter in FILL_PARAMETERS (see Section 5.1.3).

5.1.3. Fill Semantics

A subscription that carries a FILL_PARAMETERS parameter (see Section 10.2.15) causes the publisher to open a unidirectional stream beginning with a FETCH_HEADER (see Section 11.4.4) and delivered as a FETCH response (see Section 10.13). This is called a fill fetch stream.

The fill range is the range of Locations selected by the Location filter inside FILL_PARAMETERS, or the subscription's Location filter if it is omitted. The filter is evaluated using the rules for a Fetch in Section 5.1.2, so the fill range never extends beyond Largest Object. When the subscription has no Location filter, or the LOCATION_FILTER inside FILL_PARAMETERS is zero-length, the fill range is the entire track up to Largest Object. The subscriber learns the Largest Object from the LARGEST_OBJECT parameter in SUBSCRIBE_OK or REQUEST_UPDATE_OK.

Because the fill range is specified independently of the subscription's Location filter, a subscriber can retrieve a range of Groups prior to the live edge while the subscription itself starts at the Next Group. If the fill range is empty, or starts after Largest Object, the publisher does not open a fill fetch stream.

The fill fetch stream inherits the subscription's parameters, including subscriber priority, range filters and authorization; parameters carried inside FILL_PARAMETERS override them for the fill fetch stream. FILL_TIMEOUT (see Section 10.2.5) applies to fill fetch streams in the same way it applies to a FETCH.

The FETCH_HEADER on the fill fetch stream carries the Request ID of the message that initiated it: the SUBSCRIBE Request ID for the initial fill, or the REQUEST_UPDATE Request ID for a subsequent fill. As a result of REQUEST_UPDATE, a subscription can have multiple fill fetch streams open at once, each identified by its Request ID; opening a new fill fetch stream does not implicitly cancel any previously opened fill fetch streams.

An object delivered on the fill fetch stream is fill-delivered. When the fill range overlaps the subscription's Location filter, an object can be both fill-delivered and subscription-delivered. A subscriber that wants each Object delivered exactly once uses the Next Object Subscription Location Filter coupled with an open-ended fill range, which the publisher will end at Largest Object.

5.1.3.1. Opening and Closing Fill Fetch Streams

A publisher opens a fill fetch stream when it processes a SUBSCRIBE or REQUEST_UPDATE that carries FILL_PARAMETERS while Forward State is 1.

  • FILL_PARAMETERS carried while Forward State is 0 opens no fill fetch stream. Transitioning to Forward State 1 without re-sending FILL_PARAMETERS does not open one either.

  • A REQUEST_UPDATE that does not carry FILL_PARAMETERS does not open a new fill fetch stream.

  • When the subscription is cancelled, the publisher MUST reset any open fill fetch streams.

The publisher signals that the fill is complete by closing the stream with a FIN once all objects in the fill range have been delivered. Because there is no REQUEST_ERROR associated with a fill fetch stream, the publisher signals a fill failure by resetting the stream; it MUST open a fill fetch stream and reset it immediately after the FETCH_HEADER if necessary. A subscriber can cancel a fill fetch stream independently using STOP_SENDING. Resetting or cancelling a fill fetch stream, by either endpoint, does not affect the subscription, which continues to deliver objects using subscribe subgroups and datagrams.

5.1.4. Range Filters

Range Filters are parameters in SUBSCRIBE, FETCH, or SUBSCRIBE_TRACKS that tell a publisher to filter tracks (via TRACK PROPERTY FILTER) and objects according to subscriber-provided criteria. Range filters are specified as ranges of integer values in Track and Object Properties and other Object header fields (Subgroup ID, Object ID, and Publisher Priority). There are five Range Filter parameter types, 0x25-0x29, as shown below.

SUBGROUP_FILTER {
  Type (vi64) = 0x25,
  Length (vi64),
  [SetID (8)],
  [Range (..) ...]
}

OBJECTID_FILTER {
  Type (vi64) = 0x26,
  Length (vi64),
  [SetID (8)],
  [Range (..) ...]
}

PRIORITY_FILTER {
  Type (vi64) = 0x27,
  Length (vi64),
  [SetID (8)],
  [Range (..) ...]
}

OBJECT_PROPERTY_FILTER {
  Type (vi64) = 0x28,
  Length (vi64),
  [SetID (8)],
  [Property Type (vi64)],
  [Range (..) ...]
}

TRACK_PROPERTY_FILTER {
  Type (vi64) = 0x29,
  Length (vi64),
  [SetID (8)],
  [Property Type (vi64)],
  [Range (..) ...]
}

Range {
  Start (vi64),
  [End (vi64)]
}

Length (vi64) is the byte count of all fields after itself. When Length is 0, there is no filter and no further fields are present. This can be used in REQUEST_UPDATE to remove a filter. The Object Property and Track Property Filters include a Property Type (vi64) which follows SetID.

Each Range is an inclusive Start/End pair. End is optional in the last pair; if omitted it indicates the last Range is open-ended. An object matches the filter if its value falls within any Range (i.e., Ranges are OR'd within a filter parameter).

Each Start is delta encoded from the prior Range's End (or from 0 for the first Range), and End is delta encoded from its own Start. If adding the delta would exceed 2^64-1, the request MUST be rejected with INVALID_FILTER. For example, ranges 3-5 and 10-15 encode as: Start=3, End=2, Start=5, End=5.

Filter parameters with the same SetID are AND'd; distinct SetIDs are OR'd. The final result is SetID=0 OR SetID=1 OR ... SetID=255, where each SetID=i is the AND of all filter parameters carrying that SetID.

The Track Property filter parameter MAY appear multiple times in a SUBSCRIBE_TRACKS message or REQUEST_UPDATE for it. All other filter parameters MAY appear multiple times in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, or REQUEST_UPDATE (on a subscription, from the subscriber only) message. If the same combination of Parameter Type, SetID, and Property Type (only in the Track and Object Property Filters) repeat in any message, an endpoint MUST reject this with REQUEST_ERROR with error code INVALID_FILTER.

In REQUEST_UPDATE, Length of 0 removes the filter; non-zero replaces it entirely. If a filter parameter is omitted from REQUEST_UPDATE, it is unchanged. If omitted from other messages, the default is no filter.

Range Filters are only allowed if the setup option MAX_FILTER_RANGES is non-zero, which limits the total number of Ranges allowed in all Range Filter parameters for a given subscription or fetch. If this limit is exceeded, an endpoint MUST reject this with REQUEST_ERROR with error code INVALID_FILTER.

The Track Property Filter can be used in SUBSCRIBE_TRACKS to filter PUBLISH messages with required Track Property types and values. PUBLISH messages which pass the filter will be forwarded while those which do not pass it will not be forwarded nor will any Objects.

The Object Property Filter can be used to filter Objects with required Object Property types and values. It only filters Object Properties in the Object header, and does not evaluate Track Properties in PUBLISH messages.

5.1.5. Combining Filters

All filter types are combined using logical "AND" operations to further restrict which tracks and objects pass all filter criteria. This includes all Range Filters Section 5.1.4 and Location Filters Section 5.1.2, which can be evaluated in any order. The Forward parameter is also a type of filter. The publisher MUST forward only objects that pass all filters.

Pass = Forward AND Location Filters AND Range Filters

5.1.6. Joining an Ongoing Track

The MOQT Object model is designed with the concept that the beginning of a Group is a join point, so in order for a subscriber to join a Track, it needs to request an existing Group or wait for a future Group. Different applications will have different approaches for when to begin a new Group.

To join a Track immediately, the subscriber sends a SUBSCRIBE with a Location Filter Section 5.1.2 that starts at the Next Object. Delivery begins with the next Object and can begin mid-group.

To join a Track at the current Group, the subscriber sends a SUBSCRIBE with a Location Filter that starts at the Next Object and a FILL_PARAMETERS parameter (see Section 10.2.15) whose Location filter has StartGroup=1, which fills the current Group from its start.

To join a Track at a past Group, the subscriber sends a SUBSCRIBE with a FILL_PARAMETERS parameter whose Location filter selects the intended Groups, which can be relative. The publisher delivers the fill range on a fill fetch stream and subscription-delivered Objects in subgroups or datagrams (see Section 5.1.3).

To join a Track at the next Group, the subscriber sends a SUBSCRIBE with a Location Filter Section 5.1.2 that starts at the Next Group.

5.1.6.1. Dynamically Starting New Groups

While some publishers will deterministically create new Groups, other applications might want to only begin a new Group when needed. A subscriber joining a Track might detect that it is more efficient to request the Original Publisher create a new group than to fill the current group. Publishers indicate a Track supports dynamic group creation using the DYNAMIC_GROUPS Track Property (Section 12.6).

One possible subscriber pattern is to SUBSCRIBE to a Track using a Location Filter that starts at the Next Object and observe the Largest Object in the response. If the Object ID is below the application's threshold, the subscriber sends a FETCH for the beginning of the Group. If the Object ID is above the threshold and the Track supports dynamic groups, the subscriber sends a REQUEST_UPDATE message with the NEW_GROUP_REQUEST parameter equal to the Next Group (see Section 10.2.19).

Another possible subscriber pattern is to send a SUBSCRIBE with a Location Filter that starts at the Next Group and NEW_GROUP_REQUEST equal to 0. The value of DYNAMIC_GROUPS in SUBSCRIBE_OK will indicate if the publisher supports dynamic groups. A publisher that does will begin the next group as soon as practical.

This is one section of the MoQT specification, rendered per-section for quick reference and citation. The authoritative text is draft-ietf-moq-transport-20 at the IETF.