MOQT uses a pair of unidirectional streams for creating the session and exchanging control messages. Each peer opens one control stream beginning with a SETUP message. Using a pair of unidirectional streams rather than a single bidirectional stream allows either peer to send data as soon as it is able. Depending on whether 0-RTT is available on the QUIC connection, either client or server might be able to send stream data first.
In addition to the control streams, this specification uses bidirectional streams
to carry requests. A request stream begins with one of these seven message types:
TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS. Bidirectional streams MUST NOT
begin with any other message type unless negotiated. If they do, the peer MUST
close the Session with a PROTOCOL_VIOLATION. Objects are sent on unidirectional
streams.
As such, a client can initiate a MOQT session, subscribe, and start publishing Objects all in parallel. When this is done before the handshake completes using 0-RTT, the security implications described in Section 3.3.1 apply.
Unidirectional streams containing Objects or bidirectional stream(s) beginning with a request message could arrive prior to the control streams, in which case the data SHOULD be buffered until both control streams arrive and setup is complete. If an implementation does not want to buffer or if the message type is not supported, it MAY reset such bidirectional streams before the session and control streams are established.
A control stream MUST NOT be closed at the underlying transport layer during the
session's lifetime. Doing so results in the session being closed as a
PROTOCOL_VIOLATION.
Prior to receiving the peer's SETUP message, it's unknown what extensions a peer will support. Message Parameters requiring negotiation SHOULD NOT be used prior to receiving the peer's SETUP message unless the application requires the extension or the endpoint knows the peer supports the extension. If an unsupported Message Parameter is used, the peer will be unable to process it and the session will be terminated. See Section 10.2.
3.3.1. 0-RTT
QUIC supports 0-RTT (Section 2.3 of [RFC8446]), but WebTransport over QUIC is not expected to use 0-RTT, because initializing a WebTransport session uses CONNECT, which is not a safe method. [RFC8470] describes the use of 0-RTT with HTTP in more detail. If 0-RTT is used with an existing or future version of WebTransport, the following would apply to it as well as QUIC.
MOQT Messages and Objects as defined in this draft are safe to replay in most circumstances.
-
TRACK_STATUS gets the Largest Object and Track Properties, but does not change the state of a Track or any Object in the Track.
-
SUBSCRIBE requests Objects be delivered, but does not change the Objects being requested.
-
PUBLISH initiates a Subscription. Objects can be immediately sent to the Subscriber. Processing the same Objects multiple times is idempotent, as the subscriber or relay can identify and discard duplicates based on the Group ID and Object ID.
-
SUBSCRIBE_NAMESPACE requests a list of namespaces and the establishment of new subscriptions, but does not change the available Namespaces, Tracks, or Objects contained within a Track.
-
PUBLISH_NAMESPACE requests that Subscriptions under the namespace be sent to that Publisher. If a Subscription was sent to the replaying endpoint, it would fail because the endpoint cannot complete the handshake.
Some potential side effects of replay are:
-
Publishing Objects that were previously published could cause those Objects to be distributed to active Subscriptions if the relays do not identify them as already having been published. This re-distribution could also make them available in cache again after they previously expired.
Replays could increase load on the MOQT network. For relay to client traffic, this is no worse than 0-RTT in HTTP/3, since the server is limited by the amplification factor until address validation. However, it could cause the relay to initiate new upstream Subscriptions. For a SUBSCRIBE_TRACKS request, sending that upstream could cause the Relay to receive a number of new Subscriptions on the replaying client's behalf.
Relays MAY defer initiating upstream subscriptions until the handshake is complete or reject 0-RTT entirely to mitigate resource exhaustion from replayed packets.
3.3.2. Graceful Request Stream Closure
A request stream is bidirectional and each direction is closed independently, either gracefully with a FIN or abruptly with RESET_STREAM.
A FIN only indicates that an endpoint will send no further messages in that
direction; it is not a request cancellation. An endpoint MUST NOT send a FIN on
a direction of a request stream until it has sent all required messages on that
direction for its request type. In particular, an endpoint sending a response to
a request MUST send the corresponding response message, and the publisher of an
Established subscription MUST send PUBLISH_DONE, before sending a FIN. A FIN
sent by the responder after its response and any subsequent messages for the
request signals that the request is complete; if it has not already done so, the
requester SHOULD then send a FIN on its direction, gracefully closing the stream.
An endpoint that receives a FIN before all required messages have arrived treats
the request as failed.
An endpoint SHOULD send a FIN promptly after a message when it has nothing further to send on that direction and will not need to respond to a future REQUEST_UPDATE. A requester, with the exception of the sender of PUBLISH, MAY FIN immediately after sending a message if it will not send a REQUEST_UPDATE.
3.3.3. Request Cancellation and Rejection
Once a request stream has been opened, the request MAY be cancelled by either endpoint. Senders cancel requests if the response is no longer of interest; Receivers cancel requests if they are unable to or choose not to respond. Implementations cancel a request by abruptly terminating any directions of the stream that are still open, using RESET_STREAM for a direction they are sending and STOP_SENDING for a direction they are receiving. An endpoint that has already sent a FIN on its sending direction and subsequently wishes to cancel sends STOP_SENDING on the receiving direction.
When an endpoint rejects a request without performing any application processing, it SHOULD send a REQUEST_ERROR and FIN the stream.
3.3.4. Stream Reset Error Codes
The application SHOULD use a relevant error code when resetting or sending STOP_SENDING on any stream.
- INTERNAL_ERROR (0x0):
-
An implementation specific error.
- CANCELLED (0x1):
-
The stream was cancelled by either endpoint. For Subscriptions, PUBLISH_DONE (Section 10.11) may have a more detailed status code.
- DELIVERY_TIMEOUT (0x2):
-
A delivery timeout (Section 8) was exceeded for this stream.
- SESSION_CLOSED (0x3):
-
The session is being closed.
- GOING_AWAY (0x4):
-
The endpoint is rejecting this request because it has sent or received a GOAWAY.
- TOO_FAR_BEHIND (0x5):
-
The corresponding subscription has exceeded the publisher's resource limits and is being terminated (see Section 8).
- UNKNOWN_OBJECT_STATUS (0x6):
-
In response to a FETCH, the publisher is unable to determine the status of the next Object in the requested range.
- EXPIRED_AUTH_TOKEN (0x7):
-
The authorization token for the request has expired.
- EXCESSIVE_LOAD (0x9):
-
The endpoint is overloaded and is resetting this stream.
- MALFORMED_TRACK (0x12):
-
A relay publisher detected that the track was malformed (see Section 2.4.2).