§10.4.

Streams

When Objects are sent on streams, the stream begins with a Subgroup or Fetch Header and is followed by one or more sets of serialized Object fields. If a stream ends gracefully (i.e., the stream terminates with a FIN) in the middle of a serialized Object, the session SHOULD be terminated with a PROTOCOL_VIOLATION.

A publisher SHOULD NOT open more than one stream at a time with the same Subgroup Header field values.

10.4.1. Stream Cancellation

Streams aside from the control stream MAY be canceled due to congestion or other reasons by either the publisher or subscriber. Early termination of a stream does not affect the MoQ application state, and therefore has no effect on outstanding subscriptions.

10.4.2. Subgroup Header

All Objects on a Subgroup stream belong to the track identified by Track Alias (see Section 10.1) and the Subgroup indicated by 'Group ID' and Subgroup ID indicated by the SUBGROUP_HEADER.

If an endpoint receives a subgroup with an unknown Track Alias, it MAY abandon the stream, or choose to buffer it for a brief period to handle reordering with the control message that establishes the Track Alias. The endpoint MAY withhold stream flow control beyond the SUBGROUP_HEADER until the Track Alias has been established. To prevent deadlocks, the publisher MUST allocate connection flow control to the control stream before allocating it any data streams. Otherwise, a receiver might wait for a control message containing a Track Alias to release flow control, while the sender waits for flow control to send the message.

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

All Objects received on a stream opened with SUBGROUP_HEADER have an Object Forwarding Preference = Subgroup.

There are 24 defined Type values for SUBGROUP_HEADER:

Table 6
Type Subgroup ID Subgroup ID Extensions Contains End Priority
  Field Present Value Present of Group Present
0x10 No 0 No No Yes
0x11 No 0 Yes No Yes
0x12 No First Object ID No No Yes
0x13 No First Object ID Yes No Yes
0x14 Yes N/A No No Yes
0x15 Yes N/A Yes No Yes
0x18 No 0 No Yes Yes
0x19 No 0 Yes Yes Yes
0x1A No First Object ID No Yes Yes
0x1B No First Object ID Yes Yes Yes
0x1C Yes N/A No Yes Yes
0x1D Yes N/A Yes Yes Yes
0x30 No 0 No No No
0x31 No 0 Yes No No
0x32 No First Object ID No No No
0x33 No First Object ID Yes No No
0x34 Yes N/A No No No
0x35 Yes N/A Yes No No
0x38 No 0 No Yes No
0x39 No 0 Yes Yes No
0x3A No First Object ID No Yes No
0x3B No First Object ID Yes Yes No
0x3C Yes N/A No Yes No
0x3D Yes N/A Yes Yes No

For Type values where Contains End of Group is Yes, the last Object in this Subgroup stream before a FIN is the last Object in the Group. If the Subgroup stream is terminated with a RESET_STREAM or RESET_STREAM_AT, the receiver cannot determine the End of Group Object ID.

For Type values where Subgroup ID Field Present is No, there is no explicit Subgroup ID field in the header and the Subgroup ID is either 0 (for Types 0x10-11 and 0x18-19) or the Object ID of the first object transmitted in this subgroup (for Types 0x12-13 and 0x1A-1B).

For Type values where Extensions Present is No, the Extensions field is never present and all Objects have no extensions. When Extensions Present is Yes, the Extensions structure defined in Section 10.2.1.2 is present in all Objects in this subgroup. Objects with no extensions set Extension Headers Length to 0.

For Type values where Priority Present is No, Priority is not present and this Subgroup inherits the Publisher Priority specified in the control message that established the subscription. When Priority Present is Yes, the Priority field is present in the Subgroup header.

To send an Object with Object Forwarding Preference = Subgroup, find the open stream that is associated with the subscription, Group ID and Subgroup ID, or open a new one and send the SUBGROUP_HEADER. Then serialize the following fields.

The Object Status field is only sent if the Object Payload Length is zero.

The Object ID Delta + 1 is added to the previous Object ID in the Subgroup stream if there was one. The Object ID is the Object ID Delta if it's the first Object in the Subgroup stream. For example, a Subgroup of sequential Object IDs starting at 0 will have 0 for all Object ID Delta values. A consumer cannot infer information about the existence of Objects between the current and previous Object ID in the Subgroup (e.g. when Object ID Delta is non-zero) unless there is an Prior Object ID Gap extesnion header (see Section 11.2).

{
  Object ID Delta (i),
  [Extensions (..),]
  Object Payload Length (i),
  [Object Status (i),]
  [Object Payload (..),]
}
Figure 28: MOQT Subgroup Object Fields

10.4.3. Closing Subgroup Streams

Subscribers will often need to know if they have received all objects in a Subgroup, particularly if they serve as a relay or cache. QUIC and Webtransport streams provide signals that can be used for this purpose. Closing Subgroups promptly frees system resources and often unlocks flow control credit to open more streams.

If a sender has delivered all objects in a Subgroup to the QUIC stream, except any Objects with Locations smaller than the subscription's Start Location, it MUST close the stream with a FIN.

If a sender closes the stream before delivering all such objects to the QUIC stream, it MUST use a RESET_STREAM or RESET_STREAM_AT [I-D.draft-ietf-quic-reliable-stream-reset] frame. This includes, but is not limited to:

  • An Object in an open Subgroup exceeding its Delivery Timeout

  • Early termination of subscription due to an UNSUBSCRIBE message

  • A publisher's decision to end the subscription early

  • A SUBSCRIBE_UPDATE moving the subscription's End Group to a smaller Group or the Start Location to a larger Location

  • Omitting a Subgroup Object due to the subcriber's Forward State

When RESET_STREAM_AT is used, the reliable_size SHOULD include the stream header so the receiver can identify the corresponding subscription and accurately account for reset data streams when handling PUBLISH_DONE (see Section 9.15). Publishers that reset data streams without using RESET_STREAM_AT with an appropriate reliable_size can cause subscribers to hold on to subscription state until a timeout expires.

A sender might send all objects in a Subgroup and the FIN on a QUIC stream, and then reset the stream. In this case, the receiving application would receive the FIN if and only if all objects were received. If the application receives all data on the stream and the FIN, it can ignore any RESET_STREAM it receives.

If a sender will not deliver any objects from a Subgroup, it MAY send a SUBGROUP_HEADER on a new stream, with no objects, and then send RESET_STREAM_AT with a reliable_size equal to the length of the stream header. This explicitly tells the receiver there is an unsent Subgroup.

A relay MUST NOT forward an Object on an existing Subgroup stream unless it is the next Object in that Subgroup. A relay determines that an Object is the next Object in the Subgroup if at least one of the following is true:

  • The Object ID is one greater than the previous Object sent on this Subgroup stream.

  • The Object was received on the same upstream Subgroup stream as the previously sent Object on the downstream Subgroup stream, with no other Objects in between.

  • It determined all Object IDs between the current and previous Object IDs on the Subgroup stream belong to different Subgroups or do not exist.

If the relay does not know if an Object is the next Object, it MUST reset the Subgroup stream and open a new one to forward it.

Since SUBSCRIBEs always end on a group boundary, an ending subscription can always cleanly close all its subgroups. A sender that terminates a stream early for any other reason (e.g., to handoff to a different sender) MUST use RESET_STREAM or RESET_STREAM_AT. Senders SHOULD terminate a stream on Group boundaries to avoid doing so.

An MOQT implementation that processes a stream FIN is assured it has received all objects in a subgroup from the start of the subscription. If a relay, it can forward stream FINs to its own subscribers once those objects have been sent. A relay MAY treat receipt of EndOfGroup or EndOfTrack objects as a signal to close corresponding streams even if the FIN has not arrived, as further objects on the stream would be a protocol violation.

Similarly, an EndOfGroup message indicates the maximum Object ID in the Group, so if all Objects in the Group have been received, a FIN can be sent on any stream where the entire subgroup has been sent. This might be complex to implement.

Processing a RESET_STREAM or RESET_STREAM_AT means that there might be other objects in the Subgroup beyond the last one received. A relay might immediately reset the corresponding downstream stream, or it might attempt to recover the missing Objects in an effort to send all the Objects in the subgroups and the FIN. It also might send RESET_STREAM_AT with reliable_size set to the last Object it has, so as to reliably deliver the Objects it has while signaling that other Objects might exist.

A subscriber MAY send a QUIC STOP_SENDING frame for a subgroup stream if the Group or Subgroup is no longer of interest to it. The publisher SHOULD respond with RESET_STREAM or RESET_STREAM_AT. If RESET_STREAM_AT is sent, note that the receiver has indicated no interest in the objects, so setting a reliable_size beyond the stream header is of questionable utility.

RESET_STREAM and STOP_SENDING on SUBSCRIBE data streams have no impact on other Subgroups in the Group or the subscription, although applications might cancel all Subgroups in a Group at once.

The application SHOULD use a relevant error code in RESET_STREAM or RESET_STREAM_AT, as defined below:

INTERNAL_ERROR (0x0):

An implementation specific error.

CANCELLED (0x1):

The subscriber requested cancellation via UNSUBSCRIBE, FETCH_CANCEL or STOP_SENDING, or the publisher ended the subscription, in which case PUBLISH_DONE (Section 9.15) will have a more detailed status code.

DELIVERY_TIMEOUT (0x2):

The DELIVERY TIMEOUT Section 9.2.1.2 was exceeded for this stream.

SESSION_CLOSED (0x3):

The publisher session is being closed.

10.4.4. Fetch Header

When a stream begins with FETCH_HEADER, all objects on the stream belong to the track requested in the Fetch message identified by Request ID.

FETCH_HEADER {
  Type (i) = 0x5,
  Request ID (i),
}
Figure 29: MOQT FETCH_HEADER

Each Object sent on a FETCH stream after the FETCH_HEADER has the following format:

{
  Serialization Flags (8),
  [Group ID (i),]
  [Subgroup ID (i),]
  [Object ID (i),]
  [Publisher Priority (8),]
  [Extensions (..),]
  Object Payload Length (i),
  [Object Status (i),]
  [Object Payload (..),]
}
Figure 30: MOQT Fetch Object Fields

The Serialization Flags field defines the serialization of the Object.

The two least significant bits (LSBs) of the Serialization Flags form a two-bit field that defines the encoding of the Subgroup. To extract this value, the Subscriber performs a bitwise AND operation with the mask 0x03.

Table 7
Bitmask Result (Serialization Flags & 0x03) Meaning
0x00 Subgroup ID is zero
0x01 Subgroup ID is the prior Object's Subgroup ID
0x02 Subgroup ID is the prior Object's Subgroup ID plus one
0x03 The Subgroup ID field is present

The following table defines additional flags within the Serialization Flags field. Each flag is an independent boolean value, where a set bit (1) indicates the corresponding condition is true.

Table 8
Bitmask Condition if set Condition if not set (0)
0x04 Object ID field is present Object ID is the prior Object's ID plus one
0x08 Group ID field is present Group ID is the prior Object's Group ID
0x10 Priority field is present Priority is the prior Object's Priority
0x20 Extensions field is present Extensions field is not present
0x40 PROTOCOL_VIOLATION N/A
0x80 PROTOCOL_VIOLATION N/A

If the first Object in the FETCH response uses a flag that references fields in the prior Object, the Subscriber MUST close the session with a PROTOCOL_VIOLATION.

The Extensions structure is defined in Section 10.2.1.2.

The Object Status field is only present if the Object Payload Length is zero.

When encoding an Object with a Forwarding Preference of "Datagram" (see Section 10.2.1), the Publisher treats it as having a Subgroup ID equal to the Object ID.

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