moqtap_client/draft23/connection.rs
1use std::collections::VecDeque;
2use std::sync::Mutex;
3
4use bytes::{Buf, Bytes, BytesMut};
5
6use crate::draft23::endpoint::{Endpoint, EndpointError, FetchExistence, TrackExistence};
7use crate::draft23::event::{ClientEvent, Direction, StreamKind};
8use crate::draft23::fill::LocationFilter;
9use crate::draft23::observer::ConnectionObserver;
10use crate::draft23::session::request_id::Role;
11use crate::draft23::session::setup;
12use crate::malformed_tracks::MalformedTrackCondition;
13use crate::track_locations::{ObjectLocation, ObjectRole, TrackObjects};
14use crate::transport::{RecvStream, SendStream, Transport, TransportError};
15use moqtap_codec::dispatch::{
16 AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
17};
18use moqtap_codec::draft23::data_stream::{
19 FetchHeader, FetchObject, FetchObjectHeader, FetchObjectReader, GroupOrder, SubgroupObject,
20 SubgroupObjectReader,
21};
22use moqtap_codec::draft23::error_codes::{SessionErrorCode, StreamResetErrorCode};
23use moqtap_codec::draft23::message::{
24 max_control_message_size, ControlMessage, MessageType, Namespace, NamespaceDone,
25 PublishSkipped, RequestError, RequestOk, SubscribeOk, DEFAULT_MAX_CONTROL_MESSAGE_SIZE,
26};
27use moqtap_codec::error::CodecError;
28use moqtap_codec::kvp::KeyValuePair;
29use moqtap_codec::types::*;
30use moqtap_codec::varint::VarInt;
31use moqtap_codec::version::DraftVersion;
32
33/// The ALPN identifier draft-23 uses on raw QUIC, `moqt-23`.
34///
35/// The version is settled by the TLS handshake before a byte of MoQT is
36/// written.
37///
38/// This is [`DraftVersion::Draft23`]'s own
39/// [`quic_alpn`](DraftVersion::quic_alpn), which is what
40/// [`ClientConfig::alpn`] offers; the test below holds the two together.
41pub const MOQT_ALPN: &[u8] = b"moqt-23";
42
43/// The unidirectional stream type that marks one direction of the control
44/// plane, and the SETUP message type. On draft-23 they are one number,
45/// 0x2F00: Section 6.4.1 (Unidirectional Stream Types) lists it as the type of
46/// a SETUP stream, and Section 9.1 gives it as the SETUP message's own type
47/// field.
48///
49/// Because they are the same number a control stream carries no separate
50/// stream header. The varint a reader uses to recognise the stream is the
51/// first field of the SETUP message it then decodes, and a writer that
52/// encodes a SETUP onto a fresh unidirectional stream has already written the
53/// stream type by writing the message.
54///
55/// Encoded with MoQT's variable-length integer, whose width is the number of
56/// leading 1 bits in the first byte, 0x2F00 is the two bytes `AF 00` — four
57/// under RFC 9000's encoding, which this draft does not use. Read it through
58/// [`DraftVersion::decode_varint`] rather than assuming a width.
59pub const CONTROL_STREAM_TYPE: u64 = 0x2F00;
60
61/// The unidirectional stream type of FETCH_HEADER, 0x05 (draft-23 Section 6.4.1).
62///
63/// A fill fetch stream opens with it too: draft-23 Section 3.4 has a
64/// subscription carrying FILL_PARAMETERS open "a unidirectional stream
65/// beginning with a FETCH_HEADER".
66const FETCH_STREAM_TYPE: u64 = 0x05;
67
68/// The application error code a request stream is reset with when the
69/// requester abandons it: `CANCELLED`, 0x1.
70///
71/// Draft-23 has no UNSUBSCRIBE or FETCH_CANCEL. Cancelling a request is
72/// resetting the bidirectional stream it was made on, and `CANCELLED` is the
73/// code draft-23 Section 12.5 assigns for "The stream was cancelled by either
74/// endpoint" — see [`StreamResetErrorCode::Cancelled`].
75/// Taken from the codec's own registry rather than written as a literal so a
76/// renumbering in a later draft cannot be missed here.
77///
78/// This is what [`RequestStream::cancel`] uses when no code is chosen for it,
79/// and what `RequestStream`'s [`Drop`] sends.
80pub const REQUEST_CANCELLED: u64 = StreamResetErrorCode::Cancelled as u64;
81
82/// The application error code a request stream **the peer opened** is reset
83/// with when this endpoint abandons it: `INTERNAL_ERROR`, 0x0.
84///
85/// Dropping an inbound request is not the act [`REQUEST_CANCELLED`] describes.
86/// Draft-23 Section 6.4.2.3 grants a responder a cancel — "Receivers cancel
87/// requests if they are unable to or choose not to respond" — but a handle
88/// that fell out of scope chose nothing; it failed to serve, which is what
89/// [`StreamResetErrorCode::InternalError`], Section 12.5's "An implementation
90/// specific error", names. The two codes are on the wire, so a peer counting refusals
91/// can tell a deliberate rejection from a dropped request only if they differ.
92///
93/// It is also what a responder that already answered is reset with when it is
94/// dropped without finishing. A FIN there would claim the request completed,
95/// and Section 6.4.2.2 says when it has not: "the publisher of an Established
96/// subscription MUST send PUBLISH_DONE, before sending a FIN."
97pub const REQUEST_UNANSWERED: u64 = StreamResetErrorCode::InternalError as u64;
98
99/// Errors from the connection layer.
100#[derive(Debug, thiserror::Error)]
101pub enum ConnectionError {
102 /// Endpoint state machine error.
103 #[error("endpoint error: {0}")]
104 Endpoint(#[from] EndpointError),
105 /// Wire codec error.
106 #[error("codec error: {0}")]
107 Codec(#[from] CodecError),
108 /// Transport-level error.
109 #[error("transport error: {0}")]
110 Transport(#[from] TransportError),
111 /// Variable-length integer decoding error.
112 #[error("varint error: {0}")]
113 VarInt(#[from] moqtap_codec::varint::VarIntError),
114 /// Control stream was not opened.
115 #[error("control stream not open")]
116 NoControlStream,
117 /// Stream ended before a complete message was read.
118 #[error("unexpected end of stream")]
119 UnexpectedEnd,
120 /// A data stream ended with a FIN inside an Object.
121 ///
122 /// draft-23 Section 11.3: "If a stream ends gracefully (i.e., the stream
123 /// terminates with a FIN) in the middle of a serialized Object, the
124 /// receiver SHOULD close the session with TRUNCATED_OBJECT." A FIN between
125 /// two Objects is [`ConnectionError::UnexpectedEnd`].
126 #[error("the data stream ended with a FIN in the middle of an Object")]
127 TruncatedObject,
128 /// Stream was finished by the peer.
129 #[error("stream finished")]
130 StreamFinished,
131 /// Invalid server address string.
132 #[error("invalid server address: {0}")]
133 InvalidAddress(String),
134 /// TLS configuration error.
135 #[error("TLS config error: {0}")]
136 TlsConfig(String),
137 /// Data stream used out of order (e.g. object before header).
138 #[error("data stream state error: {0}")]
139 DataStreamState(&'static str),
140 /// A control message this build decoded for draft-23 and then could not
141 /// narrow to draft-23's own message type.
142 ///
143 /// Unreachable, and that is not the same as harmless. `read_control`
144 /// decodes with this connection's own draft, so the `AnyControlMessage` it
145 /// hands back can only carry this draft's variant — but the narrowing arm
146 /// is compiled in every configuration anyway, under
147 /// `#[allow(unreachable_patterns)]` rather than a `cfg` naming the other
148 /// drafts, because such a list has to be edited in every per-draft
149 /// module whenever a draft is added and a copy that omits one leaves the
150 /// match non-exhaustive.
151 ///
152 /// Spelled as `CodecError::UnknownMessageType(0)` it would not stay inert:
153 /// every draft's
154 /// [`codec_session_error_code`](Connection::codec_session_error_code)
155 /// answers that variant with a session close. So the day the
156 /// narrowing did fail, this build's own defect would reach a caller as *the
157 /// peer sent a control message type this draft does not assign, and the
158 /// session must be closed with a Protocol Violation* — carrying `0x00` as
159 /// the codepoint that proved it. A conformance report reading that
160 /// publishes a named, well-evidenced accusation against a relay for
161 /// something no relay did.
162 ///
163 /// A variant of its own is what stops that.
164 /// [`draft_specific_cause`](Connection::draft_specific_cause) answers it
165 /// [`LocalRefusal`], the facade turns that into [`ErrorCause::Facade`], and
166 /// nothing downstream can read a rule out of a cause that says nothing
167 /// reached the wire. What is pinned is the consequence rather than the
168 /// unreachability: nothing pins the arm's reachability, which is exactly
169 /// why the consequence must not be an accusation.
170 ///
171 /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
172 /// [`ErrorCause::Facade`]: crate::dispatch::ErrorCause::Facade
173 #[error(
174 "a control message decoded for draft-23 did not narrow to draft-23: a defect in this build, and evidence about nothing the peer did"
175 )]
176 ControlMessageNarrowing,
177 /// A message that begins a request stream was handed to
178 /// [`Connection::send_control`]. Nothing was written.
179 #[error(
180 "{0:?} begins a request stream of its own and must not be written on the control stream"
181 )]
182 RequestOnControlStream(MessageType),
183 /// A message draft-23 places on a request stream was handed to
184 /// [`Connection::send_control`]. Nothing was written.
185 ///
186 /// Distinct from [`ConnectionError::RequestOnControlStream`], which is about
187 /// a message that would *begin* a request stream of its own. This one is
188 /// about a message that belongs on a request stream already open, and so has
189 /// no meaning without one around it.
190 #[error("{0:?} belongs on a request stream, not on the control stream")]
191 RequestStreamMessageOnControlStream(MessageType),
192 /// The codec refused a datagram with [`CodecError::MalformedTrack`], for a
193 /// Prior Group ID Gap or Prior Object ID Gap fault (draft-23 Section 10.8,
194 /// draft-23 Section 10.9), or with [`CodecError::MalformedObjectProperties`],
195 /// for a property block that does not parse (draft-23 Section 10.7). Either
196 /// makes the datagram's track malformed.
197 ///
198 /// Carries the datagram's Track Alias, which the codec's error does not
199 /// name, so [`Connection::requests_to_cancel`] can name the requests to
200 /// cancel. The session stays open.
201 #[error("datagram for track alias {track_alias}: {error}")]
202 MalformedTrackDatagram {
203 /// The Track Alias the datagram named.
204 track_alias: u64,
205 /// The codec's refusal.
206 error: CodecError,
207 },
208 /// A datagram carrying an Object Status arrived with bytes after its
209 /// header.
210 /// Draft-23 Section 11.1.1: "An Object MUST have an empty payload unless
211 /// its Object Status value is registered as permitting a payload in the
212 /// Object Status registry" Section 11.2.1 says the same thing about the
213 /// framing: a datagram with the STATUS bit set "is present and there is no
214 /// Object Payload."
215 ///
216 /// The codec cannot see this. `DatagramHeader::decode` stops at the end of
217 /// the header and never owns the datagram's tail, so the only layer that
218 /// holds both the status and the bytes after it is this one. Without the
219 /// check the application is handed, say, an End-of-Group object carrying
220 /// four bytes of payload — a combination the draft forbids outright.
221 ///
222 /// Recoverable: the drafts state the rule as a property of a conforming
223 /// object, not as one of the "MUST close the session" cases, so the datagram
224 /// is refused and the session left running.
225 #[error(
226 "datagram for object {object_id} carries {payload_len} bytes after a header \
227 whose status is {status:?}, which permits no payload"
228 )]
229 PayloadOnStatusDatagram {
230 /// Object ID from the datagram header.
231 object_id: u64,
232 /// How many bytes followed the header.
233 payload_len: usize,
234 /// The status the header declared.
235 ///
236 /// Spelled out in full because the glob import of `moqtap_codec::types`
237 /// brings a different `ObjectStatus` into this module.
238 status: Option<moqtap_codec::draft23::types::ObjectStatus>,
239 },
240 /// An Object arrived carrying properties on a status that is not Normal.
241 ///
242 /// Draft-23 Section 11.1.2: "Any Object with status Normal can have
243 /// properties (Section 8.4). If an endpoint receives properties on an Object with status that
244 /// is not Normal, it MUST close the session with INVALID_OBJECT_STATUS."
245 ///
246 /// The codec's `read_object` refuses such an Object as a bare
247 /// `InvalidField`. Its `read_object_meta` frames it, which is how the
248 /// receive path names the rule here and stays in step with the wire.
249 #[error(
250 "object {object_id} carries {properties_len} bytes of properties on status {status:?}, \
251 which is not Normal"
252 )]
253 PropertiesOnNonNormalStatus {
254 /// The Object ID the properties arrived on.
255 object_id: u64,
256 /// Length in bytes of the properties block.
257 properties_len: usize,
258 /// The Object's status, resolved through the encoding's elision rule.
259 ///
260 /// Spelled out in full because the glob import of `moqtap_codec::types`
261 /// brings a different `ObjectStatus` into this module.
262 status: moqtap_codec::draft23::types::ObjectStatus,
263 },
264 /// A bidirectional stream the peer opened began with a message type that
265 /// does not open a request stream.
266 ///
267 /// Draft-23 Section 6.3: "Bidirectional streams MUST NOT begin with any
268 /// other message type unless negotiated. If they do, the peer MUST close the Session with
269 /// UNEXPECTED_MESSAGE." The session has already been
270 /// closed on the wire by the time this is returned, and the offending
271 /// stream reset.
272 #[error(
273 "a bidirectional stream the peer opened began with {0:?}, which does not begin a request stream; the session was closed"
274 )]
275 NonRequestOnRequestStream(MessageType),
276 /// A `respond_*` helper was handed a request stream this endpoint opened.
277 /// Nothing was written and no state moved.
278 #[error(
279 "this endpoint opened request {0}; only the endpoint a request stream was opened toward may answer it"
280 )]
281 RespondedToOwnRequest(u64),
282 /// [`RequestStream::finish`] was called on a request stream the peer opened
283 /// before a response had been written on it. Nothing was written.
284 ///
285 /// Draft-23 Section 6.4.2.2: "An endpoint MUST NOT send a FIN on a direction
286 /// of a request stream until it has sent all required messages on that
287 /// direction for its request type. In particular, an endpoint sending a
288 /// response to a request MUST send the corresponding response message ...
289 /// before sending a FIN."
290 #[error(
291 "request {0} has not been answered; draft-23 Section 6.4.2.2 forbids finishing a request stream before its response"
292 )]
293 FinBeforeResponse(u64),
294 /// [`RequestStream::finish`] was called on a subscription this endpoint is
295 /// the publisher of, before PUBLISH_DONE. Nothing was written.
296 ///
297 /// The second half of the same sentence in draft-23 Section 6.4.2.2: "the
298 /// publisher of an Established subscription MUST send PUBLISH_DONE, before
299 /// sending a FIN." It applies to the accepted SUBSCRIBE a responder
300 /// answered with SUBSCRIBE_OK and to the PUBLISH this endpoint sent, which
301 /// is why [`Connection::publish_done`] and
302 /// [`Connection::publish_done_on`] are the only routes that clear it.
303 #[error(
304 "request {0} is an established subscription; draft-23 Section 6.4.2.2 requires PUBLISH_DONE before a FIN"
305 )]
306 FinBeforePublishDone(u64),
307}
308
309impl From<crate::transport::DialError> for ConnectionError {
310 /// Maps a dial failure onto the variants this error already has, so a
311 /// caller matches `InvalidAddress` or `TlsConfig`.
312 ///
313 /// A `LocalSocket` failure maps to `InvalidAddress`, which the facade reads
314 /// as this side's failure
315 /// ([`ErrorCause::Facade`](crate::dispatch::ErrorCause::Facade)). The dial
316 /// phase stays on [`DialError::phase`](crate::transport::DialError::phase).
317 fn from(e: crate::transport::DialError) -> Self {
318 match e {
319 // Two variants, one arm, deliberately — see above.
320 crate::transport::DialError::InvalidAddress(s)
321 | crate::transport::DialError::LocalSocket(s) => ConnectionError::InvalidAddress(s),
322 crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
323 crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
324 }
325 }
326}
327
328/// Transport type for the connection.
329#[derive(Debug, Clone)]
330pub enum TransportType {
331 /// Raw QUIC via quinn. The `addr` field should be `host:port`.
332 Quic,
333 /// WebTransport via wtransport. The `url` field is the WebTransport URL.
334 WebTransport {
335 /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
336 url: String,
337 },
338}
339
340/// Configuration for a MoQT client connection.
341///
342/// Both `draft` and `transport` are required -- there is no `Default` impl.
343pub struct ClientConfig {
344 /// The MoQT draft version to use (primary, determines codec/framing).
345 pub draft: DraftVersion,
346 /// The transport type (QUIC or WebTransport).
347 pub transport: TransportType,
348 /// Whether to skip TLS certificate verification (for testing).
349 pub skip_cert_verification: bool,
350 /// Custom CA certificates to trust (DER-encoded).
351 pub ca_certs: Vec<Vec<u8>>,
352 /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
353 pub setup_parameters: Vec<KeyValuePair>,
354}
355
356impl ClientConfig {
357 /// Returns the ALPN protocol identifiers for the transport.
358 pub fn alpn(&self) -> Vec<Vec<u8>> {
359 match &self.transport {
360 TransportType::Quic => vec![self.draft.quic_alpn().to_vec()],
361 TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
362 }
363 }
364}
365
366/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
367pub struct FramedSendStream {
368 inner: SendStream,
369 draft: DraftVersion,
370 /// Stateful subgroup object writer.
371 subgroup_io: Option<SubgroupObjectReader>,
372}
373
374impl FramedSendStream {
375 /// Create a new framed send stream for the given draft version.
376 pub fn new(inner: SendStream, draft: DraftVersion) -> Self {
377 Self { inner, draft, subgroup_io: None }
378 }
379
380 /// Get the transport-level stream ID.
381 pub fn stream_id(&self) -> u64 {
382 self.inner.stream_id()
383 }
384
385 /// Write a control message to the stream with type+length framing.
386 /// Returns the raw bytes that were written (for event capture).
387 ///
388 /// Encodes under the default MAX_CONTROL_MESSAGE_SIZE of 65535; see
389 /// [`Self::write_control_with_limit`].
390 pub async fn write_control(
391 &mut self,
392 msg: &AnyControlMessage,
393 ) -> Result<Vec<u8>, ConnectionError> {
394 let mut buf = Vec::new();
395 msg.encode(&mut buf)?;
396 self.inner.write_all(&buf).await?;
397 Ok(buf)
398 }
399
400 /// [`Self::write_control`] for a peer whose MAX_CONTROL_MESSAGE_SIZE is
401 /// `limit`.
402 ///
403 /// draft-23 Section 9: "An endpoint MUST NOT send a control message with a
404 /// length larger than the peer's MAX_CONTROL_MESSAGE_SIZE (Section
405 /// 9.1.8)." A longer message is refused with
406 /// [`CodecError::ControlMessageExceedsLimit`] and nothing is written.
407 pub async fn write_control_with_limit(
408 &mut self,
409 msg: &ControlMessage,
410 limit: u64,
411 ) -> Result<Vec<u8>, ConnectionError> {
412 let mut buf = Vec::new();
413 msg.encode_with_limit(&mut buf, limit)?;
414 self.inner.write_all(&buf).await?;
415 Ok(buf)
416 }
417
418 /// Write a subgroup stream header. Also initializes the internal
419 /// delta-encoding state used by
420 /// [`FramedSendStream::write_subgroup_object`].
421 ///
422 /// The header is refused, and nothing is written, if its fields disagree
423 /// with its own stream type. That check has to happen here rather than at
424 /// the first object: the type is what every object after it is framed
425 /// against, so a header that went out saying the wrong thing cannot be
426 /// taken back.
427 pub async fn write_subgroup_header(
428 &mut self,
429 header: &AnySubgroupHeader,
430 ) -> Result<(), ConnectionError> {
431 let mut buf = Vec::new();
432 header.encode_stream_checked(&mut buf)?;
433 self.inner.write_all(&buf).await?;
434 // Clippy would rather see these two arms as an `if let`, and rustc rejects
435 // that in a single-draft build, where the pattern is irrefutable. Only a
436 // `match` satisfies both.
437 #[allow(clippy::single_match)]
438 match header {
439 AnySubgroupHeader::Draft23(ref header) => {
440 self.subgroup_io = Some(SubgroupObjectReader::new(header));
441 }
442 // Only this draft's header seeds the object reader. With draft 23 the only enabled
443 // draft `AnySubgroupHeader` has a single variant, the arm above is exhaustive and this
444 // one unreachable. Compiled in every configuration with the lint allowed, rather than
445 // gated on a `cfg` naming the other drafts: such a list has to be edited in
446 // every draft module whenever a draft is added, and a copy that omits one leaves this
447 // match non-exhaustive.
448 #[allow(unreachable_patterns)]
449 _ => {}
450 }
451 Ok(())
452 }
453
454 /// Write a fetch response header.
455 pub async fn write_fetch_header(
456 &mut self,
457 header: &AnyFetchHeader,
458 ) -> Result<(), ConnectionError> {
459 let mut buf = Vec::new();
460 header.encode_stream(&mut buf);
461 self.inner.write_all(&buf).await?;
462 Ok(())
463 }
464
465 /// Append a draft-23 subgroup object to the stream using the
466 /// stateful writer seeded from
467 /// [`FramedSendStream::write_subgroup_header`].
468 pub async fn write_subgroup_object(
469 &mut self,
470 object: &SubgroupObject,
471 ) -> Result<(), ConnectionError> {
472 let writer = self
473 .subgroup_io
474 .as_mut()
475 .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
476 let mut buf = Vec::new();
477 writer.write_object(object, &mut buf)?;
478 self.inner.write_all(&buf).await?;
479 Ok(())
480 }
481
482 /// Append a fetch object to the stream.
483 ///
484 /// The fetch stream had a header writer and no object writer, so a caller
485 /// could open one and put nothing on it through this type. The subgroup
486 /// stream has had both since the writer was introduced.
487 ///
488 /// The declared length comes from the payload rather than from the caller's
489 /// field: a header that disagrees with the bytes beside it desynchronises
490 /// every object after it on the stream, and nothing downstream can recover.
491 ///
492 /// # Errors
493 ///
494 /// [`ConnectionError::Codec`] if the header's fields disagree with the
495 /// Serialization Flags that announce them, which the encoder refuses rather
496 /// than writing a frame its own reader cannot take apart.
497 pub async fn write_fetch_object(
498 &mut self,
499 header: &FetchObjectHeader,
500 payload: &[u8],
501 ) -> Result<(), ConnectionError> {
502 let mut header = header.clone();
503 header.payload_length = VarInt::from_usize(payload.len());
504 let mut buf = Vec::new();
505 header.encode(&mut buf)?;
506 buf.extend_from_slice(payload);
507 self.inner.write_all(&buf).await?;
508 Ok(())
509 }
510
511 /// Finish the stream (send FIN).
512 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
513 self.inner.finish()?;
514 Ok(())
515 }
516
517 /// Reset the stream with `code`, telling the peer transmission was
518 /// abandoned rather than completed.
519 ///
520 /// Dropping a send stream sends a FIN, which claims the stream ended
521 /// cleanly; this is the only way to say the opposite. See
522 /// [`SendStream::reset`].
523 pub fn reset(&mut self, code: u64) -> Result<(), ConnectionError> {
524 self.inner.reset(code)?;
525 Ok(())
526 }
527
528 /// Returns the draft version this stream is framed for.
529 pub fn draft(&self) -> DraftVersion {
530 self.draft
531 }
532}
533
534/// What an Object Status makes of an object here.
535///
536/// The end-of-track status settles where the track ended and is judged
537/// against nothing: draft-23 states no rule about where one may be placed. `a_track_may_end_where_it_has_already_been.rs` asserts
538/// that acceptance.
539///
540/// Every other status is a statement about objects rather than one of them.
541fn object_role(status: Option<u64>) -> ObjectRole {
542 match status {
543 None | Some(0x0) => ObjectRole::Produced,
544 Some(0x4) => ObjectRole::EndsTrack(None),
545 _ => ObjectRole::Neither,
546 }
547}
548
549/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
550pub struct FramedRecvStream {
551 inner: RecvStream,
552 buf: BytesMut,
553 draft: DraftVersion,
554 /// Stateful subgroup object reader.
555 subgroup_io: Option<SubgroupObjectReader>,
556 /// Stateful fetch object reader, started by
557 /// [`FramedRecvStream::begin_fetch_objects`] rather than by the fetch
558 /// header, which does not carry the order.
559 fetch_io: Option<FetchObjectReader>,
560 /// The record this stream's objects are measured against, and the Group ID
561 /// its header named.
562 ///
563 /// One group for the whole stream: a subgroup header names it once and no
564 /// object header repeats it. `None` on a stream that was never given one -
565 /// a stream for an alias no live binding names, and every stream built
566 /// outside [`Connection::accept_subgroup_stream`].
567 tracking: Option<(TrackObjects, TrackExistence, u64)>,
568 /// What this FETCH stream's entries are judged against, when it answers a
569 /// FETCH this endpoint sent.
570 fetch_existence: Option<FetchExistence>,
571 /// The bytes of the last control frame a capturing read refused.
572 refused: Option<Vec<u8>>,
573}
574
575impl FramedRecvStream {
576 /// Create a new framed receive stream for the given draft version.
577 pub fn new(inner: RecvStream, draft: DraftVersion) -> Self {
578 Self {
579 inner,
580 buf: BytesMut::with_capacity(4096),
581 draft,
582 subgroup_io: None,
583 fetch_io: None,
584 tracking: None,
585 fetch_existence: None,
586 refused: None,
587 }
588 }
589
590 /// Get the transport-level stream ID.
591 pub fn stream_id(&self) -> u64 {
592 self.inner.stream_id()
593 }
594
595 /// The bytes of the control frame the last capturing read refused.
596 pub(crate) fn take_refused(&mut self) -> Option<Vec<u8>> {
597 self.refused.take()
598 }
599
600 /// Keep the first `len` buffered bytes as a refused frame when the read is
601 /// capturing, and hand `err` back.
602 fn refuse(&mut self, capture_raw: bool, len: usize, err: ConnectionError) -> ConnectionError {
603 if capture_raw {
604 self.refused = Some(self.buf[..len].to_vec());
605 }
606 err
607 }
608
609 /// Measure this stream's objects against `objects`, all of them in `group`.
610 fn measure_objects_against(
611 &mut self,
612 objects: TrackObjects,
613 existence: TrackExistence,
614 group: u64,
615 ) {
616 self.tracking = Some((objects, existence, group));
617 }
618
619 /// Judge one object this stream carried against where its track ended.
620 fn note_subgroup_object(
621 &self,
622 object: u64,
623 status: Option<u64>,
624 properties: &[u8],
625 ) -> Result<(), ConnectionError> {
626 let Some((objects, existence, group)) = &self.tracking else { return Ok(()) };
627 let at = ObjectLocation { group: *group, object };
628 let role = object_role(status);
629 objects.note_past_final(at, role).map_err(|end| {
630 ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject {
631 alias: objects.alias(),
632 group: at.group,
633 object: at.object,
634 final_group: end.group,
635 final_object: end.object,
636 })
637 })?;
638 crate::draft23::endpoint::note_existence(existence, at, role, properties)
639 .map_err(ConnectionError::Endpoint)
640 }
641
642 /// What a FIN met while an Object was being read means: a clean end when
643 /// no byte of the next Object has arrived, a truncation otherwise.
644 fn ended_between_objects(&self) -> ConnectionError {
645 if self.buf.is_empty() {
646 ConnectionError::UnexpectedEnd
647 } else {
648 ConnectionError::TruncatedObject
649 }
650 }
651
652 /// Read more data from the stream into the internal buffer.
653 async fn fill(&mut self) -> Result<bool, ConnectionError> {
654 let mut tmp = [0u8; 4096];
655 match self.inner.read(&mut tmp).await {
656 Ok(Some(n)) => {
657 self.buf.extend_from_slice(&tmp[..n]);
658 Ok(true)
659 }
660 Ok(None) => Ok(false),
661 Err(e) => Err(ConnectionError::Transport(e)),
662 }
663 }
664
665 /// Ensure at least `n` bytes are available in the buffer.
666 async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
667 while self.buf.len() < n {
668 if !self.fill().await? {
669 return Err(ConnectionError::UnexpectedEnd);
670 }
671 }
672 Ok(())
673 }
674
675 /// Read this stream's leading variable-length integer **without
676 /// consuming it**, and return its value.
677 ///
678 /// Every unidirectional MoQT stream on draft-23 opens with a varint
679 /// naming what it is (Section 6.4.1): 0x05 for FETCH_HEADER, 0x10-0x1D for
680 /// SUBGROUP_HEADER, and [`CONTROL_STREAM_TYPE`] for SETUP. Telling the
681 /// peer's control stream apart from a data stream means reading that
682 /// varint, and taking it off the transport would destroy it: the control
683 /// stream's type varint *is* the SETUP message's type field, so a stream
684 /// whose type had been stripped would no longer decode as a SETUP.
685 ///
686 /// Nothing is stripped. The bytes land in this reader's own buffer, and
687 /// every other method here — [`read_control`](Self::read_control),
688 /// [`read_subgroup_header`](Self::read_subgroup_header),
689 /// [`read_fetch_header`](Self::read_fetch_header) — decodes out of that
690 /// buffer and advances it only on a successful decode. A stream this was
691 /// called on is indistinguishable from one it was not, which is what
692 /// makes it safe to peek a stream and then hand it to whichever reader
693 /// the type turned out to call for.
694 ///
695 /// It reads, so it can block: a peer that opens a stream and then writes
696 /// nothing leaves this pending until a byte arrives or the stream ends.
697 ///
698 /// # Errors
699 ///
700 /// - [`ConnectionError::UnexpectedEnd`] if the stream ends before a whole
701 /// varint has arrived.
702 /// - [`ConnectionError::Transport`] if the peer reset the stream.
703 /// - [`ConnectionError::VarInt`] if the bytes are not a valid varint.
704 ///
705 /// Whatever did arrive stays in the buffer in every case.
706 pub async fn peek_stream_type(&mut self) -> Result<u64, ConnectionError> {
707 self.ensure(1).await?;
708 let type_len = self.draft.varint_len(self.buf[0]);
709 self.ensure(type_len).await?;
710 let mut cursor = &self.buf[..type_len];
711 Ok(self.draft.decode_varint(&mut cursor)?.into_inner())
712 }
713
714 /// Read a control message from the stream.
715 ///
716 /// When `capture_raw` is true, the returned tuple includes a clone of the
717 /// framed wire bytes (for observer emission). When false, the second
718 /// element is `None` and the payload clone is skipped.
719 pub async fn read_control(
720 &mut self,
721 capture_raw: bool,
722 ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
723 self.read_control_with_limit(capture_raw, DEFAULT_MAX_CONTROL_MESSAGE_SIZE).await
724 }
725
726 /// [`read_control`](Self::read_control) under the MAX_CONTROL_MESSAGE_SIZE
727 /// this endpoint advertised.
728 ///
729 /// Draft-23 Section 9: "If an endpoint receives a control message with a
730 /// length larger than its MAX_CONTROL_MESSAGE_SIZE, it MUST close the
731 /// session with FIELD_LENGTH_EXCEEDED." The Length is checked before the
732 /// body is buffered, so an oversized message costs no memory.
733 pub async fn read_control_with_limit(
734 &mut self,
735 capture_raw: bool,
736 limit: u64,
737 ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
738 self.refused = None;
739 // Read type ID varint
740 self.ensure(1).await?;
741 let type_len = self.draft.varint_len(self.buf[0]);
742 self.ensure(type_len).await?;
743
744 let mut cursor = &self.buf[..type_len];
745 if let Err(e) = self.draft.decode_varint(&mut cursor) {
746 return Err(self.refuse(capture_raw, type_len, e.into()));
747 }
748
749 // Draft-23 Section 9: "Message Length (vi64)".
750 let (payload_len, len_field_size) = if self.draft.uses_fixed_length_framing() {
751 self.ensure(type_len + 2).await?;
752 let hi = self.buf[type_len] as usize;
753 let lo = self.buf[type_len + 1] as usize;
754 ((hi << 8) | lo, 2)
755 } else {
756 self.ensure(type_len + 1).await?;
757 let payload_len_start = type_len;
758 let payload_len_varint_len = self.draft.varint_len(self.buf[payload_len_start]);
759 self.ensure(type_len + payload_len_varint_len).await?;
760 let header_len = type_len + payload_len_varint_len;
761 let mut cursor = &self.buf[payload_len_start..header_len];
762 let length = match self.draft.decode_varint(&mut cursor) {
763 Ok(length) => length.into_inner(),
764 Err(e) => return Err(self.refuse(capture_raw, header_len, e.into())),
765 };
766 if length > limit {
767 // Only the type and the length are buffered, and they are what
768 // the refusal is about.
769 let err = ConnectionError::Codec(CodecError::ControlMessageExceedsLimit {
770 length,
771 limit,
772 });
773 return Err(self.refuse(capture_raw, header_len, err));
774 }
775 (length as usize, payload_len_varint_len)
776 };
777
778 // Read full payload
779 let total = type_len + len_field_size + payload_len;
780 self.ensure(total).await?;
781
782 // Capture raw bytes only if requested (observer attached).
783 let raw = capture_raw.then(|| self.buf[..total].to_vec());
784
785 // Now decode the whole message
786 let mut frame = &self.buf[..total];
787 let decoded = if self.draft == DraftVersion::Draft23 {
788 ControlMessage::decode_with_limit(&mut frame, limit).map(AnyControlMessage::Draft23)
789 } else {
790 AnyControlMessage::decode(self.draft, &mut frame)
791 };
792 let msg = match decoded {
793 Ok(msg) => msg,
794 Err(e) => return Err(self.refuse(capture_raw, total, e.into())),
795 };
796 self.buf.advance(total);
797 Ok((msg, raw))
798 }
799
800 /// Read a subgroup stream header. Also initializes the internal
801 /// delta-decoding state.
802 pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
803 self.ensure(1).await?;
804 loop {
805 let mut cursor = &self.buf[..];
806 match AnySubgroupHeader::decode(self.draft, &mut cursor) {
807 Ok(header) => {
808 let consumed = self.buf.len() - cursor.remaining();
809 self.buf.advance(consumed);
810 // Clippy would rather see these two arms as an `if let`, and rustc rejects
811 // that in a single-draft build, where the pattern is irrefutable. Only a
812 // `match` satisfies both.
813 #[allow(clippy::single_match)]
814 match header {
815 AnySubgroupHeader::Draft23(ref header) => {
816 self.subgroup_io = Some(SubgroupObjectReader::new(header));
817 }
818 // Only this draft's header seeds the object reader. With draft 23 the only
819 // enabled draft `AnySubgroupHeader` has a single variant, the arm above is
820 // exhaustive and this one unreachable. Compiled in every configuration with
821 // the lint allowed, rather than gated on a `cfg` naming every other
822 // draft: such a list has to be edited in every draft module whenever a
823 // draft is added, and a copy that omits one leaves this match
824 // non-exhaustive.
825 #[allow(unreachable_patterns)]
826 _ => {}
827 }
828 return Ok(header);
829 }
830 Err(e) if e.is_incomplete() => {
831 if !self.fill().await? {
832 return Err(ConnectionError::UnexpectedEnd);
833 }
834 }
835 Err(e) => return Err(ConnectionError::Codec(e)),
836 }
837 }
838 }
839
840 /// Read a fetch response header.
841 pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
842 self.ensure(1).await?;
843 loop {
844 let mut cursor = &self.buf[..];
845 match AnyFetchHeader::decode(self.draft, &mut cursor) {
846 Ok(header) => {
847 let consumed = self.buf.len() - cursor.remaining();
848 self.buf.advance(consumed);
849 return Ok(header);
850 }
851 Err(e) if e.is_incomplete() => {
852 if !self.fill().await? {
853 return Err(ConnectionError::UnexpectedEnd);
854 }
855 }
856 Err(e) => return Err(ConnectionError::Codec(e)),
857 }
858 }
859 }
860
861 /// Read the next draft-23 subgroup object from this stream using
862 /// the stateful reader seeded by
863 /// [`FramedRecvStream::read_subgroup_header`].
864 ///
865 /// Errors with [`ConnectionError::PropertiesOnNonNormalStatus`] on an
866 /// Object that carries properties on a status other than Normal, which
867 /// draft-23 Section 11.1.2 answers with a session close. The object is
868 /// consumed from the stream before the check, so the reader stays in step
869 /// with the wire and a caller that reports the violation and reads on sees
870 /// the following object rather than a re-parse of this one.
871 pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
872 if self.subgroup_io.is_none() {
873 return Err(ConnectionError::DataStreamState("subgroup header not read yet"));
874 }
875 loop {
876 let reader = self.subgroup_io.as_mut().unwrap();
877 let mut probe = reader.clone();
878 let mut cursor = &self.buf[..];
879 match probe.read_object(&mut cursor) {
880 Ok(obj) => {
881 let consumed = self.buf.len() - cursor.remaining();
882 self.buf.advance(consumed);
883 *reader = probe;
884 if !obj.properties_permitted() {
885 return Err(ConnectionError::PropertiesOnNonNormalStatus {
886 object_id: obj.object_id.into_inner(),
887 properties_len: obj.extension_headers.len(),
888 status: obj.status(),
889 });
890 }
891 self.note_subgroup_object(
892 obj.object_id.into_inner(),
893 obj.object_status.map(|s| s as u64),
894 &obj.extension_headers,
895 )?;
896 return Ok(obj);
897 }
898 Err(e) if e.is_incomplete() => {
899 if !self.fill().await? {
900 return Err(self.ended_between_objects());
901 }
902 }
903 // The codec refuses properties on a non-Normal status as
904 // `InvalidField`, one of many readings of that variant. Its
905 // framing-only reader accepts them, which tells this case apart
906 // from the others and keeps the stream in step.
907 Err(CodecError::InvalidField) => {
908 let mut framing = reader.clone();
909 let mut cursor = &self.buf[..];
910 if let Ok(meta) = framing.read_object_meta(&mut cursor) {
911 let status = meta
912 .status
913 .and_then(moqtap_codec::draft23::types::ObjectStatus::from_u64);
914 if let Some(status) = status.filter(|s| {
915 meta.extension_headers_len > 0
916 && *s != moqtap_codec::draft23::types::ObjectStatus::Normal
917 }) {
918 let consumed = self.buf.len() - cursor.remaining();
919 self.buf.advance(consumed);
920 *reader = framing;
921 return Err(ConnectionError::PropertiesOnNonNormalStatus {
922 object_id: meta.object_id,
923 properties_len: meta.extension_headers_len as usize,
924 status,
925 });
926 }
927 }
928 return Err(ConnectionError::Codec(CodecError::InvalidField));
929 }
930 Err(e) => return Err(ConnectionError::Codec(e)),
931 }
932 }
933 }
934
935 /// Read the next draft-23 fetch header from this stream.
936 pub async fn read_fetch_stream_header(&mut self) -> Result<FetchHeader, ConnectionError> {
937 loop {
938 let mut cursor = &self.buf[..];
939 match FetchHeader::decode(&mut cursor) {
940 Ok(hdr) => {
941 let consumed = self.buf.len() - cursor.remaining();
942 self.buf.advance(consumed);
943 return Ok(hdr);
944 }
945 Err(e) if e.is_incomplete() => {
946 if !self.fill().await? {
947 return Err(ConnectionError::UnexpectedEnd);
948 }
949 }
950 Err(e) => return Err(ConnectionError::Codec(e)),
951 }
952 }
953 }
954
955 /// Stop accepting data on this stream with `code` as the `STOP_SENDING`
956 /// application error code, discarding anything unread.
957 ///
958 /// Dropping a receive stream also stops it, but with a hard-coded 0. See
959 /// [`RecvStream::stop`].
960 pub fn stop(&mut self, code: u64) -> Result<(), ConnectionError> {
961 self.inner.stop(code)?;
962 Ok(())
963 }
964
965 /// Wait for the peer to reset this stream, consuming nothing.
966 ///
967 /// See [`RecvStream::received_reset`] for what `Ok(None)` means and why a
968 /// caller must not re-poll after it.
969 pub async fn received_reset(&mut self) -> Result<Option<u64>, ConnectionError> {
970 Ok(self.inner.received_reset().await?)
971 }
972
973 /// Start reading fetch objects on this stream, in a response whose Groups
974 /// arrive in `group_order`.
975 ///
976 /// Draft-23 has to be told. Section 11.4.1.1 makes a
977 /// Group ID Delta count downward under Descending and upward under
978 /// Ascending, so the same bytes are two different Locations and nothing on
979 /// the data stream says which. The order is the one the **request** asked
980 /// for — Section 3.2.1: "A publisher MUST send fetched groups in the
981 /// requested group order (see Section 9.19.8)." — carried by the
982 /// GROUP_ORDER parameter on the FETCH, or by its absence, which Section
983 /// 9.19.8 reads as Ascending. Either way it is a control message this
984 /// stream never sees.
985 ///
986 /// Call it after [`FramedRecvStream::read_fetch_header`] and before the
987 /// first [`FramedRecvStream::read_fetch_object`]; calling it again restarts
988 /// the running state, which is what a second fetch stream on the same
989 /// connection would want and what the middle of one would not.
990 pub fn begin_fetch_objects(&mut self, group_order: GroupOrder) {
991 self.fetch_io = Some(FetchObjectReader::new(group_order));
992 if let Some(existence) = &mut self.fetch_existence {
993 existence.begin(group_order);
994 }
995 }
996
997 /// Read the next draft-23 fetch object and its payload.
998 ///
999 /// The mirror of [`FramedSendStream::write_fetch_object`]. What comes back
1000 /// is a [`FetchObject`] rather than a header: draft-23's reader resolves the
1001 /// delta-encoded fields, so the Group ID, Subgroup ID, Object ID and
1002 /// Priority it carries are the frame's own values rather than differences
1003 /// from the frame before. `object.header` is the frame exactly as it
1004 /// arrived, for a caller forwarding the bytes on.
1005 ///
1006 /// The payload comes back with it for the reason the codec leaves it on the
1007 /// wire: `payload_length` says how many bytes follow, and a reader that
1008 /// takes the wrong number of them desynchronises every later object on the
1009 /// stream. Doing it here is the only place that count and the buffer are
1010 /// both in hand.
1011 ///
1012 /// # Errors
1013 ///
1014 /// [`ConnectionError::DataStreamState`] when
1015 /// [`FramedRecvStream::begin_fetch_objects`] has not been called,
1016 /// [`ConnectionError::UnexpectedEnd`] when the stream ends inside the header
1017 /// or inside the payload it declared, and [`ConnectionError::Codec`] on
1018 /// every rule Section 11.4.1.1 states — a first Object that inherits, a
1019 /// delta that runs off either end of the space, or an Object ID above
1020 /// 2^64-1.
1021 ///
1022 /// A Prior Group ID Gap or Prior Object ID Gap fault the stream shows is
1023 /// returned as [`CodecError::MalformedTrack`], and a property block that
1024 /// does not parse as [`CodecError::MalformedObjectProperties`];
1025 /// [`Connection::requests_to_cancel_for_fetch_stream`] names the requests to
1026 /// cancel.
1027 pub async fn read_fetch_object(&mut self) -> Result<(FetchObject, Vec<u8>), ConnectionError> {
1028 if self.fetch_io.is_none() {
1029 return Err(ConnectionError::DataStreamState("fetch object reader not started"));
1030 }
1031 let object = loop {
1032 let reader = self.fetch_io.as_mut().unwrap();
1033 // Advanced on a probe and committed only once the whole header was
1034 // there to read: a reader advanced by a short read would resolve
1035 // the next Object against a half-read one.
1036 let mut probe = reader.clone();
1037 let mut cursor = &self.buf[..];
1038 match probe.read_object_header(&mut cursor) {
1039 Ok(object) => {
1040 let consumed = self.buf.len() - cursor.remaining();
1041 self.buf.advance(consumed);
1042 *reader = probe;
1043 break object;
1044 }
1045 Err(e) if e.is_incomplete() => {
1046 if !self.fill().await? {
1047 return Err(self.ended_between_objects());
1048 }
1049 }
1050 Err(e) => return Err(ConnectionError::Codec(e)),
1051 }
1052 };
1053 let payload = self.read_object_payload(&object.header.payload_length).await?;
1054 if let Some(existence) = &mut self.fetch_existence {
1055 existence.note_entry(&object).map_err(ConnectionError::Endpoint)?;
1056 }
1057 Ok((object, payload))
1058 }
1059
1060 /// Take the `length` payload bytes that follow a fetch object's header.
1061 ///
1062 /// Separate from the header read because the header is decoded from a probe
1063 /// cursor that may have to be retried after a fill, and the payload is a
1064 /// flat byte count that never is.
1065 async fn read_object_payload(&mut self, length: &VarInt) -> Result<Vec<u8>, ConnectionError> {
1066 let length = length.into_inner() as usize;
1067 self.ensure(length).await.map_err(|e| match e {
1068 ConnectionError::UnexpectedEnd => ConnectionError::TruncatedObject,
1069 other => other,
1070 })?;
1071 let payload = self.buf[..length].to_vec();
1072 self.buf.advance(length);
1073 Ok(payload)
1074 }
1075
1076 /// Returns the draft version this stream is framed for.
1077 pub fn draft(&self) -> DraftVersion {
1078 self.draft
1079 }
1080}
1081
1082/// Which of the seven message types draft-23 Section 6.3 lets a bidirectional
1083/// stream begin with opened a request stream.
1084///
1085/// Draft-23 Section 6.3: "A request stream begins with one of these seven
1086/// message types: TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
1087/// SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS. Bidirectional streams MUST NOT
1088/// begin with any other message type unless negotiated."
1089///
1090/// The set is per draft. A
1091/// seven-variant enum here is what makes it impossible to label a draft-23
1092/// request stream with a kind this draft does not have.
1093#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1094pub enum RequestKind {
1095 /// TRACK_STATUS, 0x0D.
1096 TrackStatus,
1097 /// SUBSCRIBE, 0x03.
1098 Subscribe,
1099 /// PUBLISH, 0x1D.
1100 Publish,
1101 /// FETCH, 0x16. Draft-23 has one shape of FETCH: Section 9.11 has no `Fetch Type` field.
1102 Fetch,
1103 /// PUBLISH_NAMESPACE, 0x06.
1104 PublishNamespace,
1105 /// SUBSCRIBE_NAMESPACE, 0x50.
1106 SubscribeNamespace,
1107 /// SUBSCRIBE_TRACKS, 0x51.
1108 SubscribeTracks,
1109}
1110
1111impl RequestKind {
1112 /// The message type a request stream of this kind begins with.
1113 pub const fn message_type(self) -> MessageType {
1114 match self {
1115 RequestKind::TrackStatus => MessageType::TrackStatus,
1116 RequestKind::Subscribe => MessageType::Subscribe,
1117 RequestKind::Publish => MessageType::Publish,
1118 RequestKind::Fetch => MessageType::Fetch,
1119 RequestKind::PublishNamespace => MessageType::PublishNamespace,
1120 RequestKind::SubscribeNamespace => MessageType::SubscribeNamespace,
1121 RequestKind::SubscribeTracks => MessageType::SubscribeTracks,
1122 }
1123 }
1124
1125 /// The kind of request stream `ty` opens, or `None` when it opens none.
1126 ///
1127 /// The inverse of [`message_type`](Self::message_type) and the classifier
1128 /// the accept path runs on the first message of a bidirectional stream the
1129 /// peer opened. `None` is the UNEXPECTED_MESSAGE case of draft-23 Section 6.3.
1130 ///
1131 /// Like [`starts_a_request_stream`] the match is exhaustive over
1132 /// [`MessageType`] with **no wildcard arm**, so a message type added in a
1133 /// later draft stops this compiling until someone classifies it; the unit
1134 /// test below holds the two functions to the same answer for every
1135 /// assigned type, so neither can drift from the other.
1136 pub const fn from_message_type(ty: MessageType) -> Option<RequestKind> {
1137 match ty {
1138 MessageType::TrackStatus => Some(RequestKind::TrackStatus),
1139 MessageType::Subscribe => Some(RequestKind::Subscribe),
1140 MessageType::Publish => Some(RequestKind::Publish),
1141 MessageType::Fetch => Some(RequestKind::Fetch),
1142 MessageType::PublishNamespace => Some(RequestKind::PublishNamespace),
1143 MessageType::SubscribeNamespace => Some(RequestKind::SubscribeNamespace),
1144 MessageType::SubscribeTracks => Some(RequestKind::SubscribeTracks),
1145 MessageType::Setup
1146 | MessageType::GoAway
1147 | MessageType::Namespace
1148 | MessageType::NamespaceDone
1149 | MessageType::PublishSkipped
1150 | MessageType::RequestUpdate
1151 | MessageType::SubscribeOk
1152 | MessageType::RequestOk
1153 | MessageType::RequestError
1154 | MessageType::PublishDone
1155 // // Section 9.10 puts it on "a subscription's
1156 // bidirectional stream", which is a stream a SUBSCRIBE or a PUBLISH
1157 // already opened, so it begins none of its own.
1158 | MessageType::PublishStateNotify => None,
1159 }
1160 }
1161}
1162
1163/// Which side opened the bidirectional stream a request travels on.
1164///
1165/// Draft-23 Section 6.3 gives every request a bidirectional stream, and either
1166/// endpoint may open one. The two directions are not symmetric — one side owes
1167/// a response and the other is waiting for it — so a [`RequestStream`] carries
1168/// this to say which side of that it is on.
1169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1170pub enum RequestOrigin {
1171 /// This endpoint opened the stream and wrote the request on it. What comes
1172 /// back is a response, and dropping the handle cancels the request.
1173 Local,
1174 /// The peer opened the stream; this endpoint owes it a response. What
1175 /// comes back is a follow-up to the peer's request, never a response, and
1176 /// dropping the handle abandons a request that was asked of us.
1177 Peer,
1178}
1179/// Whether draft-23 Table 5 places this message on a request stream that is
1180/// already open.
1181///
1182/// All five of the messages Table 5 marks "Request" without their beginning one:
1183/// REQUEST_UPDATE modifies the request its stream carries (Section 9.5),
1184/// NAMESPACE and NAMESPACE_DONE report namespaces on the SUBSCRIBE_NAMESPACE
1185/// stream that asked for them (Section 9.15 and Section 9.16), PUBLISH_SKIPPED
1186/// names a track on the SUBSCRIBE_TRACKS stream that asked for it (Section
1187/// 9.17), and PUBLISH_STATE_NOTIFY reports a change on the
1188/// subscription's own stream (Section 9.10).
1189///
1190/// `Endpoint::receive_message` already closes the session over all five when
1191/// they arrive on the control stream. Without this the client would write on the
1192/// control stream exactly what its own peer half refuses to read there.
1193fn belongs_on_a_request_stream(ty: MessageType) -> bool {
1194 matches!(
1195 ty,
1196 MessageType::RequestUpdate
1197 | MessageType::Namespace
1198 | MessageType::NamespaceDone
1199 | MessageType::PublishSkipped
1200 | MessageType::PublishStateNotify
1201 )
1202}
1203
1204/// Whether `ty` is one of the seven message types draft-23 Section 6.3 lets a
1205/// bidirectional stream begin with.
1206///
1207/// The match is exhaustive over [`MessageType`] and deliberately has **no
1208/// wildcard arm**. That is the drift guard: `MessageType` is not
1209/// `#[non_exhaustive]`, so the day a draft gains a message type this stops
1210/// compiling until someone says here whether the new type opens a request
1211/// stream. A wildcard would silently answer "no" for it.
1212///
1213/// Classification is over the typed `MessageType`, never over a raw `u64`,
1214/// because the number alone does not say which registry it came from: on
1215/// draft-23, 0x50 is SUBSCRIBE_NAMESPACE as a control message type and also a
1216/// SUBGROUP_HEADER as a unidirectional stream type.
1217pub const fn starts_a_request_stream(ty: MessageType) -> bool {
1218 match ty {
1219 // The seven that begin a request stream.
1220 MessageType::TrackStatus
1221 | MessageType::Subscribe
1222 | MessageType::Publish
1223 | MessageType::Fetch
1224 | MessageType::PublishNamespace
1225 | MessageType::SubscribeNamespace
1226 | MessageType::SubscribeTracks => true,
1227 // Messages that cannot begin a request stream. SETUP is the control
1228 // stream's own type varint. NAMESPACE, NAMESPACE_DONE, PUBLISH_SKIPPED
1229 // and REQUEST_UPDATE all travel ON a request stream — Table 5 gives
1230 // every one of them the Stream value "Request" — but none of them
1231 // opens one: the first three report on the namespace or publication of
1232 // the stream that is already there, and REQUEST_UPDATE modifies a
1233 // request that already has a stream, draft-23 Section 9.5 putting it
1234 // "on the same bidi stream as the request". This function answers
1235 // *does this message open a stream*, not *does it belong on one*.
1236 //
1237 // GOAWAY is here because it opens nothing, not because it is confined
1238 // to the control stream: it has no Request ID, so it has one wire form for both places. It
1239 // may follow a
1240 // request on an already-open request stream — see
1241 // [`Connection::send_on_request_stream`], which refuses nothing.
1242 MessageType::Setup
1243 | MessageType::GoAway
1244 | MessageType::Namespace
1245 | MessageType::NamespaceDone
1246 | MessageType::PublishSkipped
1247 | MessageType::RequestUpdate => false,
1248 // Responses. They cannot begin a stream: they arrive on the request
1249 // stream their request opened, which is why they carry no request id
1250 // of their own on this draft. PUBLISH_OK is a REQUEST_OK (Section 1.5).
1251 MessageType::SubscribeOk
1252 | MessageType::RequestOk
1253 | MessageType::RequestError
1254 | MessageType::PublishDone => false,
1255 // Not a response — Section 9.10 makes it unilateral, answered with
1256 // nothing — but it opens no stream either: it travels on the
1257 // subscription's, which a SUBSCRIBE or a PUBLISH already opened.
1258 MessageType::PublishStateNotify => false,
1259 }
1260}
1261
1262/// One request and its answer, on a bidirectional stream of their own.
1263///
1264/// Draft-23 Section 6.3 keeps requests off the control plane: each request is
1265/// the first message on a bidirectional stream it opens, and the response
1266/// comes back on that same stream. Responses carry no request id on this
1267/// draft — **the stream is the correlation**, which is why this handle exists
1268/// and why a bare request id is no longer enough to find an answer.
1269///
1270/// # Reading and writing go through the connection
1271///
1272/// This handle owns both halves of the stream but not the session, so the
1273/// endpoint state machine and the observer stay where they were. Read a
1274/// response with [`Connection::recv_on_request_stream`], write a follow-up with
1275/// [`Connection::send_on_request_stream`], and cancel with
1276/// [`Connection::cancel_request_stream`].
1277///
1278/// [`cancel`](Self::cancel) and [`peer_cancelled`](Self::peer_cancelled) are on
1279/// the handle because they touch the stream and nothing else, and [`Drop`]
1280/// needs the first of them. Neither moves the endpoint's record of the request,
1281/// which is why the connection carries a pair of its own.
1282///
1283/// # Dropping this cancels the request
1284///
1285/// A dropped handle resets the send half and sends `STOP_SENDING` on the
1286/// receive half, both with [`REQUEST_CANCELLED`], unless the stream was
1287/// already cancelled or finished. Letting the default drop stand would send a
1288/// FIN instead, telling the peer the request ended *cleanly* when it was
1289/// abandoned.
1290///
1291/// The consequence is sharp and worth stating: a live subscription's request
1292/// stream must be **held for the subscription's life**, because PUBLISH_DONE
1293/// arrives on it. Keeping only [`request_id`](Self::request_id) and letting
1294/// the handle fall out of scope cancels the subscription.
1295///
1296/// What a drop cannot do is say so at the endpoint. [`Drop`] holds the stream
1297/// and not the session, so the request stays where it was in the endpoint's
1298/// record while the stream it travelled on is gone. Call
1299/// [`Connection::cancel_request_stream`] wherever that record matters.
1300///
1301/// # Which side opened it changes what this handle does
1302///
1303/// [`origin`](Self::origin) says whether this endpoint opened the stream or
1304/// accepted it, and three behaviours turn on it: reads dispatch as responses
1305/// or as follow-ups to the peer's request, the `respond_*` helpers refuse a
1306/// stream this endpoint opened, and [`Drop`] resets with
1307/// [`REQUEST_UNANSWERED`] rather than [`REQUEST_CANCELLED`]. Everything else —
1308/// [`cancel`](Self::cancel), [`peer_cancelled`](Self::peer_cancelled),
1309/// [`Connection::send_on_request_stream`] — is the same in both directions.
1310/// Draft-23 Section 6.4.2.3 is explicit that a cancel is available to both:
1311/// "Senders cancel requests if the response is no longer of interest;
1312/// Receivers cancel requests if they are unable to or choose not to respond."
1313///
1314/// [`finish`](Self::finish) is the one act that is constrained rather than
1315/// merely different: Section 6.4.2.2 forbids a FIN before the messages that
1316/// direction still owes.
1317///
1318/// All fields are private so the shape can grow without breaking callers.
1319#[must_use = "dropping a request stream cancels the request; hold it until the response arrives"]
1320pub struct RequestStream {
1321 send: FramedSendStream,
1322 recv: FramedRecvStream,
1323 request_id: VarInt,
1324 kind: RequestKind,
1325 /// The unidirectional stream this request's objects are being served on.
1326 ///
1327 /// Only a FETCH has one, and only once the caller has opened it through
1328 /// [`Connection::open_fetch_stream_on`]. Held here rather than in a table
1329 /// on the connection because the request stream is already the thing that
1330 /// knows what this request still owes, and because a handle kept beside
1331 /// the request cannot outlive it.
1332 fetch_data: Option<FramedSendStream>,
1333 draft: DraftVersion,
1334 stream_id: u64,
1335 cancelled: bool,
1336 finished: bool,
1337 origin: RequestOrigin,
1338 /// Whether a `respond_*` helper has written a response on this stream.
1339 /// True on a [`RequestOrigin::Peer`] stream from the response written on
1340 /// it, and on a [`RequestOrigin::Local`] one from the answer to an update
1341 /// the peer sent, which is the only response this endpoint writes on a
1342 /// stream of its own.
1343 responded: bool,
1344 /// Whether this endpoint is the publisher of a subscription established on
1345 /// this stream and has not yet sent PUBLISH_DONE.
1346 ///
1347 /// True from the PUBLISH this endpoint sent, and from the SUBSCRIBE_OK it
1348 /// wrote answering a peer's SUBSCRIBE; false again once PUBLISH_DONE is
1349 /// out. It is what draft-23 Section 6.4.2.2's second clause is checked
1350 /// against in [`finish`](Self::finish).
1351 owes_publish_done: bool,
1352}
1353
1354impl RequestStream {
1355 /// The request id the endpoint allocated for this request.
1356 ///
1357 /// Useful for logging and for endpoint calls that still take one. It is
1358 /// not enough to find the response: draft-23 responses carry no request
1359 /// id, so only this stream identifies them.
1360 pub fn request_id(&self) -> VarInt {
1361 self.request_id
1362 }
1363
1364 /// Which of the seven request types opened this stream.
1365 pub fn kind(&self) -> RequestKind {
1366 self.kind
1367 }
1368
1369 /// The transport-level stream identifier, the same one
1370 /// [`ClientEvent::StreamOpened`] reports for data streams.
1371 pub fn stream_id(&self) -> u64 {
1372 self.stream_id
1373 }
1374
1375 /// The draft version this stream is framed for.
1376 pub fn draft(&self) -> DraftVersion {
1377 self.draft
1378 }
1379
1380 /// Which side opened this stream.
1381 ///
1382 /// [`RequestOrigin::Peer`] means this endpoint owes a response and the
1383 /// `respond_*` helpers apply; [`RequestOrigin::Local`] means it is waiting
1384 /// for one.
1385 pub fn origin(&self) -> RequestOrigin {
1386 self.origin
1387 }
1388
1389 /// Whether a response has been written on this stream by one of the
1390 /// `respond_*` helpers.
1391 ///
1392 /// On a [`RequestOrigin::Local`] stream this says an update the peer sent
1393 /// was answered here, not that the request itself was: that one is
1394 /// answered by the peer.
1395 pub fn responded(&self) -> bool {
1396 self.responded
1397 }
1398
1399 /// Whether this endpoint still owes the peer a PUBLISH_DONE on this stream.
1400 ///
1401 /// True for a PUBLISH this endpoint sent and for a peer's SUBSCRIBE it
1402 /// answered with SUBSCRIBE_OK — the two ways draft-23 Section 6.4.2.2's
1403 /// "publisher of an Established subscription" is reached — until
1404 /// [`Connection::publish_done`] or [`Connection::publish_done_on`] has run.
1405 /// While it is true, [`finish`](Self::finish) refuses.
1406 pub fn owes_publish_done(&self) -> bool {
1407 self.owes_publish_done
1408 }
1409
1410 /// The stream this request's objects are being served on, if one is open.
1411 ///
1412 /// Only a FETCH answered through
1413 /// [`Connection::open_fetch_stream_on`](Connection::open_fetch_stream_on)
1414 /// has one. Writing objects goes through this rather than through a handle
1415 /// the caller keeps, so that the connection can still reach the stream
1416 /// when a rule says to reset it.
1417 pub fn fetch_data(&mut self) -> Option<&mut FramedSendStream> {
1418 self.fetch_data.as_mut()
1419 }
1420
1421 /// Reset the fetch data stream, if one was opened, and forget it.
1422 ///
1423 /// The sentence that requires this names no error code, so the code comes
1424 /// from the registry rather than from here: CANCELLED is "the stream was
1425 /// cancelled by either endpoint", which is what a publisher abandoning the
1426 /// objects it was serving has done.
1427 fn reset_fetch_data(&mut self) {
1428 if let Some(mut framed) = self.fetch_data.take() {
1429 let _ = framed.reset(StreamResetErrorCode::Cancelled as u64);
1430 }
1431 }
1432
1433 /// Whether [`cancel`](Self::cancel) has already run on this handle.
1434 ///
1435 /// Says nothing about the peer: a peer reset is learned from
1436 /// [`peer_cancelled`](Self::peer_cancelled) or from the next read.
1437 pub fn is_cancelled(&self) -> bool {
1438 self.cancelled
1439 }
1440
1441 /// Cancel the request by resetting the stream, handing the peer `code`.
1442 ///
1443 /// Draft-23 has no UNSUBSCRIBE or FETCH_CANCEL: resetting the request stream is how a request
1444 /// is withdrawn. Both halves are shut — a QUIC bidirectional stream has two
1445 /// independent halves, so resetting only the send half would leave the
1446 /// peer free to keep writing a response nobody will read. The send half is
1447 /// reset with `code` and the receive half is stopped with the same value.
1448 ///
1449 /// [`REQUEST_CANCELLED`] is the ordinary choice. Draft-23 Section 12.5
1450 /// says an application SHOULD take the code from the Stream Reset Error
1451 /// Codes registry when resetting, or sending STOP_SENDING on, any stream —
1452 /// request streams included — so [`StreamResetErrorCode`] is where a value
1453 /// other than the default should come from. The parameter is a plain `u64`
1454 /// because the registry reserves greasing code points that have no variant;
1455 /// a caller with a named code has [`StreamResetErrorCode::as_u64`].
1456 ///
1457 /// **This is the stream and nothing else.** The endpoint's record of the
1458 /// request does not move, so a response already in flight is still accepted
1459 /// after this returns. [`Connection::cancel_request_stream`] does both and
1460 /// is what a caller holding a connection should reach for; this stays
1461 /// because [`Drop`] has no connection to reach.
1462 ///
1463 /// Idempotent, and it retires the [`Drop`] behaviour: a cancelled handle
1464 /// does nothing further when it goes out of scope. Errors from a stream
1465 /// that was already reset or stopped are swallowed for the same reason —
1466 /// the request is cancelled either way.
1467 ///
1468 /// # Errors
1469 ///
1470 /// [`ConnectionError::Transport`] carrying [`TransportError::Write`] if
1471 /// `code` is outside the QUIC varint range (`0..2^62`). Nothing is sent
1472 /// in that case, and the handle is *not* marked cancelled, so a caller
1473 /// can retry with a representable code.
1474 pub fn cancel(&mut self, code: u64) -> Result<(), ConnectionError> {
1475 if self.cancelled {
1476 return Ok(());
1477 }
1478 // Reject an unrepresentable code before either half is touched, so a
1479 // failed call leaves the stream exactly as it was.
1480 if code > MAX_QUIC_VARINT {
1481 return Err(ConnectionError::Transport(TransportError::Write(format!(
1482 "error code {code} exceeds the varint range"
1483 ))));
1484 }
1485 self.cancelled = true;
1486 // Already-finished or already-reset halves report StreamClosed; the
1487 // request ends up cancelled regardless, so neither is worth raising.
1488 let _ = self.send.reset(code);
1489 let _ = self.recv.stop(code);
1490 Ok(())
1491 }
1492
1493 /// Wait for the peer to cancel this request, consuming nothing.
1494 ///
1495 /// A caller applying backpressure is deliberately not calling
1496 /// [`Connection::recv_on_request_stream`], which is the only other place a
1497 /// peer reset surfaces — so without this the abandonment goes unobserved
1498 /// for as long as the backpressure lasts. This grants no flow-control
1499 /// credit and is cancel-safe.
1500 ///
1501 /// Returns `Ok(Some(code))` with the peer's application error code, or
1502 /// `Ok(None)` meaning **no reset is observable, now or ever — stop
1503 /// asking**. A caller that re-polls after `Ok(None)` spins.
1504 ///
1505 /// Like [`cancel`](Self::cancel), this records nothing at the endpoint.
1506 /// [`Connection::peer_cancelled_on_request_stream`] is the same wait with
1507 /// the record attached.
1508 ///
1509 /// On WebTransport this always answers `Ok(None)`: `wtransport` exposes no
1510 /// reset-only observable, so a WebTransport caller learns of a peer cancel
1511 /// on its next read and not before.
1512 pub async fn peer_cancelled(&mut self) -> Result<Option<u64>, ConnectionError> {
1513 self.recv.received_reset().await
1514 }
1515
1516 /// Finish the send half cleanly, leaving the receive half open.
1517 ///
1518 /// Draft-23 Section 6.4.2.2 settles what a FIN means, which earlier drafts
1519 /// left open: "A FIN only indicates that an endpoint will send no further
1520 /// messages in that direction; it is not a request cancellation." A
1521 /// requester may therefore FIN before its response arrives — "A requester,
1522 /// with the exception of the sender of PUBLISH, MAY FIN immediately after
1523 /// sending a message if it will not send a REQUEST_UPDATE" — and the
1524 /// receive half stays open to carry the response, which is why this touches
1525 /// only the send half.
1526 ///
1527 /// What the same section forbids is finishing early: "An endpoint MUST NOT
1528 /// send a FIN on a direction of a request stream until it has sent all
1529 /// required messages on that direction for its request type", and "An
1530 /// endpoint that receives a FIN before all required messages have arrived
1531 /// treats the request as failed." The section names the two messages that
1532 /// are always required — "an endpoint sending a response to a request MUST
1533 /// send the corresponding response message, and the publisher of an
1534 /// Established subscription MUST send PUBLISH_DONE" — and both are things
1535 /// this handle knows about itself, so both are checked here and **nothing
1536 /// is written** when either is outstanding.
1537 ///
1538 /// A responder that owes a response has not sent one:
1539 /// [`ConnectionError::FinBeforeResponse`]. A publisher that owes a
1540 /// PUBLISH_DONE, whether from a PUBLISH it sent or a peer's SUBSCRIBE it
1541 /// accepted, gets [`ConnectionError::FinBeforePublishDone`] — which is why
1542 /// [`Connection::publish_done`] and [`Connection::publish_done_on`] are the
1543 /// routes that end a publication, rather than a bare `finish`.
1544 ///
1545 /// What is not checked is everything the caller alone knows: a
1546 /// SUBSCRIBE_NAMESPACE responder deciding it has no more namespaces to
1547 /// announce, or a requester deciding it will send no REQUEST_UPDATE. The
1548 /// section leaves those to the endpoint, and so does this.
1549 ///
1550 /// A finished handle, like a cancelled one, does nothing further on
1551 /// [`Drop`]. That matters here: the default drop resets both halves, which
1552 /// Section 6.4.2.2 contrasts with a FIN as the abrupt close, so a request
1553 /// that ended cleanly must come through this to avoid being reported as
1554 /// cancelled.
1555 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
1556 if self.finished || self.cancelled {
1557 return Ok(());
1558 }
1559 if self.origin == RequestOrigin::Peer && !self.responded {
1560 return Err(ConnectionError::FinBeforeResponse(self.request_id.into_inner()));
1561 }
1562 if self.owes_publish_done {
1563 return Err(ConnectionError::FinBeforePublishDone(self.request_id.into_inner()));
1564 }
1565 self.finished = true;
1566 self.send.finish().await
1567 }
1568}
1569
1570impl Drop for RequestStream {
1571 /// Reset the request unless it was already cancelled or finished.
1572 ///
1573 /// See the type-level note: the default drop would FIN the send half,
1574 /// which claims a clean end for a request the caller walked away from —
1575 /// and on draft-23 a FIN is a stronger claim than on earlier drafts, since
1576 /// Section 6.4.2.2 has the receiver of one *treat the request as failed*
1577 /// only when messages are still owed and complete otherwise.
1578 ///
1579 /// The code says which walking away it was. A stream this endpoint opened
1580 /// is cancelled — [`REQUEST_CANCELLED`] — which is the requester act
1581 /// Section 6.4.2.3 describes. A stream the peer opened is reset with
1582 /// [`REQUEST_UNANSWERED`] whether or not a response was already written:
1583 /// before one, the request was never served; after one, the obligations
1584 /// that follow it are still outstanding.
1585 fn drop(&mut self) {
1586 if self.cancelled || self.finished {
1587 return;
1588 }
1589 let code = match self.origin {
1590 RequestOrigin::Local => REQUEST_CANCELLED,
1591 RequestOrigin::Peer => REQUEST_UNANSWERED,
1592 };
1593 let _ = self.send.reset(code);
1594 let _ = self.recv.stop(code);
1595 }
1596}
1597
1598/// Holds a peer-opened stream pair while its first message is being read, and
1599/// puts it back on the connection's queue if that read is abandoned.
1600///
1601/// [`Connection::accept_request_stream`] awaits a whole control message, and a
1602/// caller may drop that future — a `select!` against a shutdown signal is the
1603/// ordinary reason. Without this the stream, and every byte already read off
1604/// it into the reader's buffer, would go with the future: the peer would see a
1605/// request stream reset for no reason it could act on.
1606///
1607/// [`Drop`] is the only place this can run, because a cancelled future is
1608/// never polled again. Every path that finishes — success or error — takes the
1609/// pair out first, so a pair still present when this drops was cancelled.
1610struct PendingInbound<'a> {
1611 pair: Option<(FramedSendStream, FramedRecvStream)>,
1612 queue: &'a Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1613}
1614
1615impl Drop for PendingInbound<'_> {
1616 fn drop(&mut self) {
1617 if let Some(pair) = self.pair.take() {
1618 // Front, not back: this stream arrived before anything still
1619 // queued behind it, and a partially read message must not be
1620 // handed out after a stream that arrived later.
1621 self.queue.lock().unwrap_or_else(|p| p.into_inner()).push_front(pair);
1622 }
1623 }
1624}
1625
1626/// The largest value a QUIC application error code can carry, `2^62 - 1`.
1627///
1628/// Checked by [`RequestStream::cancel`] before either half of the stream is
1629/// touched, so an unrepresentable code cannot half-cancel a request.
1630const MAX_QUIC_VARINT: u64 = (1u64 << 62) - 1;
1631
1632/// A live MoQT connection over QUIC or WebTransport, combining the endpoint
1633/// state machine with actual network I/O.
1634pub struct Connection {
1635 transport: Transport,
1636 endpoint: Endpoint,
1637 draft: DraftVersion,
1638 /// The MAX_CONTROL_MESSAGE_SIZE this endpoint put in its SETUP, which
1639 /// bounds every control message it reads (draft-23 Section 9.1.8).
1640 recv_control_limit: u64,
1641 /// The MAX_CONTROL_MESSAGE_SIZE the server's SETUP advertised, which every
1642 /// control message after this endpoint's SETUP is encoded under.
1643 send_control_limit: u64,
1644 control_send: Option<FramedSendStream>,
1645 control_recv: Option<FramedRecvStream>,
1646 observer: Option<Box<dyn ConnectionObserver>>,
1647 /// Setup events buffered during `connect()` and replayed when an
1648 /// observer attaches via `set_observer` — without this, an observer
1649 /// attached after `connect` returns would never see the handshake.
1650 pending_events: Vec<ClientEvent>,
1651 /// The server's half of the setup handshake, kept whole.
1652 ///
1653 /// The endpoint acts on the parameters it recognises and retains none of
1654 /// them, and which parameters a server sends — in what order, with what
1655 /// values — is the sharpest thing a session says about the implementation
1656 /// behind it.
1657 server_setup: AnyControlMessage,
1658 /// The framed wire bytes of [`Self::server_setup`].
1659 server_setup_raw: Option<Vec<u8>>,
1660 /// Unidirectional streams accepted while `connect` was looking for the
1661 /// peer's control stream, in arrival order, and then any stream that is
1662 /// not a FETCH_HEADER one that a fetch or fill accept met.
1663 ///
1664 /// Data streams are allowed to arrive before the control streams on this
1665 /// draft, so the search cannot assume the first unidirectional stream is
1666 /// the control one — and dropping the ones that are not would silently
1667 /// lose objects the peer already sent.
1668 /// [`accept_subgroup_stream`](Connection::accept_subgroup_stream) empties
1669 /// this before it accepts anything new.
1670 ///
1671 /// Behind a mutex because that method takes `&self`. The lock is only
1672 /// ever held for a push or a pop, never across an await.
1673 deferred_uni: Mutex<VecDeque<FramedRecvStream>>,
1674 /// FETCH_HEADER streams, fetch and fill alike, accepted by a call that
1675 /// wanted another kind, in arrival order. The type varint is still on
1676 /// the front of each.
1677 /// [`accept_fetch_stream`](Connection::accept_fetch_stream) and
1678 /// [`accept_fill_stream`](Connection::accept_fill_stream) empty this
1679 /// before they accept anything new.
1680 deferred_fetch: Mutex<VecDeque<FramedRecvStream>>,
1681 /// Bidirectional streams the peer opened that
1682 /// [`accept_request_stream`](Connection::accept_request_stream) took off
1683 /// the transport but did not finish reading a first message from, because
1684 /// its future was dropped. In arrival order.
1685 ///
1686 /// Without this a caller could not put `accept_request_stream` in a
1687 /// `select!` at all: losing the race would lose a stream the peer had
1688 /// already opened and, with it, whatever of the request had arrived.
1689 /// [`accept_request_stream`](Connection::accept_request_stream) empties
1690 /// this before it accepts anything new.
1691 ///
1692 /// Behind a mutex for the same reason `deferred_uni` is: the lock is only
1693 /// ever held for a push or a pop, never across an await.
1694 pending_inbound: Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1695}
1696
1697impl Connection {
1698 /// Connect to a MoQT server as a client.
1699 ///
1700 /// Establishes a QUIC or WebTransport connection (based on
1701 /// `config.transport`), brings up the control plane, performs the SETUP
1702 /// handshake, and returns a ready-to-use connection.
1703 ///
1704 /// # The control plane is a pair of unidirectional streams
1705 ///
1706 /// Draft-23 Section 6.3: "MOQT uses a pair of unidirectional streams for
1707 /// creating the session and exchanging control messages. Each peer opens
1708 /// one control stream beginning with a SETUP message." So each direction
1709 /// is a separate stream opened by the peer that writes on it. This opens
1710 /// one with `open_uni` and writes SETUP on it, then finds the peer's by
1711 /// accepting unidirectional streams until one leads with
1712 /// [`CONTROL_STREAM_TYPE`].
1713 ///
1714 /// Nothing is written ahead of the SETUP: 0x2F00 is both the SETUP
1715 /// message type and the unidirectional stream type for a control stream,
1716 /// so the message's own first field is the stream header. See
1717 /// [`CONTROL_STREAM_TYPE`].
1718 ///
1719 /// A bidirectional stream is *not* the control stream here — the same
1720 /// section makes it a request stream, one that begins with TRACK_STATUS,
1721 /// SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE or
1722 /// SUBSCRIBE_TRACKS: "Bidirectional streams MUST NOT begin with any other
1723 /// message type unless negotiated. If they do, the peer MUST close the Session with
1724 /// UNEXPECTED_MESSAGE." A SETUP written on a bidirectional
1725 /// stream is exactly that case, so a peer that enforces the topology
1726 /// answers it by closing the session.
1727 ///
1728 /// # Unidirectional streams that arrive before the peer's control stream
1729 ///
1730 /// They are kept, not dropped. Section 6.3 expects them: "Unidirectional
1731 /// streams containing Objects or bidirectional stream(s) beginning with a
1732 /// request message could arrive prior to the control streams, in which
1733 /// case the data SHOULD be buffered until both control streams arrive and
1734 /// setup is complete." Each such stream is set aside and handed to
1735 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream) in arrival
1736 /// order, ahead of any newly accepted stream. Only the leading type
1737 /// varint is read from them here; the rest stays on the transport, unread
1738 /// and still flow-controlled, so nothing is buffered in this process
1739 /// beyond those few bytes.
1740 ///
1741 /// One limit worth knowing: the search waits for each stream's type
1742 /// varint in turn, so a peer that opens a unidirectional stream and then
1743 /// writes nothing on it stalls the handshake behind that stream.
1744 pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
1745 // PATH is for native QUIC only, and the transport is known here and
1746 // nowhere further in. Refusing before dialling means a session that
1747 // the server would close on sight is never opened.
1748 setup::validate_client_path_transport(
1749 &config.setup_parameters,
1750 matches!(config.transport, TransportType::WebTransport { .. }),
1751 )
1752 .map_err(EndpointError::from)?;
1753
1754 let transport = match &config.transport {
1755 TransportType::Quic => Self::connect_quic(addr, &config).await?,
1756 TransportType::WebTransport { url } => {
1757 let url = url.clone();
1758 Self::connect_webtransport(&url, &config).await?
1759 }
1760 };
1761
1762 Self::adopt(transport, config).await
1763 }
1764
1765 /// Run the MoQT setup handshake over a transport somebody else established.
1766 ///
1767 /// For choosing the draft from what the server selected: dial once through
1768 /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
1769 /// connection to the module its answer names. [`Self::connect`] cannot do
1770 /// this — it derives its single ALPN from the draft it was given.
1771 ///
1772 /// `config.draft` must match this module. The transport is adopted as
1773 /// given; nothing here re-checks the ALPN it was negotiated with.
1774 pub async fn adopt(
1775 transport: Transport,
1776 config: ClientConfig,
1777 ) -> Result<Self, ConnectionError> {
1778 Self::adopt_with(transport, config, None).await
1779 }
1780
1781 /// [`Self::adopt`] with `observer` attached before the handshake.
1782 ///
1783 /// The SETUP this side sends is reported as it is written, and a SETUP from
1784 /// the peer that this side refuses as [`ClientEvent::ControlMessageRefused`].
1785 /// [`Self::set_observer`] after `adopt` sees the same handshake events, but
1786 /// only once the handshake has succeeded.
1787 pub async fn adopt_observed(
1788 transport: Transport,
1789 config: ClientConfig,
1790 observer: Box<dyn ConnectionObserver>,
1791 ) -> Result<Self, ConnectionError> {
1792 Self::adopt_with(transport, config, Some(observer)).await
1793 }
1794
1795 /// [`Self::adopt`], reporting to `observer` from the first byte written.
1796 pub(crate) async fn adopt_with(
1797 transport: Transport,
1798 config: ClientConfig,
1799 observer: Option<Box<dyn ConnectionObserver>>,
1800 ) -> Result<Self, ConnectionError> {
1801 let draft = config.draft;
1802 // PATH is for native QUIC only, and the transport is known here and
1803 // nowhere further in. Refusing before dialling means a session that
1804 // the server would close on sight is never opened.
1805 setup::validate_client_path_transport(
1806 &config.setup_parameters,
1807 matches!(config.transport, TransportType::WebTransport { .. }),
1808 )
1809 .map_err(EndpointError::from)?;
1810
1811 // Send half of the control plane: one unidirectional stream whose
1812 // first message is SETUP, which is also its stream header.
1813 let send = transport.open_uni().await?;
1814 let mut control_send = FramedSendStream::new(send, draft);
1815
1816 // Perform setup handshake (draft-23: no versions)
1817 let mut endpoint = Endpoint::new(Role::Client);
1818 endpoint.connect()?;
1819 let setup_msg = endpoint.send_setup(config.setup_parameters.clone())?;
1820 // Section 9.1.8: "Until an endpoint receives the peer's SETUP, it uses
1821 // the default value, including for SETUP itself." That binds what the
1822 // peer sends; what this endpoint accepts is what it advertised.
1823 let recv_control_limit = max_control_message_size(&config.setup_parameters)?;
1824 let any_setup = AnyControlMessage::Draft23(setup_msg);
1825 let raw_setup = control_send.write_control(&any_setup).await?;
1826 // Reported as written, so a peer that never answers still leaves it
1827 // observed.
1828 if let Some(obs) = &observer {
1829 obs.on_event_owned(ClientEvent::ControlMessage {
1830 direction: Direction::Send,
1831 message: any_setup.clone(),
1832 stream_id: None,
1833 raw: Some(raw_setup.clone()),
1834 });
1835 }
1836
1837 // Receive half: the peer's control stream is whichever unidirectional
1838 // stream leads with CONTROL_STREAM_TYPE.
1839 let mut deferred_uni: VecDeque<FramedRecvStream> = VecDeque::new();
1840 let mut deferred_fetch: VecDeque<FramedRecvStream> = VecDeque::new();
1841 let mut control_recv = loop {
1842 let recv = transport.accept_uni().await?;
1843 let mut framed = FramedRecvStream::new(recv, draft);
1844 match framed.peek_stream_type().await {
1845 Ok(CONTROL_STREAM_TYPE) => break framed,
1846 Ok(FETCH_STREAM_TYPE) => deferred_fetch.push_back(framed),
1847 // Every other type is a data stream — and so is a stream that
1848 // ended or failed before its type arrived, not because it is
1849 // one but because there is nothing left to decide with. The
1850 // data path sees the same end one read later and reports it
1851 // the way it reports every other. Treating it as the control
1852 // stream would hand the session's control plane to a stream
1853 // that carried nothing.
1854 _ => deferred_uni.push_back(framed),
1855 }
1856 };
1857
1858 let (server_setup, raw_server_setup) =
1859 match control_recv.read_control_with_limit(true, recv_control_limit).await {
1860 Ok(read) => read,
1861 Err(e) => {
1862 if let ConnectionError::Codec(codec) = &e {
1863 if let Some(code) = Self::codec_session_error_code(codec) {
1864 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1865 transport.close(wire_code, codec.to_string().as_bytes());
1866 }
1867 }
1868 if let Some(obs) = &observer {
1869 if let Some(event) =
1870 Self::refused_event(&e, None, control_recv.take_refused())
1871 {
1872 obs.on_event_owned(event);
1873 }
1874 }
1875 return Err(e);
1876 }
1877 };
1878 // Unified SETUP in draft-23: server responds with the same message type.
1879 //
1880 // Section 9: "An endpoint MUST NOT send a control message with a
1881 // length larger than the peer's MAX_CONTROL_MESSAGE_SIZE (Section
1882 // 9.1.8)." The decoder has already refused a value that does not parse.
1883 let send_control_limit = match &server_setup {
1884 AnyControlMessage::Draft23(ControlMessage::Setup(ref s)) => {
1885 if let Err(e) = endpoint.receive_setup(s) {
1886 let err = ConnectionError::from(e);
1887 Self::refuse_setup(observer.as_deref(), &err, &server_setup, &raw_server_setup);
1888 return Err(err);
1889 }
1890 match s.max_control_message_size() {
1891 Ok(limit) => limit,
1892 Err(e) => {
1893 let err = ConnectionError::Codec(e);
1894 Self::refuse_setup(
1895 observer.as_deref(),
1896 &err,
1897 &server_setup,
1898 &raw_server_setup,
1899 );
1900 return Err(err);
1901 }
1902 }
1903 }
1904 _ => {
1905 return Err(ConnectionError::Endpoint(EndpointError::NotActive));
1906 }
1907 };
1908
1909 let mut pending_events = vec![
1910 ClientEvent::ControlMessage {
1911 direction: Direction::Send,
1912 message: any_setup,
1913 stream_id: None,
1914 raw: Some(raw_setup),
1915 },
1916 ClientEvent::ControlMessage {
1917 direction: Direction::Receive,
1918 message: server_setup.clone(),
1919 stream_id: None,
1920 raw: raw_server_setup.clone(),
1921 },
1922 ClientEvent::SetupComplete { negotiated_version: 0xff000000 + 23 },
1923 ];
1924
1925 if let Some(obs) = &observer {
1926 // The SETUP this side sent was reported as it was written.
1927 for event in pending_events.drain(..).skip(1) {
1928 obs.on_event_owned(event);
1929 }
1930 }
1931
1932 Ok(Self {
1933 transport,
1934 endpoint,
1935 draft,
1936 recv_control_limit,
1937 send_control_limit,
1938 control_send: Some(control_send),
1939 control_recv: Some(control_recv),
1940 observer,
1941 pending_events,
1942 server_setup,
1943 server_setup_raw: raw_server_setup,
1944 deferred_uni: Mutex::new(deferred_uni),
1945 deferred_fetch: Mutex::new(deferred_fetch),
1946 pending_inbound: Mutex::new(VecDeque::new()),
1947 })
1948 }
1949
1950 /// Establish a raw QUIC connection.
1951 ///
1952 /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
1953 /// TLS and endpoint setup.
1954 async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
1955 let (transport, _negotiated) = crate::transport::dial_quic(
1956 addr,
1957 &crate::transport::QuicDialOptions {
1958 skip_cert_verification: config.skip_cert_verification,
1959 ca_certs: config.ca_certs.clone(),
1960 ..crate::transport::QuicDialOptions::new(config.alpn())
1961 },
1962 )
1963 .await?;
1964 Ok(transport)
1965 }
1966
1967 /// Establish a WebTransport connection.
1968 ///
1969 /// [`crate::transport::dial_webtransport`] holds the TLS and endpoint
1970 /// setup, exactly as `connect_quic` above defers its own. That is not
1971 /// only deduplication: both dials must trust the same roots. Settling trust
1972 /// at this call site instead — from `wtransport`'s own builder settings, or
1973 /// from a second config of this draft's own — puts the decision in two
1974 /// places, where it can stop matching what the QUIC dial trusts, so one
1975 /// relay would pass on one transport and fail on the other and a caller's
1976 /// private CA would reach only the dials whose call site installed it.
1977 /// Both ask the same function what to trust.
1978 #[cfg(feature = "webtransport")]
1979 async fn connect_webtransport(
1980 url: &str,
1981 config: &ClientConfig,
1982 ) -> Result<Transport, ConnectionError> {
1983 Ok(crate::transport::dial_webtransport(
1984 url,
1985 &crate::transport::QuicDialOptions {
1986 skip_cert_verification: config.skip_cert_verification,
1987 ca_certs: config.ca_certs.clone(),
1988 // The draft's own protocol identifier, which this draft
1989 // negotiates in `WT-Available-Protocols` rather than in
1990 // CLIENT_SETUP: "The client includes MOQT protocol identifiers
1991 // in the WT-Available-Protocols header". `config.alpn()` above
1992 // is `h3`, which is the HTTP/3 name and settles no version.
1993 wt_protocols: vec![config.draft.quic_alpn().to_vec()],
1994 ..crate::transport::QuicDialOptions::new(config.alpn())
1995 },
1996 )
1997 .await?)
1998 }
1999
2000 /// Stub for when the webtransport feature is not enabled.
2001 #[cfg(not(feature = "webtransport"))]
2002 async fn connect_webtransport(
2003 _url: &str,
2004 _config: &ClientConfig,
2005 ) -> Result<Transport, ConnectionError> {
2006 Err(ConnectionError::Transport(TransportError::Connect(
2007 "webtransport feature not enabled".into(),
2008 )))
2009 }
2010
2011 // -- Observer ---------------------------------------------------
2012
2013 /// Attach an observer. Buffered handshake events from `connect()` are
2014 /// flushed in arrival order before this returns.
2015 pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
2016 self.observer = Some(observer);
2017 for event in self.pending_events.drain(..) {
2018 if let Some(ref obs) = self.observer {
2019 obs.on_event_owned(event);
2020 }
2021 }
2022 }
2023
2024 /// Remove the observer.
2025 pub fn clear_observer(&mut self) {
2026 self.observer = None;
2027 }
2028
2029 /// How the QUIC connection was closed, once it has been. See
2030 /// [`TransportClose`](crate::transport::TransportClose).
2031 pub fn transport_close(&self) -> Option<crate::transport::TransportClose> {
2032 self.transport.transport_close()
2033 }
2034
2035 /// Emit an event to the observer, if one is attached.
2036 fn emit(&self, event: ClientEvent) {
2037 if let Some(ref obs) = self.observer {
2038 obs.on_event_owned(event);
2039 }
2040 }
2041
2042 /// Report a received frame this endpoint refused, when an observer is
2043 /// attached and the refusal is the peer's: see [`Self::refused_event`].
2044 fn emit_refused(&self, err: &ConnectionError, stream_id: Option<u64>, raw: Option<Vec<u8>>) {
2045 if self.observer.is_some() {
2046 if let Some(event) = Self::refused_event(err, stream_id, raw) {
2047 self.emit(event);
2048 }
2049 }
2050 }
2051
2052 /// The [`ClientEvent::ControlMessageRefused`] for `raw`, when `err` refuses
2053 /// what the peer sent: the decoder refused it, or it broke a rule
2054 /// [`EndpointError::fault`] or [`Self::draft_specific_cause`] lays on the
2055 /// peer.
2056 fn refused_event(
2057 err: &ConnectionError,
2058 stream_id: Option<u64>,
2059 raw: Option<Vec<u8>>,
2060 ) -> Option<ClientEvent> {
2061 use crate::above_codec_rules::{DraftSpecificCause, EndpointFault};
2062 let peers = match err {
2063 ConnectionError::Codec(_) | ConnectionError::VarInt(_) => true,
2064 ConnectionError::Endpoint(e) => matches!(e.fault(), EndpointFault::Peer(_)),
2065 other => matches!(
2066 Self::draft_specific_cause(other),
2067 Some(DraftSpecificCause::PeerViolation { .. } | DraftSpecificCause::Codec { .. })
2068 ),
2069 };
2070 let raw = raw.filter(|_| peers)?;
2071 Some(ClientEvent::ControlMessageRefused { stream_id, raw })
2072 }
2073
2074 /// Report a peer's SETUP the handshake refused to an observer attached
2075 /// before it: the message as received, then the refusal.
2076 fn refuse_setup(
2077 observer: Option<&dyn ConnectionObserver>,
2078 err: &ConnectionError,
2079 setup: &AnyControlMessage,
2080 raw: &Option<Vec<u8>>,
2081 ) {
2082 let Some(obs) = observer else { return };
2083 obs.on_event_owned(ClientEvent::ControlMessage {
2084 direction: Direction::Receive,
2085 message: setup.clone(),
2086 stream_id: None,
2087 raw: raw.clone(),
2088 });
2089 if let Some(event) = Self::refused_event(err, None, raw.clone()) {
2090 obs.on_event_owned(event);
2091 }
2092 }
2093
2094 // -- Control message I/O ----------------------------------------
2095
2096 /// Send a control message on the control stream.
2097 ///
2098 /// Wraps the draft-23 message in `AnyControlMessage::Draft23` for framing.
2099 /// Of the messages draft-23 Table 5 does not place on a request stream,
2100 /// exactly two reach the wire: SETUP, written by
2101 /// [`connect`](Self::connect) as the control stream's own type varint, and
2102 /// GOAWAY, which is the one message the table gives the Stream value
2103 /// "Control, Request" and which belongs here when it drains the session.
2104 ///
2105 /// # Four messages that this refuses
2106 ///
2107 /// Table 5 gives NAMESPACE (0x8), NAMESPACE_DONE (0xE), PUBLISH_SKIPPED
2108 /// (0xF) and REQUEST_UPDATE (0x2) the Stream value **Request**, not
2109 /// Control. REQUEST_UPDATE is sent "on the same bidi stream as the request"
2110 /// it modifies (Section 9.5); the other three report on the namespace or
2111 /// the publication of the request stream they arrive on. A conforming peer
2112 /// answers any of the four on the control stream by closing the session,
2113 /// and so does this client's own [`Endpoint::receive_message`] — writing
2114 /// one here produces a frame this implementation would itself refuse.
2115 ///
2116 /// All four have somewhere better to go, and none of them should come
2117 /// through here. REQUEST_UPDATE goes on the stream its request opened, with
2118 /// [`send_on_request_stream`](Self::send_on_request_stream). NAMESPACE and
2119 /// NAMESPACE_DONE go on a peer's SUBSCRIBE_NAMESPACE stream, with
2120 /// [`namespace_on`](Self::namespace_on) and
2121 /// [`namespace_done_on`](Self::namespace_done_on); PUBLISH_SKIPPED goes on
2122 /// a peer's SUBSCRIBE_TRACKS stream, with
2123 /// [`publish_skipped_on`](Self::publish_skipped_on). This method refuses
2124 /// all four with [`ConnectionError::RequestStreamMessageOnControlStream`]
2125 /// and writes nothing.
2126 ///
2127 /// [`Endpoint::receive_message`]: crate::draft23::endpoint::Endpoint::receive_message
2128 ///
2129 /// GOAWAY is correct here and is not confined here. It has no Request ID, so its control-stream
2130 /// and request-stream forms are one wire form;
2131 /// [`send_on_request_stream`](Self::send_on_request_stream)
2132 /// will put it on an open request stream.
2133 ///
2134 /// # Requests are refused here
2135 ///
2136 /// Draft-23 Section 6.3 keeps requests off the control plane: "In addition
2137 /// to the control streams, this specification uses bidirectional streams
2138 /// to carry requests. A request stream begins with one of these seven
2139 /// message types: TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH,
2140 /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS." The
2141 /// response comes back on that same bidirectional stream, and resetting it
2142 /// cancels the request.
2143 ///
2144 /// Handing one of those seven to this method returns
2145 /// [`ConnectionError::RequestOnControlStream`] and writes **nothing** —
2146 /// an enforcing peer sees no bytes at all, not a misplaced request. Use
2147 /// the typed helpers, which open a bidirectional stream each:
2148 /// [`subscribe`](Self::subscribe), [`fetch`](Self::fetch),
2149 /// [`publish`](Self::publish), [`track_status`](Self::track_status),
2150 /// [`publish_namespace`](Self::publish_namespace),
2151 /// [`subscribe_namespace`](Self::subscribe_namespace) and
2152 /// [`subscribe_tracks`](Self::subscribe_tracks).
2153 ///
2154 /// Response types are still permitted here, and need not be. A
2155 /// response written here will be refused by a conforming peer, whose
2156 /// endpoint answers a response on the control stream with an error; the
2157 /// route that is correct is
2158 /// [`accept_request_stream`](Self::accept_request_stream) and the
2159 /// `respond_*` helpers, which write on the stream the request arrived on
2160 /// and take the Request ID off the handle rather than from the caller.
2161 /// [`publish_done`](Self::publish_done) and
2162 /// [`publish_done_on`](Self::publish_done_on) do not come through here
2163 /// either: each takes the request stream its publication travels on.
2164 pub async fn send_control(&mut self, msg: &ControlMessage) -> Result<(), ConnectionError> {
2165 let ty = msg.message_type();
2166 if starts_a_request_stream(ty) {
2167 return Err(ConnectionError::RequestOnControlStream(ty));
2168 }
2169 // What this endpoint refuses to receive on the control stream it must
2170 // not write there either, or the client emits frames its own peer half
2171 // would close the session over.
2172 if belongs_on_a_request_stream(ty) {
2173 return Err(ConnectionError::RequestStreamMessageOnControlStream(ty));
2174 }
2175 let any = AnyControlMessage::Draft23(msg.clone());
2176 let send = self.control_send.as_mut().ok_or(ConnectionError::NoControlStream)?;
2177 let raw = send.write_control_with_limit(msg, self.send_control_limit).await?;
2178 self.emit(ClientEvent::ControlMessage {
2179 direction: Direction::Send,
2180 message: any,
2181 stream_id: None,
2182 raw: Some(raw),
2183 });
2184 Ok(())
2185 }
2186
2187 /// Read the next control message from the control stream.
2188 ///
2189 /// Returns the `AnyControlMessage` and also extracts the draft-23
2190 /// `ControlMessage` for internal endpoint dispatch.
2191 pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
2192 self.recv_control_raw().await.map(|(msg, _)| msg)
2193 }
2194
2195 /// [`Self::recv_control`], keeping the frame's wire bytes when an observer
2196 /// is attached.
2197 async fn recv_control_raw(
2198 &mut self,
2199 ) -> Result<(ControlMessage, Option<Vec<u8>>), ConnectionError> {
2200 let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
2201 let capture_raw = self.observer.is_some();
2202 let read = recv.read_control_with_limit(capture_raw, self.recv_control_limit).await;
2203 let (any, raw) = match read {
2204 Ok(v) => v,
2205 // Section 6.3: "A control stream MUST NOT be closed at the
2206 // underlying transport layer during the session's lifetime. If the
2207 // control stream is closed, the peer MUST close the session with
2208 // CLOSED_CONTROL_STREAM."
2209 Err(
2210 e @ (ConnectionError::StreamFinished
2211 | ConnectionError::UnexpectedEnd
2212 | ConnectionError::Transport(TransportError::StreamReset(_))),
2213 ) => {
2214 let code = SessionErrorCode::ClosedControlStream.as_u64();
2215 self.close(u32::try_from(code).unwrap_or(u32::MAX), b"control stream closed");
2216 return Err(e);
2217 }
2218 Err(e) => {
2219 let raw = recv.take_refused();
2220 let err = self.close_for_codec(e);
2221 self.emit_refused(&err, None, raw);
2222 return Err(err);
2223 }
2224 };
2225 if capture_raw {
2226 self.emit(ClientEvent::ControlMessage {
2227 direction: Direction::Receive,
2228 message: any.clone(),
2229 stream_id: None,
2230 raw: raw.clone(),
2231 });
2232 }
2233 // Unwrap to draft-23 for the endpoint
2234 match any {
2235 AnyControlMessage::Draft23(msg) => Ok((msg, raw)),
2236 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 23 the
2237 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
2238 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
2239 // naming the other drafts: such a list has to be edited in every draft module
2240 // whenever a draft is added, and a copy that omits one leaves this match
2241 // non-exhaustive.
2242 #[allow(unreachable_patterns)]
2243 _ => Err(ConnectionError::ControlMessageNarrowing),
2244 }
2245 }
2246
2247 /// Read and dispatch the next incoming control message through the
2248 /// endpoint state machine. Returns the decoded message for inspection.
2249 ///
2250 /// Responses never arrive here. Draft-23 responses carry no request id
2251 /// and belong on the request stream that asked for them, so the endpoint
2252 /// refuses a response that turns up on the control stream. Read them with
2253 /// [`recv_on_request_stream`](Self::recv_on_request_stream).
2254 ///
2255 /// A violation the draft answers with a session close is closed on the
2256 /// wire here, before the error is returned: the QUIC connection is closed
2257 /// with the code [`EndpointError::session_error_code`] names, so the peer
2258 /// that broke the rule learns of it rather than only the local endpoint.
2259 pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
2260 let (msg, raw) = self.recv_control_raw().await?;
2261 if let Err(e) = self.endpoint.receive_message(msg.clone()) {
2262 let err = self.close_if_session_fatal(e);
2263 self.emit_refused(&err, None, raw);
2264 return Err(err);
2265 }
2266
2267 // Emit draining event if this was a GoAway
2268 if let ControlMessage::GoAway(ref ga) = msg {
2269 self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
2270 }
2271
2272 Ok(msg)
2273 }
2274
2275 // -- Request streams --------------------------------------------
2276
2277 /// Open the bidirectional stream a request will be carried on.
2278 ///
2279 /// Opened *before* the endpoint allocates a request id, so a transport
2280 /// that refuses a new stream — the peer's `initial_max_streams_bidi` is
2281 /// exhausted, the connection is gone — costs nothing. The endpoint has no
2282 /// way to abandon a request it has already allocated, so every failure
2283 /// that can be moved ahead of the allocation is.
2284 ///
2285 /// Nothing is written here. A request stream carries no stream-type
2286 /// header: its first field is the leading message's own type field, which
2287 /// is what [`begin_request`](Self::begin_request) writes.
2288 async fn open_request_bi(
2289 &self,
2290 ) -> Result<(FramedSendStream, FramedRecvStream), ConnectionError> {
2291 let (send, recv) = self.transport.open_bi().await?;
2292 Ok((FramedSendStream::new(send, self.draft), FramedRecvStream::new(recv, self.draft)))
2293 }
2294
2295 /// Reset a request stream that was opened but whose request could not be
2296 /// built, and pass the endpoint's error through.
2297 ///
2298 /// Without this, an endpoint refusal — the session is draining, the
2299 /// request-id range is exhausted — would leave a bidirectional stream
2300 /// open that never carries a first message, and dropping it would FIN it,
2301 /// telling the peer an empty stream ended cleanly.
2302 fn or_abandon<T>(
2303 halves: &mut (FramedSendStream, FramedRecvStream),
2304 built: Result<T, EndpointError>,
2305 ) -> Result<T, ConnectionError> {
2306 match built {
2307 Ok(value) => Ok(value),
2308 Err(e) => {
2309 let _ = halves.0.reset(REQUEST_CANCELLED);
2310 let _ = halves.1.stop(REQUEST_CANCELLED);
2311 Err(ConnectionError::Endpoint(e))
2312 }
2313 }
2314 }
2315
2316 /// Write `msg` as the first message on an opened bidirectional stream and
2317 /// hand back the [`RequestStream`] that owns both halves.
2318 ///
2319 /// This is the one place a request reaches the wire. Every request helper
2320 /// funnels through it, so the ordering — open, allocate, write, emit — is
2321 /// stated once.
2322 ///
2323 /// A failed write resets both halves rather than leaving a half-written
2324 /// request stream behind. What it cannot undo is the endpoint's
2325 /// allocation: the request id and its state machine already exist, and
2326 /// there is no way to retract them, so a write that fails here leaves one
2327 /// pending request the endpoint will never see answered.
2328 async fn begin_request(
2329 &mut self,
2330 halves: (FramedSendStream, FramedRecvStream),
2331 kind: RequestKind,
2332 request_id: VarInt,
2333 msg: &ControlMessage,
2334 ) -> Result<RequestStream, ConnectionError> {
2335 debug_assert_eq!(
2336 msg.message_type(),
2337 kind.message_type(),
2338 "a request stream's first message must be the one its kind names"
2339 );
2340 let (mut send, mut recv) = halves;
2341 let stream_id = send.stream_id();
2342 self.emit(ClientEvent::StreamOpened {
2343 direction: Direction::Send,
2344 stream_kind: StreamKind::Request,
2345 stream_id,
2346 });
2347 let any = AnyControlMessage::Draft23(msg.clone());
2348 let raw = match send.write_control_with_limit(msg, self.send_control_limit).await {
2349 Ok(raw) => raw,
2350 Err(e) => {
2351 let _ = send.reset(REQUEST_CANCELLED);
2352 let _ = recv.stop(REQUEST_CANCELLED);
2353 return Err(e);
2354 }
2355 };
2356 self.emit(ClientEvent::ControlMessage {
2357 direction: Direction::Send,
2358 message: any,
2359 stream_id: Some(stream_id),
2360 raw: Some(raw),
2361 });
2362 Ok(RequestStream {
2363 send,
2364 recv,
2365 request_id,
2366 kind,
2367 draft: self.draft,
2368 stream_id,
2369 cancelled: false,
2370 finished: false,
2371 origin: RequestOrigin::Local,
2372 responded: false,
2373 fetch_data: None,
2374 // The sender of a PUBLISH is the one requester draft-23 Section
2375 // 6.4.2.2 excludes from "MAY FIN immediately after sending a
2376 // message": it is the publisher of the subscription the PUBLISH
2377 // establishes, so it owes a PUBLISH_DONE from the moment the
2378 // request goes out.
2379 owes_publish_done: matches!(kind, RequestKind::Publish),
2380 })
2381 }
2382
2383 /// Read the next message off a request stream and dispatch it through the
2384 /// endpoint with that stream's own request id.
2385 ///
2386 /// On draft-23 a response carries no request id; the stream is the
2387 /// correlation, so the id comes from the handle and not from the wire.
2388 ///
2389 /// This blocks until a whole message has arrived. Backpressure is per
2390 /// request: a stream nobody reads stays unread, and the peer stays flow
2391 /// controlled on it alone. A peer that reset the stream surfaces as
2392 /// [`ConnectionError::Transport`] carrying
2393 /// [`TransportError::StreamReset`] with the peer's code; a caller that is
2394 /// deliberately not reading should watch
2395 /// [`RequestStream::peer_cancelled`] instead.
2396 ///
2397 /// # Errors
2398 ///
2399 /// [`ConnectionError::Endpoint`] if the message is not one of this
2400 /// draft's response types, or if it does not fit the request's state.
2401 /// The message has already been emitted to the observer by then — what
2402 /// arrived is reported whether or not the endpoint accepts it.
2403 ///
2404 /// [`ConnectionError::Codec`] if the decoder refused the frame. The session is
2405 /// closed with the code [`Self::codec_session_error_code`] names, where it
2406 /// names one, as on the control stream.
2407 pub async fn recv_on_request_stream(
2408 &mut self,
2409 stream: &mut RequestStream,
2410 ) -> Result<ControlMessage, ConnectionError> {
2411 let capture_raw = self.observer.is_some();
2412 let (any, raw) =
2413 match stream.recv.read_control_with_limit(capture_raw, self.recv_control_limit).await {
2414 Ok(read) => read,
2415 Err(e) => {
2416 // A peer that reset this stream cancelled the request on it,
2417 // and this is where a caller reading normally learns of it. The
2418 // record is made and its verdict dropped: the read's own error
2419 // is what the caller has to act on, and returning a state error
2420 // in its place would hide a reset behind it.
2421 if matches!(e, ConnectionError::Transport(TransportError::StreamReset(_))) {
2422 let _ = self.endpoint.cancel_request(stream.request_id);
2423 }
2424 let raw = stream.recv.take_refused();
2425 let err = self.close_for_codec(e);
2426 self.emit_refused(&err, Some(stream.stream_id()), raw);
2427 return Err(err);
2428 }
2429 };
2430 if capture_raw {
2431 self.emit(ClientEvent::ControlMessage {
2432 direction: Direction::Receive,
2433 message: any.clone(),
2434 stream_id: Some(stream.stream_id()),
2435 raw: raw.clone(),
2436 });
2437 }
2438 let msg = match any {
2439 AnyControlMessage::Draft23(msg) => Ok::<_, ConnectionError>(msg),
2440 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 23 the
2441 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
2442 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
2443 // naming the other drafts: such a list has to be edited in every draft module
2444 // whenever a draft is added, and a copy that omits one leaves this match
2445 // non-exhaustive.
2446 #[allow(unreachable_patterns)]
2447 _ => Err(ConnectionError::ControlMessageNarrowing),
2448 }?;
2449 // Which dispatcher this belongs to is decided by who opened the
2450 // stream, not by the message. On a stream this endpoint opened the
2451 // next message is the answer to our request; on one the peer opened it
2452 // cannot be, because we are the one who owes an answer. Feeding a
2453 // peer's REQUEST_UPDATE to the response dispatcher would look up a
2454 // request we never made.
2455 // Section 9.3: "The parameters that can appear depend on the request
2456 // being answered", and only the stream says which request that is.
2457 if let (RequestOrigin::Local, ControlMessage::RequestOk(ok)) = (stream.origin, &msg) {
2458 if let Some(kind) = self.endpoint.request_ok_kind(stream.request_id) {
2459 if let Err(e) = ok.check_answers(kind) {
2460 let err = self.close_for_codec(ConnectionError::Codec(e));
2461 self.emit_refused(&err, Some(stream.stream_id()), raw);
2462 return Err(err);
2463 }
2464 }
2465 }
2466 let dispatched = match stream.origin {
2467 RequestOrigin::Local => {
2468 self.endpoint.receive_response_on_stream(stream.request_id, msg.clone())
2469 }
2470 RequestOrigin::Peer => {
2471 self.endpoint.receive_on_peer_request_stream(stream.request_id, msg.clone())
2472 }
2473 };
2474 if let Err(e) = dispatched {
2475 let err = self.close_if_session_fatal(e);
2476 self.emit_refused(&err, Some(stream.stream_id()), raw);
2477 return Err(err);
2478 }
2479 // Emitted after the dispatch rather than before it, so an observer sees
2480 // only a notify the endpoint accepted: one from the subscriber, or for
2481 // a request that is not a subscription, ends the session instead
2482 // (Section 9.10) and reporting it as a state change would be a lie.
2483 // The generic `ControlMessage` event above carries it either way.
2484 if let ControlMessage::PublishStateNotify(ref notify) = msg {
2485 self.emit(ClientEvent::PublishStateNotify {
2486 request_id: stream.request_id.into_inner(),
2487 stream_id: stream.stream_id(),
2488 parameters: notify.parameters.clone(),
2489 });
2490 }
2491 // A rejected request establishes nothing, so whatever the request type
2492 // would have owed on this direction is no longer owed. Without this a
2493 // PUBLISH the peer refused could never be finished cleanly — the
2494 // publisher's PUBLISH_DONE obligation would outlive the subscription
2495 // that was never created, and the only way out would be a reset.
2496 if matches!(msg, ControlMessage::RequestError(_)) {
2497 stream.owes_publish_done = false;
2498 }
2499 Ok(msg)
2500 }
2501
2502 /// Write a follow-up message on an already-open request stream.
2503 ///
2504 /// The request itself was written when the stream was opened; this is for
2505 /// what comes after it on the same stream, PUBLISH_DONE among them — see
2506 /// [`publish_done`](Self::publish_done), which uses this.
2507 ///
2508 /// It does not refuse any message type. Which messages may follow a
2509 /// request on its own stream is not something this implementation can
2510 /// settle, so the choice is left to the caller rather than guessed at.
2511 pub async fn send_on_request_stream(
2512 &mut self,
2513 stream: &mut RequestStream,
2514 msg: &ControlMessage,
2515 ) -> Result<(), ConnectionError> {
2516 let any = AnyControlMessage::Draft23(msg.clone());
2517 let raw = stream.send.write_control_with_limit(msg, self.send_control_limit).await?;
2518 self.emit(ClientEvent::ControlMessage {
2519 direction: Direction::Send,
2520 message: any,
2521 stream_id: Some(stream.stream_id()),
2522 raw: Some(raw),
2523 });
2524 Ok(())
2525 }
2526
2527 /// Cancel a request: record it at the endpoint, then terminate its stream.
2528 /// Section 6.4.2.3 puts the cancel at the stream — "Implementations cancel a
2529 /// request by abruptly terminating any directions of the stream that are
2530 /// still open" — while the request's own state lives in the endpoint, so
2531 /// the two have to move together. This is the only place that moves both.
2532 ///
2533 /// The endpoint goes first and the stream is terminated only if it agrees,
2534 /// which is the order every request path here uses: a caller acts on a
2535 /// stream after the endpoint has accepted the step, never before. A refused
2536 /// cancel therefore leaves the stream exactly as it was, and
2537 /// [`RequestStream::cancel`] is still there for a caller that wants the
2538 /// stream reset regardless.
2539 ///
2540 /// Idempotent from both ends: a request that has already ended accepts the
2541 /// cancel and stays where it is, and a handle that has already been
2542 /// cancelled resets nothing a second time.
2543 ///
2544 /// # Errors
2545 ///
2546 /// [`ConnectionError::Endpoint`] if no request carries this stream's id or
2547 /// the request has not been written, and [`ConnectionError::Transport`] if
2548 /// `code` is outside the QUIC varint range — see
2549 /// [`RequestStream::cancel`], which is what sends it.
2550 pub fn cancel_request_stream(
2551 &mut self,
2552 stream: &mut RequestStream,
2553 code: u64,
2554 ) -> Result<(), ConnectionError> {
2555 let recorded = self.endpoint.cancel_request(stream.request_id);
2556 recorded.map_err(|e| self.close_if_session_fatal(e))?;
2557 stream.cancel(code)
2558 }
2559
2560 /// Wait for the peer to cancel this request, and record it if it does.
2561 ///
2562 /// [`RequestStream::peer_cancelled`] with the endpoint's record attached. A
2563 /// caller applying backpressure is deliberately not calling
2564 /// [`recv_on_request_stream`](Self::recv_on_request_stream), which is the
2565 /// other place a peer reset surfaces, so without this the request would end
2566 /// on the wire and stay open in the endpoint's record for as long as the
2567 /// backpressure lasts.
2568 ///
2569 /// Returns what the handle's own method returns; see it for the `Ok(None)`
2570 /// case and for what WebTransport can and cannot observe. Cancel-safe, and
2571 /// it grants no flow-control credit.
2572 pub async fn peer_cancelled_on_request_stream(
2573 &mut self,
2574 stream: &mut RequestStream,
2575 ) -> Result<Option<u64>, ConnectionError> {
2576 let code = stream.peer_cancelled().await?;
2577 if code.is_some() {
2578 // Discarded for the reason the read path discards it: the peer has
2579 // ended the request whatever the record said, and a state error
2580 // here would replace the answer the caller asked for.
2581 let _ = self.endpoint.cancel_request(stream.request_id);
2582 }
2583 Ok(code)
2584 }
2585
2586 // -- Accepting the peer's request streams -----------------------
2587
2588 /// Accept the next bidirectional stream the peer opened, read the request
2589 /// it begins with, and hand back that request and a handle to answer it
2590 /// on.
2591 ///
2592 /// This is the mirror of the request helpers. Where
2593 /// [`subscribe`](Self::subscribe) and its siblings open a stream and write
2594 /// a request, this takes one the peer opened and reads one. Draft-23
2595 /// Section 6.3 puts requests in both directions on bidirectional streams,
2596 /// so a client that only ever calls the helpers can never be published to
2597 /// or subscribed from.
2598 ///
2599 /// The returned [`RequestStream`] carries [`RequestOrigin::Peer`]. Answer
2600 /// it with [`respond_subscribe_ok`](Self::respond_subscribe_ok),
2601 /// [`respond_ok`](Self::respond_ok) or
2602 /// [`respond_error`](Self::respond_error), and **hold it for as long as
2603 /// the request lasts** — a subscription's PUBLISH_DONE is written on it,
2604 /// and dropping it resets the stream.
2605 ///
2606 /// # Two refusals, two codes
2607 ///
2608 /// Draft-23 Section 6.3, on a stream that begins with the wrong type:
2609 /// "Bidirectional streams MUST NOT begin with any other message type
2610 /// unless negotiated. If they do, the peer MUST close the Session with UNEXPECTED_MESSAGE."
2611 /// Section 6.4.2.1, on the Request ID: "If an endpoint
2612 /// receives a Request ID where the least significant bit is incorrect for the sender, it MUST
2613 /// close the session with INVALID_REQUEST_ID. If an endpoint detects a duplicate Request ID, it
2614 /// MUST close the session with INVALID_REQUEST_ID." Both are closes of the session on the wire,
2615 /// with
2616 /// different codes, and both happen before this returns — the error handed
2617 /// back reports a session that is already gone, not one the caller must
2618 /// remember to close.
2619 ///
2620 /// # Cancelling this future loses nothing
2621 ///
2622 /// A stream taken off the transport but not yet read is put back on an
2623 /// internal queue, and the next call takes it before accepting anything
2624 /// new — including whatever bytes of the request had already arrived,
2625 /// which live in the stream's own reader. So this is safe to `select!`
2626 /// against a shutdown signal or a timer. See
2627 /// [`pending_inbound_count`](Self::pending_inbound_count).
2628 ///
2629 /// What it is **not** safe to do is run concurrently with another method
2630 /// on the same connection: this takes `&mut self` because registering the
2631 /// peer's request moves endpoint state, and no signature avoids that while
2632 /// the connection owns the endpoint. A caller blocked in
2633 /// [`recv_on_request_stream`](Self::recv_on_request_stream) waiting for
2634 /// its own response is not accepting, and the peer's request streams queue
2635 /// up in the transport behind it. One loop that never blocks indefinitely
2636 /// on a single read is the shape this supports.
2637 ///
2638 /// # Ordering
2639 ///
2640 /// The endpoint is told about the request last, after every step that can
2641 /// fail or be cancelled, and building the handle afterwards cannot fail.
2642 /// This is the inverse of the outbound path's reasoning — it opens the
2643 /// stream before allocating a Request ID for the same reason — and rests
2644 /// on the same fact: the endpoint has no way to abandon a request it has
2645 /// already registered. Registering earlier would let a cancelled accept
2646 /// leave a state machine keyed to a stream nobody holds, and the peer's
2647 /// next use of that Request ID would then be reported as a duplicate — a
2648 /// session close, over an id the peer used exactly once.
2649 ///
2650 /// # Errors
2651 ///
2652 /// - [`ConnectionError::NonRequestOnRequestStream`] — the session has been
2653 /// closed with UNEXPECTED_MESSAGE and the stream reset.
2654 /// - [`ConnectionError::Endpoint`] carrying `RequestId` or
2655 /// `DuplicateRequestId` — the session has been closed with
2656 /// INVALID_REQUEST_ID and the stream reset.
2657 /// - [`ConnectionError::Endpoint`] carrying `NotActive` or `Draining` —
2658 /// the stream is reset, the session is left alone.
2659 /// - [`ConnectionError::Transport`] — the stream is reset, the session is
2660 /// left alone.
2661 /// - [`ConnectionError::Codec`] — the stream is reset, and the session is
2662 /// closed with the code [`Self::codec_session_error_code`] names, where it
2663 /// names one. Section 9: "Every message on a control or request stream is
2664 /// formatted as follows", so the control message rules hold here too.
2665 pub async fn accept_request_stream(
2666 &mut self,
2667 ) -> Result<(ControlMessage, RequestStream), ConnectionError> {
2668 let pair = match self.take_pending_inbound() {
2669 Some(pair) => pair,
2670 None => {
2671 let (send, recv) = self.transport.accept_bi().await?;
2672 (FramedSendStream::new(send, self.draft), FramedRecvStream::new(recv, self.draft))
2673 }
2674 };
2675 let capture_raw = self.observer.is_some();
2676
2677 let (any, raw, mut send, mut recv) = {
2678 let mut pending = PendingInbound { pair: Some(pair), queue: &self.pending_inbound };
2679 let read = {
2680 let (_, recv) = pending.pair.as_mut().expect("set on construction");
2681 recv.read_control_with_limit(capture_raw, self.recv_control_limit).await
2682 };
2683 // Taken out before anything can return, so the guard's Drop puts
2684 // the pair back for exactly one reason: this future was cancelled.
2685 let (mut send, mut recv) = pending.pair.take().expect("set on construction");
2686 match read {
2687 Ok((any, raw)) => (any, raw, send, recv),
2688 Err(e) => {
2689 // A stream whose first message could not be read is not
2690 // worth queueing: the next accept would fail on it the
2691 // same way. Reset rather than FIN — nothing was served.
2692 let _ = send.reset(REQUEST_UNANSWERED);
2693 let _ = recv.stop(REQUEST_UNANSWERED);
2694 let raw = recv.take_refused();
2695 let err = self.close_for_codec(e);
2696 self.emit_refused(&err, Some(send.stream_id()), raw);
2697 return Err(err);
2698 }
2699 }
2700 };
2701
2702 // Reported once the request has actually arrived rather than when the
2703 // stream came off the transport, so a cancelled accept that is retried
2704 // does not report the same stream twice.
2705 let stream_id = send.stream_id();
2706 self.emit(ClientEvent::StreamOpened {
2707 direction: Direction::Receive,
2708 stream_kind: StreamKind::Request,
2709 stream_id,
2710 });
2711 if capture_raw {
2712 self.emit(ClientEvent::ControlMessage {
2713 direction: Direction::Receive,
2714 message: any.clone(),
2715 stream_id: Some(stream_id),
2716 raw: raw.clone(),
2717 });
2718 }
2719
2720 let msg = match any {
2721 AnyControlMessage::Draft23(msg) => msg,
2722 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 23 the
2723 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
2724 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
2725 // naming the other drafts: such a list has to be edited in every draft module
2726 // whenever a draft is added, and a copy that omits one leaves this match
2727 // non-exhaustive.
2728 #[allow(unreachable_patterns)]
2729 _ => {
2730 let _ = send.reset(REQUEST_UNANSWERED);
2731 let _ = recv.stop(REQUEST_UNANSWERED);
2732 return Err(ConnectionError::ControlMessageNarrowing);
2733 }
2734 };
2735
2736 let ty = msg.message_type();
2737 let Some(kind) = RequestKind::from_message_type(ty) else {
2738 let err = self.endpoint.refuse_non_request(ty);
2739 self.close_for(&err);
2740 let _ = send.reset(REQUEST_UNANSWERED);
2741 let _ = recv.stop(REQUEST_UNANSWERED);
2742 let refused = ConnectionError::NonRequestOnRequestStream(ty);
2743 self.emit_refused(&refused, Some(stream_id), raw);
2744 return Err(refused);
2745 };
2746
2747 let request_id = match self.endpoint.receive_request_on_stream(&msg) {
2748 Ok(request_id) => request_id,
2749 Err(e) => {
2750 let _ = send.reset(REQUEST_UNANSWERED);
2751 let _ = recv.stop(REQUEST_UNANSWERED);
2752 let err = self.close_if_session_fatal(e);
2753 self.emit_refused(&err, Some(stream_id), raw);
2754 return Err(err);
2755 }
2756 };
2757
2758 Ok((
2759 msg,
2760 RequestStream {
2761 send,
2762 recv,
2763 request_id,
2764 kind,
2765 draft: self.draft,
2766 stream_id,
2767 cancelled: false,
2768 finished: false,
2769 origin: RequestOrigin::Peer,
2770 responded: false,
2771 fetch_data: None,
2772 // A peer's SUBSCRIBE does not establish a subscription until
2773 // it is accepted, so nothing is owed until
2774 // `respond_subscribe_ok` runs.
2775 owes_publish_done: false,
2776 },
2777 ))
2778 }
2779
2780 /// Take the oldest stream pair a cancelled
2781 /// [`accept_request_stream`](Self::accept_request_stream) put back, if any.
2782 ///
2783 /// Synchronous on purpose, like
2784 /// [`take_deferred_uni`](Self::take_deferred_uni): the guard is dropped
2785 /// before the caller awaits, so the lock is never held across a suspension
2786 /// point.
2787 fn take_pending_inbound(&self) -> Option<(FramedSendStream, FramedRecvStream)> {
2788 self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
2789 }
2790
2791 /// How many peer-opened request streams a cancelled
2792 /// [`accept_request_stream`](Self::accept_request_stream) put back and a
2793 /// later call has not yet taken.
2794 ///
2795 /// Zero unless an accept future was dropped mid-read.
2796 pub fn pending_inbound_count(&self) -> usize {
2797 self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
2798 }
2799
2800 // -- Answering the peer's requests ------------------------------
2801
2802 /// Write `msg` on the request stream `stream` carries, driving the
2803 /// endpoint first and the wire second.
2804 ///
2805 /// The response goes on the request's own bidirectional stream and never
2806 /// on the control stream: draft-23 responses carry no Request ID, so the
2807 /// stream is the only thing that says what is being answered. Taking the
2808 /// id off the handle rather than from the caller makes that correlation
2809 /// unforgeable.
2810 ///
2811 /// `fin` is true only for REQUEST_ERROR. See
2812 /// [`respond_error`](Self::respond_error).
2813 async fn respond(
2814 &mut self,
2815 stream: &mut RequestStream,
2816 msg: ControlMessage,
2817 fin: bool,
2818 ) -> Result<(), ConnectionError> {
2819 // A request this endpoint made is answered by the peer, with one
2820 // exception draft-23 Section 9.5 states outright: "A subscriber can
2821 // also send REQUEST_UPDATE to modify parameters of a subscription
2822 // established with PUBLISH", and the receiver of one "MUST respond with
2823 // exactly one REQUEST_UPDATE_OK or REQUEST_UPDATE_ERROR message
2824 // indicating if the update was successful" — Section 1.5's names for a
2825 // REQUEST_OK and a REQUEST_ERROR answering an update. On a PUBLISH this
2826 // endpoint sent, that receiver is this endpoint, so the one response it
2827 // may write on a stream of its own is the answer to an update waiting
2828 // there.
2829 let answers_an_update =
2830 matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_))
2831 && self.endpoint.has_unanswered_update(stream.request_id);
2832 if stream.origin != RequestOrigin::Peer && !answers_an_update {
2833 return Err(ConnectionError::RespondedToOwnRequest(stream.request_id.into_inner()));
2834 }
2835 // The endpoint first, so a response that does not fit the request's
2836 // state is refused before any of it reaches the wire. What this cannot
2837 // undo is a write that fails afterwards, which leaves the state
2838 // machine one step ahead of the peer — the same asymmetry
2839 // `begin_request` carries on the outbound side.
2840 self.endpoint.send_response_on_stream(stream.request_id, &msg)?;
2841 self.send_on_request_stream(stream, &msg).await?;
2842 stream.responded = true;
2843 // A REQUEST_ERROR answering an update retires nothing: the
2844 // subscription is live and Section 9.5 now asks for a PUBLISH_DONE
2845 // carrying UPDATE_FAILED. So neither the flag nor the send half is
2846 // cleared, and the obligation this draft already models goes on
2847 // standing until that message is written.
2848 // Section 9.5.1: "When a REQUEST_UPDATE fails for a FETCH, the
2849 // publisher MUST reset the FETCH data stream." A REQUEST_ERROR on a
2850 // fetch that has already been answered can only be answering an
2851 // update, because the request's own answer was the FETCH_OK; one
2852 // before that refuses the fetch itself, and there is no data stream
2853 // open to reset.
2854 if fin && stream.kind == RequestKind::Fetch && stream.responded {
2855 stream.reset_fetch_data();
2856 }
2857 if fin && !self.endpoint.owes_update_failure(stream.request_id) {
2858 // A rejected request owes nothing further, so the guard in
2859 // `finish` cannot be standing: clear the flag rather than let a
2860 // REQUEST_ERROR trip over an obligation the error just retired.
2861 stream.owes_publish_done = false;
2862 stream.finish().await?;
2863 }
2864 Ok(())
2865 }
2866
2867 /// Answer a peer's PUBLISH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE,
2868 /// SUBSCRIBE_TRACKS or TRACK_STATUS with REQUEST_OK.
2869 ///
2870 /// The one success message for every request kind but SUBSCRIBE: draft-23
2871 /// Section 9.3, "The REQUEST_OK message is sent in response to PUBLISH,
2872 /// FETCH, REQUEST_UPDATE, TRACK_STATUS, SUBSCRIBE_NAMESPACE,
2873 /// SUBSCRIBE_TRACKS and PUBLISH_NAMESPACE requests."
2874 ///
2875 /// The send half is left open. A SUBSCRIBE_NAMESPACE responder still owes
2876 /// the peer the namespaces it accepted, so finishing here would end the
2877 /// request before it had been served; a TRACK_STATUS or FETCH responder
2878 /// owes nothing further on this stream and may call
2879 /// [`RequestStream::finish`] straight after.
2880 ///
2881 /// # Errors
2882 ///
2883 /// [`ConnectionError::RespondedToOwnRequest`] if `stream` is one this
2884 /// endpoint opened, and [`ConnectionError::Endpoint`] if no request of a
2885 /// kind REQUEST_OK answers is pending on it — including the Section 9.3
2886 /// case of Track Properties on a response other than TRACK_STATUS_OK and
2887 /// FETCH_OK.
2888 /// Nothing is written in any of them.
2889 pub async fn respond_ok(
2890 &mut self,
2891 stream: &mut RequestStream,
2892 response: RequestOk,
2893 ) -> Result<(), ConnectionError> {
2894 self.respond(stream, ControlMessage::RequestOk(response), false).await
2895 }
2896
2897 /// Answer a peer's SUBSCRIBE with SUBSCRIBE_OK.
2898 ///
2899 /// The send half is left open, and it must be: this endpoint is now the
2900 /// publisher of an established subscription and owes it a PUBLISH_DONE,
2901 /// which travels on this same stream —
2902 /// [`publish_done_on`](Self::publish_done_on). Until that has been sent,
2903 /// [`RequestStream::finish`] refuses, per draft-23 Section 6.4.2.2.
2904 pub async fn respond_subscribe_ok(
2905 &mut self,
2906 stream: &mut RequestStream,
2907 response: SubscribeOk,
2908 ) -> Result<(), ConnectionError> {
2909 self.respond(stream, ControlMessage::SubscribeOk(response), false).await?;
2910 stream.owes_publish_done = true;
2911 Ok(())
2912 }
2913
2914 /// Reject a peer's request with REQUEST_ERROR, and finish the send half.
2915 ///
2916 /// The FIN is part of the act, not a convenience: draft-23 Section 6.4.2.3
2917 /// says "When an endpoint rejects a request without performing any
2918 /// application processing, it SHOULD send a REQUEST_ERROR and FIN the
2919 /// stream." It is also the one response that can be finished immediately,
2920 /// because a rejected request leaves nothing further to send — every
2921 /// success path owes the peer something more, which is what Section 6.4.2.2
2922 /// makes a MUST NOT for the rest of them.
2923 ///
2924 /// A finished handle does nothing further on [`Drop`], so the rejected
2925 /// stream is not then reset.
2926 pub async fn respond_error(
2927 &mut self,
2928 stream: &mut RequestStream,
2929 response: RequestError,
2930 ) -> Result<(), ConnectionError> {
2931 self.respond(stream, ControlMessage::RequestError(response), true).await
2932 }
2933
2934 /// End a subscription this endpoint accepted, on the stream the peer's
2935 /// SUBSCRIBE opened.
2936 ///
2937 /// The mirror of [`publish_done`](Self::publish_done), which ends a
2938 /// publication this endpoint offered with PUBLISH. Both write PUBLISH_DONE
2939 /// on a request stream and take the Request ID off the handle; they differ
2940 /// in which state machine moves, and therefore in which one refuses.
2941 ///
2942 /// The send half is finished once the message is out, for the reason
2943 /// [`publish_done`](Self::publish_done) gives: Section 6.4.2.2 names
2944 /// PUBLISH_DONE as the last required message on a publisher's direction and
2945 /// asks for the FIN promptly after it.
2946 pub async fn publish_done_on(
2947 &mut self,
2948 stream: &mut RequestStream,
2949 status_code: VarInt,
2950 stream_count: VarInt,
2951 reason_phrase: Vec<u8>,
2952 ) -> Result<(), ConnectionError> {
2953 let msg = ControlMessage::PublishDone(moqtap_codec::draft23::message::PublishDone {
2954 status_code,
2955 stream_count,
2956 reason_phrase,
2957 });
2958 self.respond(stream, msg, false).await?;
2959 stream.owes_publish_done = false;
2960 stream.finish().await
2961 }
2962
2963 /// Announce a namespace on a peer's SUBSCRIBE_NAMESPACE stream.
2964 ///
2965 /// Draft-23 Section 9.15 puts NAMESPACE "on the response stream of a
2966 /// SUBSCRIBE_NAMESPACE request", and Table 5 gives it the Stream value
2967 /// Request rather than Control — which is why this exists at all and why
2968 /// [`send_control`](Self::send_control) is the wrong route for it. The
2969 /// announcements follow the acceptance — Section 4.2: "On success, the
2970 /// publisher MUST send a NAMESPACE message for each namespace it knows
2971 /// that matches the Track Namespace Prefix" — so this refuses until
2972 /// [`respond_ok`](Self::respond_ok) has run on the same stream.
2973 pub async fn namespace_on(
2974 &mut self,
2975 stream: &mut RequestStream,
2976 response: Namespace,
2977 ) -> Result<(), ConnectionError> {
2978 self.respond(stream, ControlMessage::Namespace(response), false).await
2979 }
2980
2981 /// Withdraw a namespace on a peer's SUBSCRIBE_NAMESPACE stream.
2982 ///
2983 /// Section 4.2: "The publisher MUST NOT send NAMESPACE_DONE for a
2984 /// namespace suffix before the corresponding NAMESPACE." Which suffixes
2985 /// have been announced is the caller's bookkeeping, not this connection's,
2986 /// so what is enforced here is only that the request was accepted first.
2987 pub async fn namespace_done_on(
2988 &mut self,
2989 stream: &mut RequestStream,
2990 response: NamespaceDone,
2991 ) -> Result<(), ConnectionError> {
2992 self.respond(stream, ControlMessage::NamespaceDone(response), false).await
2993 }
2994
2995 /// Tell the peer a track matching its SUBSCRIBE_TRACKS will not be
2996 /// published, on that request's own stream.
2997 ///
2998 /// Section 9.18: "All PUBLISH_SKIPPED messages are in response to a
2999 /// SUBSCRIBE_TRACKS," which is why this refuses any other kind of stream —
3000 /// the endpoint looks the Request ID up among the SUBSCRIBE_TRACKS
3001 /// requests alone.
3002 pub async fn publish_skipped_on(
3003 &mut self,
3004 stream: &mut RequestStream,
3005 response: PublishSkipped,
3006 ) -> Result<(), ConnectionError> {
3007 self.respond(stream, ControlMessage::PublishSkipped(response), false).await
3008 }
3009
3010 // -- Subscribe flow ---------------------------------------------
3011
3012 /// Send a SUBSCRIBE on a bidirectional stream of its own.
3013 ///
3014 /// The returned [`RequestStream`] is where SUBSCRIBE_OK, REQUEST_ERROR
3015 /// and later PUBLISH_DONE arrive — read them with
3016 /// [`recv_on_request_stream`](Self::recv_on_request_stream). **Hold it for
3017 /// the subscription's life**: dropping it resets the stream, which
3018 /// cancels the subscription.
3019 pub async fn subscribe(
3020 &mut self,
3021 track_namespace: TrackNamespace,
3022 track_name: Vec<u8>,
3023 parameters: Vec<KeyValuePair>,
3024 ) -> Result<RequestStream, ConnectionError> {
3025 let mut halves = self.open_request_bi().await?;
3026 let (req_id, msg) = Self::or_abandon(
3027 &mut halves,
3028 self.endpoint.subscribe(track_namespace, track_name, parameters),
3029 )?;
3030 self.begin_request(halves, RequestKind::Subscribe, req_id, &msg).await
3031 }
3032
3033 // // Draft-23 has no UNSUBSCRIBE. Subscribers end a
3034 // subscription by resetting its request stream — `RequestStream::cancel`
3035 // — or wait for PublishDone.
3036
3037 // -- Fetch flow -------------------------------------------------
3038
3039 /// Send a FETCH on a bidirectional stream of its own.
3040 ///
3041 /// FETCH_OK or REQUEST_ERROR comes back on the returned
3042 /// [`RequestStream`]; the fetched objects arrive on separate
3043 /// unidirectional data streams. Dropping the handle cancels the fetch.
3044 ///
3045 /// # The range is a parameter now
3046 ///
3047 /// Draft-23 Section 9.11 has no `Fetch Type`, no Standalone or Joining structure, and no inline
3048 /// `Start Location` / `End Location` pair. The range travels in the `LOCATION_FILTER`
3049 /// parameter, so this takes the same three arguments
3050 /// [`subscribe`](Self::subscribe) does. [`fetch_range`](Self::fetch_range)
3051 /// is the same call with the filter built for you; a FETCH with no filter
3052 /// covers `{0,0}` through Largest Object, inclusive.
3053 pub async fn fetch(
3054 &mut self,
3055 track_namespace: TrackNamespace,
3056 track_name: Vec<u8>,
3057 parameters: Vec<KeyValuePair>,
3058 ) -> Result<RequestStream, ConnectionError> {
3059 let mut halves = self.open_request_bi().await?;
3060 let (req_id, msg) = Self::or_abandon(
3061 &mut halves,
3062 self.endpoint.fetch(track_namespace, track_name, parameters),
3063 )?;
3064 self.begin_request(halves, RequestKind::Fetch, req_id, &msg).await
3065 }
3066
3067 /// Send a FETCH for one range, carried as a `LOCATION_FILTER` parameter.
3068 ///
3069 /// **The range is inclusive at both ends** (draft-23 Section 3.2 and
3070 /// draft-23 Section 3.3.1), and nothing here adds or subtracts one.
3071 pub async fn fetch_range(
3072 &mut self,
3073 track_namespace: TrackNamespace,
3074 track_name: Vec<u8>,
3075 range: &LocationFilter,
3076 parameters: Vec<KeyValuePair>,
3077 ) -> Result<RequestStream, ConnectionError> {
3078 let mut halves = self.open_request_bi().await?;
3079 let (req_id, msg) = Self::or_abandon(
3080 &mut halves,
3081 self.endpoint.fetch_range(track_namespace, track_name, range, parameters),
3082 )?;
3083 self.begin_request(halves, RequestKind::Fetch, req_id, &msg).await
3084 }
3085
3086 // Draft-23 has no joining fetch. Section 9.11 has no Fetch Type field, no variant structures
3087 // and no joining mechanism, and
3088 // Section 3.4's fill fetch stream replaces it: a subscriber asks for the
3089 // Objects behind the live edge by putting a `FILL_PARAMETERS` parameter on
3090 // its SUBSCRIBE (or on a later REQUEST_UPDATE) rather than by opening a
3091 // second request. See [`accept_fill_stream`](Self::accept_fill_stream) for
3092 // the stream that answers it, and `crate::draft23::fill` for the parameter.
3093
3094 // // Draft-23 has no FETCH_CANCEL. Fetchers abort
3095 // with `RequestStream::cancel`, which resets the request stream.
3096
3097 // -- Namespace flows --------------------------------------------
3098
3099 /// Send a SUBSCRIBE_NAMESPACE on a bidirectional stream of its own.
3100 ///
3101 /// This call sends SUBSCRIBE_NAMESPACE (type 0x50), which subscribes to NAMESPACE /
3102 /// NAMESPACE_DONE announcements only. To receive PUBLISH messages for
3103 /// matching tracks, use [`Self::subscribe_tracks`] instead — a separate
3104 /// request on a request stream of its own.
3105 pub async fn subscribe_namespace(
3106 &mut self,
3107 namespace_prefix: TrackNamespace,
3108 parameters: Vec<KeyValuePair>,
3109 ) -> Result<RequestStream, ConnectionError> {
3110 let mut halves = self.open_request_bi().await?;
3111 let (req_id, msg) = Self::or_abandon(
3112 &mut halves,
3113 self.endpoint.subscribe_namespace(namespace_prefix, parameters),
3114 )?;
3115 self.begin_request(halves, RequestKind::SubscribeNamespace, req_id, &msg).await
3116 }
3117
3118 /// Send a SUBSCRIBE_TRACKS (type 0x51) on a bidirectional
3119 /// stream of its own. Causes the relay to PUBLISH matching tracks back to
3120 /// us.
3121 ///
3122 /// One of the seven request types draft-23 Section 6.3 lists.
3123 pub async fn subscribe_tracks(
3124 &mut self,
3125 namespace_prefix: TrackNamespace,
3126 parameters: Vec<KeyValuePair>,
3127 ) -> Result<RequestStream, ConnectionError> {
3128 let mut halves = self.open_request_bi().await?;
3129 let (req_id, msg) = Self::or_abandon(
3130 &mut halves,
3131 self.endpoint.subscribe_tracks(namespace_prefix, parameters),
3132 )?;
3133 self.begin_request(halves, RequestKind::SubscribeTracks, req_id, &msg).await
3134 }
3135
3136 /// Send a PUBLISH_NAMESPACE on a bidirectional stream of its own.
3137 pub async fn publish_namespace(
3138 &mut self,
3139 track_namespace: TrackNamespace,
3140 parameters: Vec<KeyValuePair>,
3141 ) -> Result<RequestStream, ConnectionError> {
3142 let mut halves = self.open_request_bi().await?;
3143 let (req_id, msg) = Self::or_abandon(
3144 &mut halves,
3145 self.endpoint.publish_namespace(track_namespace, parameters),
3146 )?;
3147 self.begin_request(halves, RequestKind::PublishNamespace, req_id, &msg).await
3148 }
3149
3150 // -- Track Status flow ------------------------------------------
3151
3152 /// Send a TRACK_STATUS on a bidirectional stream of its own.
3153 pub async fn track_status(
3154 &mut self,
3155 track_namespace: TrackNamespace,
3156 track_name: Vec<u8>,
3157 parameters: Vec<KeyValuePair>,
3158 ) -> Result<RequestStream, ConnectionError> {
3159 let mut halves = self.open_request_bi().await?;
3160 let (req_id, msg) = Self::or_abandon(
3161 &mut halves,
3162 self.endpoint.track_status(track_namespace, track_name, parameters),
3163 )?;
3164 self.begin_request(halves, RequestKind::TrackStatus, req_id, &msg).await
3165 }
3166
3167 // -- Publish flow (publisher side) ------------------------------
3168
3169 /// Send a PUBLISH on a bidirectional stream of its own.
3170 ///
3171 /// REQUEST_OK — which is what draft-23 answers a PUBLISH with, PUBLISH_OK
3172 /// having been folded into it — or REQUEST_ERROR comes back on the
3173 /// returned [`RequestStream`], and [`publish_done`](Self::publish_done) is
3174 /// written back on it when the publication ends, so the handle must be
3175 /// held for as long as the publication lasts.
3176 pub async fn publish(
3177 &mut self,
3178 track_namespace: TrackNamespace,
3179 track_name: Vec<u8>,
3180 track_alias: VarInt,
3181 parameters: Vec<KeyValuePair>,
3182 track_properties: Vec<KeyValuePair>,
3183 ) -> Result<RequestStream, ConnectionError> {
3184 let mut halves = self.open_request_bi().await?;
3185 let (req_id, msg) = Self::or_abandon(
3186 &mut halves,
3187 self.endpoint.publish(
3188 track_namespace,
3189 track_name,
3190 track_alias,
3191 parameters,
3192 track_properties,
3193 ),
3194 )?;
3195 self.begin_request(halves, RequestKind::Publish, req_id, &msg).await
3196 }
3197
3198 /// Send a PUBLISH_DONE on the request stream the PUBLISH opened.
3199 ///
3200 /// PUBLISH_DONE is a response and carries no request id on the wire, so
3201 /// the stream is the only thing that says which publication ended. The id
3202 /// the endpoint needs is taken off `stream`, which makes the correlation
3203 /// unforgeable — there is no way to name one request and write on
3204 /// another's stream.
3205 ///
3206 /// The send half is finished once the message is out. Draft-23 Section
3207 /// 6.4.2.2 names PUBLISH_DONE as the last required message on a publisher's
3208 /// direction — "the publisher of an Established subscription MUST send
3209 /// PUBLISH_DONE, before sending a FIN" — and asks for the FIN promptly
3210 /// afterwards, since nothing further is owed on that direction. Leaving it
3211 /// open instead would end the publication at [`Drop`], which resets the
3212 /// stream with REQUEST_CANCELLED and tells the peer the request was
3213 /// abandoned rather than completed.
3214 ///
3215 /// The receive half stays open. A FIN closes one direction, so the
3216 /// subscriber can still be heard from on its own.
3217 pub async fn publish_done(
3218 &mut self,
3219 stream: &mut RequestStream,
3220 status_code: VarInt,
3221 stream_count: VarInt,
3222 reason_phrase: Vec<u8>,
3223 ) -> Result<(), ConnectionError> {
3224 let request_id = stream.request_id();
3225 let msg = self.endpoint.send_publish_done(
3226 request_id,
3227 status_code,
3228 stream_count,
3229 reason_phrase,
3230 )?;
3231 self.send_on_request_stream(stream, &msg).await?;
3232 stream.owes_publish_done = false;
3233 stream.finish().await
3234 }
3235
3236 // -- Data streams -----------------------------------------------
3237
3238 /// Open a new unidirectional stream for sending subgroup data.
3239 pub async fn open_subgroup_stream(
3240 &self,
3241 header: &AnySubgroupHeader,
3242 ) -> Result<FramedSendStream, ConnectionError> {
3243 let send = self.transport.open_uni().await?;
3244 let mut framed = FramedSendStream::new(send, self.draft);
3245 let sid = framed.stream_id();
3246 framed.write_subgroup_header(header).await?;
3247 self.emit(ClientEvent::StreamOpened {
3248 direction: Direction::Send,
3249 stream_kind: StreamKind::Subgroup,
3250 stream_id: sid,
3251 });
3252 self.emit(ClientEvent::DataStreamHeader {
3253 stream_id: sid,
3254 direction: Direction::Send,
3255 header: header.clone(),
3256 });
3257 Ok(framed)
3258 }
3259
3260 /// Open a new unidirectional stream for sending a FETCH's objects.
3261 ///
3262 /// The objects answering a FETCH do not go on the request's own stream:
3263 /// they go on a unidirectional stream of their own, which opens with a
3264 /// FETCH_HEADER naming the request they belong to. This writes that header
3265 /// and hands back the stream, the same way
3266 /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
3267 /// subgroup.
3268 ///
3269 /// The caller owns the stream that comes back. Nothing here remembers
3270 /// which request it belongs to, so an endpoint serving several fetches at
3271 /// once keeps its own map from Request ID to stream.
3272 pub async fn open_fetch_stream(
3273 &self,
3274 header: &AnyFetchHeader,
3275 ) -> Result<FramedSendStream, ConnectionError> {
3276 let send = self.transport.open_uni().await?;
3277 let mut framed = FramedSendStream::new(send, self.draft);
3278 let sid = framed.stream_id();
3279 framed.write_fetch_header(header).await?;
3280 self.emit(ClientEvent::StreamOpened {
3281 direction: Direction::Send,
3282 stream_kind: StreamKind::Fetch,
3283 stream_id: sid,
3284 });
3285 Ok(framed)
3286 }
3287
3288 /// Open the data stream for a FETCH this endpoint is answering, and keep
3289 /// the handle on the request.
3290 ///
3291 /// The same stream [`open_fetch_stream`](Self::open_fetch_stream) returns,
3292 /// parked on the request stream it belongs to. That is what lets a rule
3293 /// about the fetch reach the objects it is serving: a refused
3294 /// REQUEST_UPDATE has to reset this stream, and the connection cannot
3295 /// reset a handle the caller walked away with.
3296 ///
3297 /// Write objects through
3298 /// [`RequestStream::fetch_data`](RequestStream::fetch_data), or through
3299 /// the borrow this returns.
3300 pub async fn open_fetch_stream_on<'s>(
3301 &self,
3302 stream: &'s mut RequestStream,
3303 header: &AnyFetchHeader,
3304 ) -> Result<&'s mut FramedSendStream, ConnectionError> {
3305 let framed = self.open_fetch_stream(header).await?;
3306 stream.fetch_data = Some(framed);
3307 Ok(stream.fetch_data.as_mut().expect("just stored"))
3308 }
3309
3310 /// Accept an incoming unidirectional data stream and read its subgroup
3311 /// header.
3312 ///
3313 /// Streams the peer opened before its control stream are returned first,
3314 /// in arrival order, before any new one is accepted from the transport:
3315 /// [`connect`](Self::connect) had to look at them to find the control
3316 /// stream and set the rest aside rather than drop them. They are
3317 /// otherwise ordinary — the type varint `connect` read is still on the
3318 /// front of each one.
3319 ///
3320 /// A FETCH_HEADER stream, fetch or fill, is never read as a subgroup: it
3321 /// is set aside for [`accept_fetch_stream`](Self::accept_fetch_stream)
3322 /// and [`accept_fill_stream`](Self::accept_fill_stream), and this waits
3323 /// for the next stream.
3324 pub async fn accept_subgroup_stream(
3325 &self,
3326 ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
3327 let mut framed = self.next_non_fetch_uni().await?;
3328 let sid = framed.stream_id();
3329 let header = framed.read_subgroup_header().await?;
3330 self.emit(ClientEvent::StreamOpened {
3331 direction: Direction::Receive,
3332 stream_kind: StreamKind::Subgroup,
3333 stream_id: sid,
3334 });
3335 self.emit(ClientEvent::DataStreamHeader {
3336 stream_id: sid,
3337 direction: Direction::Receive,
3338 header: header.clone(),
3339 });
3340 // The track is resolved here and not inside the stream: it takes the
3341 // endpoint's alias table, which a stream handle has no way back to.
3342 // Handed over rather than offered, so measuring is not something a
3343 // caller has to remember to ask for.
3344 if let (Some(objects), Some(existence)) = (
3345 self.endpoint.track_objects(header.track_alias()),
3346 self.endpoint.track_existence(header.track_alias()),
3347 ) {
3348 framed.measure_objects_against(objects, existence, header.group_id());
3349 }
3350 Ok((header, framed))
3351 }
3352
3353 /// Accept the next unidirectional stream and read its fetch header.
3354 ///
3355 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
3356 /// are separate because the header decides how every object after it is
3357 /// framed, so a caller has to know which it is expecting before the first
3358 /// byte is read.
3359 ///
3360 /// Objects come off the returned stream with
3361 /// [`FramedRecvStream::read_fetch_object`].
3362 ///
3363 /// FETCH_HEADER streams another accept set aside come first, in arrival
3364 /// order. A stream of any other type that arrives while this waits is
3365 /// set aside for [`accept_subgroup_stream`](Self::accept_subgroup_stream).
3366 pub async fn accept_fetch_stream(
3367 &self,
3368 ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
3369 let mut framed = self.next_fetch_uni().await?;
3370 let sid = framed.stream_id();
3371 let header = framed.read_fetch_header().await?;
3372 self.emit(ClientEvent::StreamOpened {
3373 direction: Direction::Receive,
3374 stream_kind: StreamKind::Fetch,
3375 stream_id: sid,
3376 });
3377 self.emit(ClientEvent::FetchStreamHeader {
3378 stream_id: sid,
3379 direction: Direction::Receive,
3380 header: header.clone(),
3381 });
3382 // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
3383 // carries an `AnySubgroupHeader` and cannot express one.
3384 framed.fetch_existence = self.endpoint.fetch_existence(header.request_id());
3385 Ok((header, framed))
3386 }
3387
3388 /// Accept the next unidirectional stream as a **fill fetch stream** (Section 3.4).
3389 ///
3390 /// A fill fetch stream is framed exactly as a fetch response — a
3391 /// FETCH_HEADER and the fetch object records after it, read with
3392 /// [`FramedRecvStream::read_fetch_object`] — and differs only in what its
3393 /// Request ID names: the SUBSCRIBE (or the REQUEST_UPDATE, which travels
3394 /// under the same Request ID) that asked for the fill, rather than a FETCH.
3395 /// So this is [`accept_fetch_stream`](Self::accept_fetch_stream) with the
3396 /// attribution done: the endpoint is asked whether that request asked for a
3397 /// fill, the stream is counted against the subscription, and the event goes
3398 /// out under [`StreamKind::Fill`].
3399 ///
3400 /// # The object reader is already started
3401 ///
3402 /// Unlike [`accept_fetch_stream`](Self::accept_fetch_stream), the returned
3403 /// stream needs no
3404 /// [`begin_fetch_objects`](FramedRecvStream::begin_fetch_objects) call:
3405 /// objects can be read straight off it. A fetch stream cannot be started
3406 /// here because the Group Order lives on the FETCH the caller sent and
3407 /// nothing on this side holds it; a **fill** stream can, because the
3408 /// endpoint kept the order when the subscription asked for the fill — see
3409 /// [`Endpoint::fill_group_order`](crate::draft23::endpoint::Endpoint::fill_group_order)
3410 /// and [`fill::group_order`](crate::draft23::fill::group_order) for the
3411 /// three-step resolution Section 3.4 and Section 9.19.8 define.
3412 ///
3413 /// Doing it here rather than leaving it to the caller is the difference
3414 /// between an error and a wrong answer: a reader started in the wrong
3415 /// direction still parses every frame, and reports Group IDs that walk the
3416 /// wrong way. A caller that has since learned better restarts it by calling
3417 /// `begin_fetch_objects` itself before reading the first object.
3418 ///
3419 /// # What ends it, and what that does not do
3420 ///
3421 /// Section 3.4.1: a FIN means the whole fill range was delivered, and a
3422 /// reset means the fill failed — "Because there is no REQUEST_ERROR
3423 /// associated with a fill fetch stream, the publisher signals a fill
3424 /// failure by resetting the stream". Neither touches the subscription:
3425 /// "Resetting or cancelling a fill fetch stream, by either endpoint, does
3426 /// not affect the subscription, which continues to deliver objects using
3427 /// subscribe subgroups and datagrams." Tell the endpoint which happened
3428 /// with [`Endpoint::on_fill_stream_ended`](crate::draft23::endpoint::Endpoint::on_fill_stream_ended);
3429 /// a subscriber that wants to stop one early sends `STOP_SENDING` with
3430 /// [`FramedRecvStream::stop`], which Section 3.4.1 gives it outright.
3431 ///
3432 /// # Errors
3433 ///
3434 /// [`ConnectionError::Endpoint`] carrying
3435 /// [`EndpointError::UnrequestedFillStream`] when the header names a request
3436 /// that asked for no fill. Not fatal to the session — draft-23 states no
3437 /// rule for the case, and the stream is the only thing wrong.
3438 ///
3439 /// # Which stream it takes
3440 ///
3441 /// The same one [`accept_fetch_stream`](Self::accept_fetch_stream)
3442 /// would: a FETCH_HEADER stream another accept set aside, else the next
3443 /// one to arrive. Streams of any other type are set aside for
3444 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream).
3445 pub async fn accept_fill_stream(
3446 &mut self,
3447 ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
3448 let mut framed = self.next_fetch_uni().await?;
3449 let sid = framed.stream_id();
3450 let header = framed.read_fetch_header().await?;
3451 let request_id = VarInt::from_u64(header.request_id())
3452 .map_err(|e| ConnectionError::Codec(CodecError::VarInt(e)))?;
3453 self.endpoint.on_fill_stream_opened(request_id)?;
3454 // `on_fill_stream_opened` returned Ok, so the request asked for a fill
3455 // and the entry that answers this exists; the fallback is what a future
3456 // caller that split the two lookups would get, and it is the same
3457 // Section 9.19.8 default `fill::group_order` applies.
3458 let group_order =
3459 self.endpoint.fill_group_order(request_id).unwrap_or(GroupOrder::Ascending);
3460 framed.begin_fetch_objects(group_order);
3461 self.emit(ClientEvent::StreamOpened {
3462 direction: Direction::Receive,
3463 stream_kind: StreamKind::Fill,
3464 stream_id: sid,
3465 });
3466 self.emit(ClientEvent::FetchStreamHeader {
3467 stream_id: sid,
3468 direction: Direction::Receive,
3469 header: header.clone(),
3470 });
3471 Ok((header, framed))
3472 }
3473
3474 /// Take the oldest stream [`connect`](Self::connect) set aside, if any.
3475 ///
3476 /// Synchronous on purpose: the guard is dropped before the caller awaits,
3477 /// so the lock is never held across a suspension point. A poisoned lock
3478 /// is recovered rather than propagated — nothing here can leave the queue
3479 /// in a state a later reader could be misled by, since the only mutations
3480 /// are a `push_back` and a `pop_front`.
3481 fn take_deferred_uni(&self) -> Option<FramedRecvStream> {
3482 self.deferred_uni.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
3483 }
3484
3485 /// Take the oldest FETCH_HEADER stream set aside, if any. Synchronous for
3486 /// the reason [`take_deferred_uni`](Self::take_deferred_uni) is.
3487 fn take_deferred_fetch(&self) -> Option<FramedRecvStream> {
3488 self.deferred_fetch.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
3489 }
3490
3491 fn set_aside(queue: &Mutex<VecDeque<FramedRecvStream>>, framed: FramedRecvStream) {
3492 queue.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).push_back(framed);
3493 }
3494
3495 /// The next stream that is not a FETCH_HEADER stream: a set-aside one
3496 /// first, then the transport's. FETCH_HEADER streams met on the way are
3497 /// set aside. A stream whose type cannot be read is returned, so that the
3498 /// header read reports why.
3499 async fn next_non_fetch_uni(&self) -> Result<FramedRecvStream, ConnectionError> {
3500 loop {
3501 let mut framed = match self.take_deferred_uni() {
3502 Some(framed) => framed,
3503 None => FramedRecvStream::new(self.transport.accept_uni().await?, self.draft),
3504 };
3505 match framed.peek_stream_type().await {
3506 Ok(FETCH_STREAM_TYPE) => Self::set_aside(&self.deferred_fetch, framed),
3507 _ => return Ok(framed),
3508 }
3509 }
3510 }
3511
3512 /// The next FETCH_HEADER stream: a set-aside one first, then the
3513 /// transport's. Streams of other types met on the way are set aside. A
3514 /// stream whose type cannot be read is returned, so that the header read
3515 /// reports why.
3516 async fn next_fetch_uni(&self) -> Result<FramedRecvStream, ConnectionError> {
3517 if let Some(framed) = self.take_deferred_fetch() {
3518 return Ok(framed);
3519 }
3520 loop {
3521 let mut framed = FramedRecvStream::new(self.transport.accept_uni().await?, self.draft);
3522 match framed.peek_stream_type().await {
3523 Ok(stream_type) if stream_type != FETCH_STREAM_TYPE => {
3524 Self::set_aside(&self.deferred_uni, framed)
3525 }
3526 _ => return Ok(framed),
3527 }
3528 }
3529 }
3530
3531 /// How many unidirectional streams have been set aside and not yet handed
3532 /// back: those [`connect`](Self::connect) met before the control stream,
3533 /// and those an accept met that were for another accept.
3534 ///
3535 /// Zero for a peer that opened its control stream first and a caller
3536 /// that accepted each stream as the kind it is, which is the ordinary
3537 /// case.
3538 pub fn deferred_stream_count(&self) -> usize {
3539 self.deferred_uni.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
3540 + self.deferred_fetch.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
3541 }
3542
3543 /// Send an object via datagram.
3544 ///
3545 /// A header whose object status the framing cannot carry is refused rather
3546 /// than sent. A datagram writes a status field only when its type byte sets
3547 /// the STATUS bit, so a header carrying End of Group under a type byte
3548 /// without that bit would go out as an ordinary payload object with the
3549 /// marker missing — a datagram the peer cannot tell from one that never had
3550 /// a status. `AnyDatagramHeader::encode` answers that with
3551 /// `CodecError::InvalidField` and writes nothing, on every draft.
3552 pub fn send_datagram(
3553 &self,
3554 header: &AnyDatagramHeader,
3555 payload: &[u8],
3556 ) -> Result<(), ConnectionError> {
3557 let mut buf = Vec::new();
3558 header.encode(&mut buf)?;
3559 buf.extend_from_slice(payload);
3560 self.emit(ClientEvent::DatagramReceived {
3561 direction: Direction::Send,
3562 header: header.clone(),
3563 payload_len: payload.len(),
3564 });
3565 self.transport.send_datagram(bytes::Bytes::from(buf))?;
3566 Ok(())
3567 }
3568
3569 /// Receive a datagram and decode it whole.
3570 ///
3571 /// Errors with [`ConnectionError::Codec`], carrying
3572 /// `CodecError::MalformedObject`, on a datagram with the PROPERTIES bit set
3573 /// and a Properties Length of 0, which
3574 /// [`Connection::close_for_data_stream`] closes the session over with
3575 /// MALFORMED_OBJECT.
3576 ///
3577 /// Errors with [`ConnectionError::PropertiesOnNonNormalStatus`] on a
3578 /// draft-23 datagram carrying properties on a status other than Normal.
3579 /// Draft-23 Section 11.2.1 builds the datagram's Properties field out of
3580 /// the structure defined in Section 11.1.2, and that section answers the
3581 /// combination with a session close — the same rule the subgroup form
3582 /// obeys, on the same receive side.
3583 ///
3584 /// A payload after a status is returned as
3585 /// [`ConnectionError::PayloadOnStatusDatagram`], and the session stays open.
3586 /// The codec's `DatagramHeader::decode_object` then reads the whole
3587 /// datagram. A Prior Group ID Gap or Prior Object ID Gap fault, and a
3588 /// property block that does not parse, are returned as
3589 /// [`ConnectionError::MalformedTrackDatagram`], for
3590 /// [`Connection::requests_to_cancel`]; its other refusals as
3591 /// [`ConnectionError::Codec`].
3592 ///
3593 /// The event is emitted before the checks, so an observer of the client's
3594 /// event stream still sees the datagram that caused the error.
3595 pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
3596 let data = self.transport.recv_datagram().await?;
3597 let mut cursor = &data[..];
3598 let header = AnyDatagramHeader::decode(self.draft, &mut cursor)?;
3599 let consumed = data.len() - cursor.len();
3600 let payload = data.slice(consumed..);
3601 self.emit(ClientEvent::DatagramReceived {
3602 direction: Direction::Receive,
3603 header: header.clone(),
3604 payload_len: payload.len(),
3605 });
3606 // Refutable only in a build with more than one draft enabled;
3607 // in a single-draft build `AnyDatagramHeader` has one variant.
3608 #[allow(irrefutable_let_patterns)]
3609 if let AnyDatagramHeader::Draft23(h) = &header {
3610 // draft-23 Section 11.2.1: "If an endpoint receives a datagram with
3611 // the PROPERTIES bit set and an Properties Length of 0, it MUST close
3612 // the session with MALFORMED_OBJECT."
3613 if !h.properties_block_well_formed() {
3614 return Err(ConnectionError::Codec(CodecError::MalformedObject {
3615 detail: "a datagram with the PROPERTIES bit set has a Properties Length of 0",
3616 }));
3617 }
3618 // draft-23 Section 11.2.1: "If an Object Datagram includes both the
3619 // STATUS bit and PROPERTIES bit, and the Object Status is not Normal
3620 // (0x0), the endpoint MUST close the session with
3621 // INVALID_OBJECT_STATUS, because only Normal Objects can have
3622 // Properties."
3623 if !h.properties_permitted() {
3624 return Err(ConnectionError::PropertiesOnNonNormalStatus {
3625 object_id: h.object_id.into_inner(),
3626 properties_len: h.properties.len(),
3627 status: h.status(),
3628 });
3629 }
3630 if !h.permits_payload() && !payload.is_empty() {
3631 return Err(ConnectionError::PayloadOnStatusDatagram {
3632 object_id: h.object_id.into_inner(),
3633 payload_len: payload.len(),
3634 status: h.object_status,
3635 });
3636 }
3637 // The codec's whole-datagram read, for the rules a header alone
3638 // does not show. A gap fault is returned with the datagram's alias,
3639 // for `requests_to_cancel`.
3640 let whole =
3641 moqtap_codec::draft23::data_stream::DatagramHeader::decode_object(&mut &data[..]);
3642 match whole {
3643 Ok(_) => {}
3644 Err(
3645 error @ (CodecError::MalformedTrack { .. }
3646 | CodecError::MalformedObjectProperties { .. }),
3647 ) => {
3648 return Err(ConnectionError::MalformedTrackDatagram {
3649 track_alias: h.track_alias.into_inner(),
3650 error,
3651 });
3652 }
3653 Err(error) => return Err(ConnectionError::Codec(error)),
3654 }
3655 }
3656 // A datagram is a whole object, so the connection can measure it
3657 // without help from the caller. It cannot *answer* the condition,
3658 // though: the answer is a reset of a request stream the caller holds,
3659 // so both data paths report and neither withdraws - see
3660 // `Connection::requests_to_cancel`.
3661 let meta = header.meta();
3662 // Refutable only in a build with more than one draft enabled.
3663 #[allow(irrefutable_let_patterns)]
3664 let properties: &[u8] =
3665 if let AnyDatagramHeader::Draft23(h) = &header { &h.properties } else { &[] };
3666 self.endpoint.note_received_object(
3667 meta.track_alias,
3668 ObjectLocation { group: meta.group_id, object: meta.object_id },
3669 object_role(meta.status),
3670 properties,
3671 )?;
3672 Ok((header, payload))
3673 }
3674
3675 // -- Accessors --------------------------------------------------
3676
3677 /// Access the underlying endpoint state machine.
3678 pub fn endpoint(&self) -> &Endpoint {
3679 &self.endpoint
3680 }
3681
3682 /// Mutable access to the endpoint state machine.
3683 pub fn endpoint_mut(&mut self) -> &mut Endpoint {
3684 &mut self.endpoint
3685 }
3686
3687 /// The SETUP message the server answered the handshake with.
3688 ///
3689 /// [`AnyControlMessage::fields`] renders it under
3690 /// this draft's own parameter names, in the order they arrived.
3691 pub fn server_setup(&self) -> &AnyControlMessage {
3692 &self.server_setup
3693 }
3694
3695 /// The framed wire bytes of [`Self::server_setup`], as they arrived.
3696 ///
3697 /// Kept beside the decoded form because the encoding is evidence the
3698 /// decoding discards: two relays sending the same parameter can still
3699 /// disagree on how wide a varint they wrote it in.
3700 pub fn server_setup_raw(&self) -> Option<&[u8]> {
3701 self.server_setup_raw.as_deref()
3702 }
3703
3704 /// Returns the draft version this connection is using.
3705 pub fn draft(&self) -> DraftVersion {
3706 self.draft
3707 }
3708
3709 /// Close the session on the wire when the endpoint says a violation is
3710 /// fatal to it, and hand the error back unchanged.
3711 ///
3712 /// [`EndpointError::session_error_code`] answers `Some` for exactly the
3713 /// errors draft-23 tells the receiver to close the session over, and the
3714 /// endpoint has already moved its own state machine to Closed by the time
3715 /// this runs. Without this step that move is purely internal: the local
3716 /// endpoint refuses to start anything new while the peer, which is the one
3717 /// that broke the rule, sees a session that is still open and goes on
3718 /// sending. "MUST close the session" is a statement about the wire, so it takes a
3719 /// CONNECTION_CLOSE to satisfy it.
3720 ///
3721 /// The reason phrase is the error's own `Display` text, which names the
3722 /// message and the rule rather than repeating the numeric code the close
3723 /// already carries.
3724 ///
3725 /// Errors that answer `None` are recoverable and nothing is sent.
3726 fn close_for(&self, err: &EndpointError) {
3727 if let Some(code) = err.session_error_code() {
3728 // QUIC application error codes are 62-bit; every code in this
3729 // registry is far below `u32::MAX`, and saturating rather than
3730 // truncating means a future code that is not could never be
3731 // reported as a different, assigned one.
3732 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
3733 self.close(wire_code, err.to_string().as_bytes());
3734 }
3735 }
3736
3737 /// [`close_for`](Self::close_for), then the error unchanged, for the
3738 /// common case where the endpoint's error is also what the caller returns.
3739 fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
3740 self.close_for(&err);
3741 ConnectionError::Endpoint(err)
3742 }
3743
3744 /// Which of this draft's *own* `ConnectionError` variants this error is,
3745 /// and which kind of thing it says.
3746 ///
3747 /// The ten every draft carries answer `None` here: [`AnyConnectionError`]
3748 /// classifies those itself, once, and never asks a draft about them. What
3749 /// is left splits two ways, and the split is the reason this function
3750 /// exists — before it, both halves reached a caller as a sentence and read
3751 /// exactly alike. A [`LocalRefusal`] is this endpoint declining to write
3752 /// something, so nothing reached the wire and no relay is implicated; a
3753 /// [`PeerViolation`] is a peer having done something draft-23 forbids, and
3754 /// carries the session error code draft-23's own text answers it with.
3755 ///
3756 /// Matched exhaustively, with no wildcard arm and deliberately so: a
3757 /// variant added to this draft's error type has to arrive here as a compile
3758 /// error, beside the doc comment quoting the sentence it enforces, rather
3759 /// than as a silent [`ErrorCause::Unclassified`] in the facade.
3760 ///
3761 /// [`AnyConnectionError`]: crate::dispatch::AnyConnectionError
3762 /// [`ErrorCause::Unclassified`]: crate::dispatch::ErrorCause::Unclassified
3763 /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
3764 /// [`PeerViolation`]: crate::above_codec_rules::DraftSpecificCause::PeerViolation
3765 pub fn draft_specific_cause(
3766 err: &ConnectionError,
3767 ) -> Option<crate::above_codec_rules::DraftSpecificCause> {
3768 use crate::above_codec_rules::{AboveCodecRule, DraftSpecificCause};
3769 use moqtap_codec::draft23::error_codes::SessionErrorCode;
3770
3771 match err {
3772 ConnectionError::Endpoint(_)
3773 | ConnectionError::Codec(_)
3774 | ConnectionError::Transport(_)
3775 | ConnectionError::VarInt(_)
3776 | ConnectionError::NoControlStream
3777 | ConnectionError::UnexpectedEnd
3778 | ConnectionError::StreamFinished
3779 | ConnectionError::InvalidAddress(_)
3780 | ConnectionError::TlsConfig(_)
3781 | ConnectionError::DataStreamState(_) => None,
3782 // This build decoding a message and then failing to narrow it to
3783 // its own draft. Nothing reached the wire and no peer is
3784 // implicated, which is the whole reason it is not
3785 // `ConnectionError::Codec`: under that name it would carry
3786 // a close code out of `codec_session_error_code` and
3787 // publish a relay for this build's defect. See the variant's own
3788 // doc.
3789 ConnectionError::ControlMessageNarrowing => {
3790 Some(crate::above_codec_rules::DraftSpecificCause::LocalRefusal)
3791 }
3792 // Section 11.1.2 states the rule and names the code in the same
3793 // sentence. The codec refuses such an Object only as a bare
3794 // `InvalidField`, so this layer is the one that names the rule,
3795 // and `close_for_data_stream` performs the close by
3796 // reading this same answer.
3797 ConnectionError::PropertiesOnNonNormalStatus { .. } => {
3798 Some(DraftSpecificCause::PeerViolation {
3799 rule: AboveCodecRule::PropertiesOnNonNormalStatus,
3800 close: Some(SessionErrorCode::InvalidObjectStatus.as_u64()),
3801 })
3802 }
3803 // Section 11.3: "the receiver SHOULD close the session with
3804 // TRUNCATED_OBJECT". `close_for_data_stream` reads this answer.
3805 ConnectionError::TruncatedObject => Some(DraftSpecificCause::PeerViolation {
3806 rule: AboveCodecRule::TruncatedObject,
3807 close: Some(SessionErrorCode::TruncatedObject.as_u64()),
3808 }),
3809 // The codec's refusal, carried with the alias its error does
3810 // not name. draft-23 Section 12.1 answers a Malformed Track by
3811 // cancelling requests, and `codec_session_error_code` names no
3812 // close for it.
3813 ConnectionError::MalformedTrackDatagram { error, .. } => {
3814 Some(DraftSpecificCause::Codec {
3815 close: Self::codec_session_error_code(error).map(|code| code.as_u64()),
3816 error: error.clone(),
3817 })
3818 }
3819 // Section 11.1.1 states this one as a property of a conforming
3820 // Object rather than as one of the cases a draft answers with a
3821 // close. The datagram is refused and the session is left running.
3822 // `close: None` is that reading, and it is what keeps a relay from
3823 // being published for a rule its draft attaches no consequence to.
3824 ConnectionError::PayloadOnStatusDatagram { .. } => {
3825 Some(DraftSpecificCause::PeerViolation {
3826 rule: AboveCodecRule::PayloadOnStatusDatagram,
3827 close: None,
3828 })
3829 }
3830 // Section 6.3: "Bidirectional streams MUST NOT begin with any
3831 // other message type unless negotiated. If they do, the peer MUST
3832 // close the Session with UNEXPECTED_MESSAGE." The session has
3833 // already been closed on the wire by the time this is returned, so
3834 // the code is carried here for a caller to read which rule was
3835 // answered, not for it to answer one again.
3836 ConnectionError::NonRequestOnRequestStream(_) => {
3837 Some(DraftSpecificCause::PeerViolation {
3838 rule: AboveCodecRule::BidiStreamOpener,
3839 close: Some(SessionErrorCode::UnexpectedMessage.as_u64()),
3840 })
3841 }
3842 // Three ways of handing this endpoint a message it will not write,
3843 // and one answer: nothing reached the wire, so nothing here is
3844 // evidence about a peer. Two are messages put on the control stream
3845 // that belong on a request stream of their own; the third is a
3846 // `respond_*` helper pointed at a request this endpoint opened,
3847 // which only the endpoint a request was opened *toward* may answer.
3848 ConnectionError::RequestOnControlStream(_)
3849 | ConnectionError::RequestStreamMessageOnControlStream(_)
3850 | ConnectionError::RespondedToOwnRequest(_) => Some(DraftSpecificCause::LocalRefusal),
3851 // Section 6.4.2.2 forbids a FIN before the response a request type
3852 // requires, and before PUBLISH_DONE on an established
3853 // subscription. Both are raised when *this* endpoint asks for the
3854 // FIN, so both are refusals rather than findings: the draft's rule
3855 // is being kept, not broken.
3856 ConnectionError::FinBeforeResponse(_) | ConnectionError::FinBeforePublishDone(_) => {
3857 Some(DraftSpecificCause::LocalRefusal)
3858 }
3859 }
3860 }
3861
3862 /// The code to close the session with when a control message or a data
3863 /// stream could not be decoded because the peer broke a rule draft-23
3864 /// answers with a close.
3865 ///
3866 /// Draft-23 Section 12.2 gives each category its own code, and the
3867 /// sentence stating each rule names the one it takes. Where more than one
3868 /// applies, "an endpoint uses DELTA_ENCODING_OVERFLOW or
3869 /// FIELD_LENGTH_EXCEEDED in preference to the other codes."
3870 ///
3871 /// `None` for everything else, including [`CodecError::InvalidField`].
3872 /// That variant is shared by many unrelated malformations, only some of
3873 /// which the draft answers with a close, so treating it as fatal would
3874 /// close sessions the draft does not ask to be closed.
3875 pub fn codec_session_error_code(
3876 err: &CodecError,
3877 ) -> Option<moqtap_codec::draft23::error_codes::SessionErrorCode> {
3878 use moqtap_codec::draft23::error_codes::SessionErrorCode;
3879 use moqtap_codec::kvp::KvpError;
3880 match err {
3881 // Section 9: "If the length does not match the length of the
3882 // Message Body, the receiver MUST close the session with
3883 // MALFORMED_MESSAGE."
3884 CodecError::ControlMessageLengthMismatch { .. }
3885 // Section 9.4.2.1: "An endpoint that receives a REQUEST_ERROR with
3886 // Error Code REDIRECT and no REDIRECT Error Detail MUST close the
3887 // session with MALFORMED_MESSAGE."
3888 | CodecError::MissingErrorDetail { .. }
3889 // This draft's FETCH has no Fetch Type (Section 9.11), so its
3890 // decoder cannot produce this. A discriminator a reader cannot name
3891 // is a message it cannot parse, which Section 12.2 defines
3892 // MALFORMED_MESSAGE as: "A control message cannot be parsed or is
3893 // missing a required element".
3894 | CodecError::InvalidFetchType(_)
3895 // A LOCATION_FILTER (Section 9.19.9) or FILL_PARAMETERS (Section
3896 // 9.19.15) value whose fields run past the end of the message.
3897 // Neither carries a length of its own, so the Message Body is
3898 // shorter than its fields, and Section 9: "If the length does not
3899 // match the length of the Message Body, the receiver MUST close
3900 // the session with MALFORMED_MESSAGE."
3901 | CodecError::SubscriptionFilterMalformed { .. } => {
3902 Some(SessionErrorCode::MalformedMessage)
3903 }
3904 // Section 9: "An endpoint that receives an unknown message type
3905 // MUST close the session with UNEXPECTED_MESSAGE."
3906 CodecError::UnknownMessageType(_) => Some(SessionErrorCode::UnexpectedMessage),
3907 // Section 9: "If an endpoint receives a control message with a
3908 // length larger than its MAX_CONTROL_MESSAGE_SIZE, it MUST close
3909 // the session with FIELD_LENGTH_EXCEEDED."
3910 CodecError::ControlMessageExceedsLimit { .. }
3911 // Section 8.3: "The maximum length of a value is 2^16-1 bytes. If
3912 // an endpoint receives a length larger than the maximum, it MUST
3913 // close the session with FIELD_LENGTH_EXCEEDED."
3914 | CodecError::Kvp(KvpError::ValueTooLong(_))
3915 // Section 8.7, the 4096-byte Full Track Name maximum.
3916 | CodecError::TrackNameTooLong
3917 // Section 8.5, the 1024-byte Reason Phrase maximum.
3918 | CodecError::ReasonPhraseTooLong
3919 // Section 9.2, the 8192-byte New Session URI maximum.
3920 | CodecError::GoAwayUriTooLong => Some(SessionErrorCode::FieldLengthExceeded),
3921 // Section 8.7: "If an endpoint receives a Track Namespace
3922 // consisting of greater than 32 Track Namespace Fields, it MUST
3923 // close the session with INVALID_TRACK_NAMESPACE", and the same
3924 // code for a Track Namespace Field Length of 0.
3925 CodecError::InvalidNamespaceTupleSize(_) | CodecError::EmptyNamespaceField => {
3926 Some(SessionErrorCode::InvalidTrackNamespace)
3927 }
3928 // Section 2.4.1: "If an endpoint receives a Track Name with a length
3929 // of 0, it MUST close the session with a PROTOCOL_VIOLATION."
3930 CodecError::EmptyTrackName => Some(SessionErrorCode::ProtocolViolation),
3931 // The delta encodings: a Key-Value-Pair or Message Parameter Type
3932 // (Section 8.3 and Section 9.19), a Location Filter's end Group
3933 // (Section 9.19.9), a subgroup Object ID (Section 11.3.1), and a
3934 // fetch stream's Group and Object IDs (Section 11.4.1.1 and
3935 // Section 11.4.2.1). Each "MUST close the session with
3936 // DELTA_ENCODING_OVERFLOW".
3937 CodecError::KeyDeltaOverflow(..)
3938 | CodecError::FilterEndGroupOverflow { .. }
3939 | CodecError::ObjectIdOverflow(..)
3940 | CodecError::DeltaDecodingOverflow { .. } => {
3941 Some(SessionErrorCode::DeltaEncodingOverflow)
3942 }
3943 // Section 9.19: "An endpoint that receives an unknown Message
3944 // Parameter MUST close the session with INVALID_PARAMETER." Section
3945 // 9.19.1 closes with the same code over a parameter in a message
3946 // its definition does not name, the duplicate rule of Section 9.19
3947 // does, and so do the value ranges of GROUP_ORDER (Section 9.19.8),
3948 // LOCATION_FILTER's type (Section 9.19.9), PAUSED (Section 9.19.18)
3949 // and INCLUDE_PROPERTIES (Section 9.19.22).
3950 //
3951 // A repeated known Setup Option reaches `DuplicateParameter` too.
3952 // Section 9.1 says "Senders MUST NOT repeat the same Option Type in
3953 // a message unless the option definition explicitly allows multiple
3954 // instances" and names no receiver action; it takes the code the
3955 // codec's one duplicate variant carries.
3956 CodecError::UnknownMessageParameter(_)
3957 | CodecError::ParameterOutOfScope { .. }
3958 | CodecError::DuplicateParameter(_)
3959 | CodecError::ParameterValueOutOfRange { .. }
3960 | CodecError::InvalidFilterType(_) => Some(SessionErrorCode::InvalidParameter),
3961 // Section 8.3: "If a receiver understands a Type, and the following
3962 // Value or Length/Value does not match the serialization defined by
3963 // that Type, the receiver MUST close the session with
3964 // MALFORMED_KEY_VALUE." A Setup Option, a Property, or an Error
3965 // Detail. Section 10.4, Section 10.5 and Section 10.6 state the
3966 // same code for the Track Property value ranges.
3967 CodecError::KeyValueFormatting { .. }
3968 | CodecError::MalformedErrorDetail { .. }
3969 | CodecError::TrackPropertyValueOutOfRange { .. } => {
3970 Some(SessionErrorCode::MalformedKeyValue)
3971 }
3972 // Section 8.9: "If the Token structure cannot be decoded, including
3973 // an unknown Alias Type, the receiver MUST close the Session with
3974 // MALFORMED_AUTH_TOKEN."
3975 CodecError::MalformedAuthToken { .. } => Some(SessionErrorCode::MalformedAuthToken),
3976 // A `Connection` is always the client, so the SETUP that carried
3977 // the token is the server's, and Section 9.1.4's ROLE_VIOLATION
3978 // ("If a server receives Alias Type DELETE (0x0) or USE_ALIAS (0x2)
3979 // in a SETUP message") is not this endpoint's to send. No Alias is
3980 // registered before SETUP, and Section 8.9: "The receiver of a
3981 // message referencing an Alias that is not currently registered
3982 // MUST reject the message with UNKNOWN_AUTH_TOKEN_ALIAS."
3983 CodecError::TokenAliasInSetup { .. } => Some(SessionErrorCode::UnknownAuthTokenAlias),
3984 // Section 9.3: "If an endpoint receives Track Properties in one of
3985 // these messages it MUST close the session with INVALID_FIELD."
3986 CodecError::TrackPropertiesNotPermitted => Some(SessionErrorCode::InvalidField),
3987 // Section 6.4.1 for an unknown stream type and Section 11 for an
3988 // unknown datagram type, and Section 11.3.1 and Section 11.2.1 for
3989 // the Type values they list as invalid: each "MUST close the
3990 // session with INVALID_STREAM_HEADER".
3991 CodecError::UnknownStreamType(_)
3992 | CodecError::UnknownDatagramType(_)
3993 | CodecError::InvalidStreamTypeValue { .. }
3994 | CodecError::InvalidDatagramTypeValue { .. } => {
3995 Some(SessionErrorCode::InvalidStreamHeader)
3996 }
3997 // Section 11.4.1, Section 11.4.1.1 and Section 11.4.2.2: an
3998 // undefined Serialization Flags value above 127, and an entry that
3999 // refers to a prior Object there is none of, each "MUST close the
4000 // session with MALFORMED_OBJECT".
4001 CodecError::MalformedObject { .. } => Some(SessionErrorCode::MalformedObject),
4002 // Section 11.1.1: "If an endpoint receives any other value, it
4003 // SHOULD close the session with INVALID_OBJECT_STATUS (Section
4004 // 12.2)."
4005 CodecError::UnknownObjectStatus(_) => Some(SessionErrorCode::InvalidObjectStatus),
4006 // Everything this draft does not answer with a close, named rather
4007 // than swept up by a wildcard, so a new `CodecError` variant does
4008 // not compile until it has been placed.
4009 //
4010 // A Malformed Track (Section 12.1) costs the track and not the
4011 // session: "When a subscriber detects a Malformed Track, it MUST
4012 // cancel any corresponding subscription or fetches for that Track
4013 // from that publisher".
4014 CodecError::MalformedTrack { .. }
4015 | CodecError::MalformedObjectProperties { .. }
4016 // Section 11.1.1 states the payload rule as a MUST on the sender and
4017 // names no receiver action.
4018 | CodecError::PayloadNotPermitted { .. }
4019 | CodecError::InvalidForward(_)
4020 | CodecError::InvalidContentExists(_)
4021 | CodecError::UnexpectedEnd
4022 | CodecError::MessageTooLong(_)
4023 | CodecError::VarInt(_)
4024 | CodecError::InvalidField
4025 | CodecError::InvalidRange(..)
4026 | CodecError::ParameterLengthMismatch(_)
4027 | CodecError::EndOfTrackObjectId(_)
4028 | CodecError::ParametersOutOfOrder(..)
4029 | CodecError::ExtensionsOnNonExistentObject(_)
4030 | CodecError::InvalidRequiredRequestIdDelta(..)
4031 | CodecError::UnsupportedDraft(_)
4032 | CodecError::Kvp(
4033 KvpError::MissingLength | KvpError::UnexpectedEnd | KvpError::VarInt(_),
4034 ) => None,
4035 }
4036 }
4037
4038 /// Close the session on the wire when a decode failure is one draft-23
4039 /// answers with a close, and hand the error back unchanged.
4040 ///
4041 /// The codec's counterpart to
4042 /// [`close_if_session_fatal`](Self::close_if_session_fatal). Without it
4043 /// every bound the decoder enforces would stop at *this endpoint refused the
4044 /// frame* while the peer, which is the one that broke the rule, saw a
4045 /// session that was still open and went on sending. "MUST close the session" is a statement
4046 /// about the wire.
4047 fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
4048 if let ConnectionError::Codec(inner) = &err {
4049 if let Some(code) = Self::codec_session_error_code(inner) {
4050 // QUIC application error codes are 62-bit; every code in this
4051 // registry is far below `u32::MAX`, and saturating rather than
4052 // truncating means a future code that is not could never be
4053 // reported as a different, assigned one.
4054 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
4055 self.close(wire_code, inner.to_string().as_bytes());
4056 }
4057 }
4058 err
4059 }
4060
4061 /// Name every request whose stream the caller must reset, for a track a
4062 /// data path has just found malformed.
4063 ///
4064 /// Section 12.1 answers its whole list of conditions at once: "it MUST
4065 /// cancel any corresponding subscription or fetches for that Track from
4066 /// that publisher". On this draft cancelling a request is a transport
4067 /// operation rather than a message — Section 6.4.2.3: "Implementations cancel a request by
4068 /// abruptly terminating any directions of the stream that are still open,
4069 /// using RESET_STREAM for a direction they are sending and STOP_SENDING for
4070 /// a direction they are receiving."
4071 ///
4072 /// # Why this returns ids instead of doing it
4073 ///
4074 /// Because the streams are the caller's. Every request on this draft lives
4075 /// at the front of a bidirectional stream of its own, and
4076 /// [`Connection::recv_on_request_stream`] hands that stream back as a
4077 /// [`RequestStream`]. There is no handle here to reset. So the connection
4078 /// does the half it can — note the track, and work out which requests
4079 /// receive it — and the caller passes each id to
4080 /// [`Connection::cancel_request_stream`], which resets the stream *and*
4081 /// moves the endpoint's record.
4082 ///
4083 /// # Every data path comes here
4084 ///
4085 /// Subgroup streams, FETCH streams and datagrams report the condition the
4086 /// same way, so there is one entry point. Pass it whatever error a read
4087 /// returned; anything that is not this condition gives back an empty list.
4088 ///
4089 /// Empty is not "the track was fine" — it is also what an alias no live
4090 /// binding names gives, and what a track this endpoint only publishes
4091 /// gives.
4092 pub fn requests_to_cancel(&self, err: &ConnectionError) -> Vec<VarInt> {
4093 match err {
4094 ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject { alias, .. }) => {
4095 self.endpoint.requests_for_malformed_track(
4096 *alias,
4097 MalformedTrackCondition::ObjectPastFinalObject,
4098 )
4099 }
4100 ConnectionError::Endpoint(EndpointError::ObjectExistenceContradicted {
4101 namespace,
4102 name,
4103 ..
4104 }) => self.endpoint.requests_for_malformed_track_named(
4105 namespace,
4106 name,
4107 MalformedTrackCondition::ExistenceContradicted,
4108 ),
4109 ConnectionError::MalformedTrackDatagram { track_alias, error } => {
4110 malformed_track_condition(error).map_or_else(Vec::new, |condition| {
4111 self.endpoint.requests_for_malformed_track(*track_alias, condition)
4112 })
4113 }
4114 _ => Vec::new(),
4115 }
4116 }
4117
4118 /// [`Connection::requests_to_cancel`] for an error read from the subgroup
4119 /// stream `header` opened, which also names the requests to cancel over
4120 /// the [`CodecError::MalformedTrack`] that
4121 /// [`FramedRecvStream::read_subgroup_object`] returns for a Prior Group ID
4122 /// Gap or Prior Object ID Gap fault (draft-23 Section 10.8,
4123 /// draft-23 Section 10.9), and over the
4124 /// [`CodecError::MalformedObjectProperties`] it returns for a property block
4125 /// that does not parse (draft-23 Section 10.7). Those errors name no track, so
4126 /// the header supplies its alias.
4127 ///
4128 /// draft-23 Section 12.1: "When a subscriber detects a Malformed Track, it
4129 /// MUST cancel any corresponding subscription or fetches for that Track
4130 /// from that publisher (see Section 6.4.2.3)". The session stays open.
4131 pub fn requests_to_cancel_for_subgroup_stream(
4132 &self,
4133 header: &AnySubgroupHeader,
4134 err: &ConnectionError,
4135 ) -> Vec<VarInt> {
4136 if let ConnectionError::Codec(codec) = err {
4137 if let Some(condition) = malformed_track_condition(codec) {
4138 return self.endpoint.requests_for_malformed_track(header.track_alias(), condition);
4139 }
4140 }
4141 self.requests_to_cancel(err)
4142 }
4143
4144 /// [`Connection::requests_to_cancel`] for an error read from the fetch stream
4145 /// `header` opened, which also names the requests to cancel over the
4146 /// [`CodecError::MalformedTrack`] that
4147 /// [`FramedRecvStream::read_fetch_object`] returns for a Prior Group ID
4148 /// Gap or Prior Object ID Gap fault (draft-23 Section 10.8,
4149 /// draft-23 Section 10.9), and over the
4150 /// [`CodecError::MalformedObjectProperties`] it returns for a property block
4151 /// that does not parse (draft-23 Section 10.7). Those errors name no track, so
4152 /// the track the FETCH under the header's Request ID asked for is the one.
4153 ///
4154 /// draft-23 Section 12.1: "When a subscriber detects a Malformed Track, it
4155 /// MUST cancel any corresponding subscription or fetches for that Track
4156 /// from that publisher (see Section 6.4.2.3)". The session stays open.
4157 pub fn requests_to_cancel_for_fetch_stream(
4158 &self,
4159 header: &AnyFetchHeader,
4160 err: &ConnectionError,
4161 ) -> Vec<VarInt> {
4162 if let ConnectionError::Codec(codec) = err {
4163 if let Some(condition) = malformed_track_condition(codec) {
4164 return self
4165 .endpoint
4166 .requests_for_malformed_fetch_track(header.request_id(), condition);
4167 }
4168 }
4169 self.requests_to_cancel(err)
4170 }
4171
4172 /// Close the session when a failure raised while reading a *data* stream is
4173 /// one draft-23 answers with a close. Reports whether it closed.
4174 ///
4175 /// [`recv_control`](Self::recv_control) does this for itself, because it
4176 /// owns both the stream and the connection. A data stream does not:
4177 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream) hands the caller
4178 /// a [`FramedRecvStream`], which holds no connection and so cannot close
4179 /// one, and the reads that raise these failures happen there. The caller is
4180 /// the only party holding both halves, which is what this is for:
4181 ///
4182 /// ```no_run
4183 /// # async fn f(conn: &moqtap_client::draft23::connection::Connection,
4184 /// # framed: &mut moqtap_client::draft23::connection::FramedRecvStream)
4185 /// # -> Result<(), Box<dyn std::error::Error>> {
4186 /// if let Err(err) = framed.read_subgroup_object().await {
4187 /// conn.close_for_data_stream(&err);
4188 /// return Err(err.into());
4189 /// }
4190 /// # Ok(()) }
4191 /// ```
4192 ///
4193 /// Splitting it this way rather than closing inside the reader keeps a
4194 /// caller that is deliberately permissive — a tool reproducing a capture,
4195 /// say — able to read a violating stream and report it without tearing the
4196 /// session down. The rule is stated at endpoints, and this is where an
4197 /// endpoint decides it is one.
4198 ///
4199 /// Only [`ConnectionError::Codec`] failures are matched, against the same
4200 /// `codec_session_error_code` table the
4201 /// control path uses, so a rule is answered with one code whichever stream
4202 /// carried it. The Object ID delta wrap of Section 11.3.1 is the entry that
4203 /// can only arrive this way.
4204 pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
4205 use crate::above_codec_rules::DraftSpecificCause;
4206
4207 match err {
4208 ConnectionError::Codec(inner) => {
4209 let Some(code) = Self::codec_session_error_code(inner) else { return false };
4210 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
4211 self.close(wire_code, inner.to_string().as_bytes());
4212 true
4213 }
4214 // Not a `Codec` failure: the codec refuses such an Object as a bare
4215 // `InvalidField`, which `read_subgroup_object` narrows to this
4216 // crate's own variant, so the mapping table above never sees it.
4217 //
4218 // The code comes from `draft_specific_cause` rather than from a
4219 // constant here, so this draft's reading of its own sentence is
4220 // written down once and a caller who reads the error as a value
4221 // sees the same code the peer was sent.
4222 ConnectionError::PropertiesOnNonNormalStatus { .. }
4223 | ConnectionError::TruncatedObject => {
4224 let Some(DraftSpecificCause::PeerViolation { close: Some(code), .. }) =
4225 Self::draft_specific_cause(err)
4226 else {
4227 return false;
4228 };
4229 // Saturate rather than truncate, so a future code above
4230 // `u32::MAX` is never reported as a different assigned one.
4231 self.close(u32::try_from(code).unwrap_or(u32::MAX), err.to_string().as_bytes());
4232 true
4233 }
4234 _ => false,
4235 }
4236 }
4237
4238 /// Close the connection.
4239 pub fn close(&self, code: u32, reason: &[u8]) {
4240 self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
4241 self.transport.close(code, reason);
4242 }
4243}
4244
4245/// The Malformed Track condition a codec refusal on a data path is, on this
4246/// draft, or `None` for a refusal that is not one.
4247fn malformed_track_condition(err: &CodecError) -> Option<MalformedTrackCondition> {
4248 match err {
4249 CodecError::MalformedTrack { .. } => Some(MalformedTrackCondition::PriorGap),
4250 // draft-23 Section 10.7: "A Key-Value-Pair cannot be parsed."
4251 CodecError::MalformedObjectProperties { .. } => {
4252 Some(MalformedTrackCondition::MalformedPropertyBlock)
4253 }
4254 _ => None,
4255 }
4256}
4257
4258#[cfg(test)]
4259mod tests {
4260 use super::*;
4261
4262 /// Only a refusal of what the peer sent is reported as a refused frame,
4263 /// and only with the bytes it was about.
4264 #[test]
4265 fn only_a_refusal_of_the_peers_frame_is_reported() {
4266 let raw = Some(vec![0x01]);
4267 let decoder = ConnectionError::Codec(CodecError::UnexpectedEnd);
4268 assert!(Connection::refused_event(&decoder, None, raw.clone()).is_some());
4269 assert!(Connection::refused_event(&decoder, None, None).is_none());
4270 let local = ConnectionError::Endpoint(EndpointError::NotActive);
4271 assert!(Connection::refused_event(&local, None, raw).is_none());
4272 }
4273
4274 /// This build failing to narrow a message it decoded is never a finding
4275 /// about the peer.
4276 ///
4277 /// The arm that raises `ControlMessageNarrowing` is unreachable — this
4278 /// draft's decoder can only hand back this draft's variant — and nothing
4279 /// pins that. What is pinned here is the half that matters.
4280 /// `CodecError::UnknownMessageType(0)` is what the arm must not raise:
4281 /// `codec_session_error_code` answers it with a session close on every draft in range, so the
4282 /// day the narrowing failed a conformance
4283 /// probe would publish a relay for sending a control message type this
4284 /// draft does not assign — with `0x00` attached as the codepoint that
4285 /// proved it, which is an accusation better evidenced than any real one
4286 /// this build makes. The section stating that rule is numbered differently
4287 /// on every draft, and the point does not turn on the number.
4288 #[test]
4289 fn a_message_this_build_could_not_narrow_names_nobody() {
4290 use crate::dispatch::{AnyConnectionError, ErrorCause};
4291
4292 let err: AnyConnectionError = ConnectionError::ControlMessageNarrowing.into();
4293 assert!(err.is_local(), "a narrowing this build could not do is this build's");
4294 assert_eq!(
4295 err.cause(),
4296 &ErrorCause::Facade,
4297 "nothing reached the wire, so there is no rule and no close code to read"
4298 );
4299 }
4300
4301 /// Draft-23 uses MoQT's variable-length integer, whose length is the
4302 /// number of leading 1 bits in the first byte, not RFC 9000's two-bit
4303 /// prefix. Control framing measures the type field with it before any
4304 /// bytes past the first have arrived.
4305 #[test]
4306 fn varint_len_follows_the_moqt_encoding() {
4307 let draft = DraftVersion::Draft23;
4308 assert_eq!(draft.varint_len(0x00), 1);
4309 assert_eq!(draft.varint_len(0x7F), 1);
4310 assert_eq!(draft.varint_len(0x80), 2);
4311 assert_eq!(draft.varint_len(0xBF), 2);
4312 assert_eq!(draft.varint_len(0xC0), 3);
4313 assert_eq!(draft.varint_len(0xFF), 9);
4314 // SETUP's type id, 0x2F00, is two bytes here and four under RFC 9000.
4315 assert_eq!(draft.varint_len(0xAF), 2);
4316 }
4317
4318 #[test]
4319 fn client_config_alpn_quic_draft23() {
4320 let config = ClientConfig {
4321 draft: DraftVersion::Draft23,
4322 transport: TransportType::Quic,
4323 skip_cert_verification: false,
4324 ca_certs: Vec::new(),
4325 setup_parameters: Vec::new(),
4326 };
4327 assert_eq!(config.alpn(), vec![b"moqt-23".to_vec()]);
4328 }
4329
4330 #[test]
4331 fn client_config_alpn_webtransport() {
4332 let config = ClientConfig {
4333 draft: DraftVersion::Draft23,
4334 transport: TransportType::WebTransport { url: "https://example.com".to_string() },
4335 skip_cert_verification: false,
4336 ca_certs: Vec::new(),
4337 setup_parameters: Vec::new(),
4338 };
4339 assert_eq!(config.alpn(), vec![b"h3".to_vec()]);
4340 }
4341
4342 /// `MOQT_ALPN` is the ALPN a client configured for this draft offers.
4343 #[test]
4344 fn moqt_alpn_is_the_one_a_client_offers() {
4345 // Checked against what a client configured for this draft puts on
4346 // the wire, and only then against the literal.
4347 let config = ClientConfig {
4348 draft: DraftVersion::Draft23,
4349 transport: TransportType::Quic,
4350 skip_cert_verification: false,
4351 ca_certs: Vec::new(),
4352 setup_parameters: Vec::new(),
4353 };
4354 assert_eq!(
4355 config.alpn(),
4356 vec![MOQT_ALPN.to_vec()],
4357 "MOQT_ALPN is {:?}; a draft-{} client offers {:?}",
4358 String::from_utf8_lossy(MOQT_ALPN),
4359 23,
4360 config
4361 .alpn()
4362 .iter()
4363 .map(|a| String::from_utf8_lossy(a).into_owned())
4364 .collect::<Vec<_>>(),
4365 );
4366 assert_eq!(MOQT_ALPN, b"moqt-23");
4367 }
4368
4369 /// Draft-23 Section 6.3 names seven message types a bidirectional stream
4370 /// may begin with, and no others. The set is checked against the raw
4371 /// numbers this draft's registry assigns rather than against the names,
4372 /// so a renumbered variant is caught even though its spelling is the
4373 /// same, and so is a set that leaves SUBSCRIBE_TRACKS (0x51) out.
4374 ///
4375 /// Every type this draft assigns is classified: the loop walks the whole
4376 /// assigned range and asks the classifier about each one it finds.
4377 #[test]
4378 fn only_seven_message_types_open_a_request_stream() {
4379 // TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
4380 // SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS, written as the numbers
4381 // draft-23 assigns them.
4382 let mut expected = vec![0x0D, 0x03, 0x1D, 0x16, 0x06, 0x50, 0x51];
4383 expected.sort_unstable();
4384
4385 let mut opens = Vec::new();
4386 for id in 0..=CONTROL_STREAM_TYPE {
4387 if let Some(ty) = MessageType::from_id(id) {
4388 if starts_a_request_stream(ty) {
4389 opens.push(id);
4390 }
4391 }
4392 }
4393 opens.sort_unstable();
4394
4395 assert_eq!(
4396 opens, expected,
4397 "the types that open a request stream are {opens:?}; \
4398 draft-23 Section 6.3 names {expected:?}"
4399 );
4400 }
4401
4402 /// The kind a request helper labels its stream with must name the message
4403 /// that helper actually writes, and the check is made against the type
4404 /// varint the encoded message leads with — the byte a peer reads to
4405 /// decide whether the bidirectional stream is legal.
4406 ///
4407 /// SUBSCRIBE_NAMESPACE (0x50) and SUBSCRIBE_TRACKS (0x51) are adjacent,
4408 /// which makes them the pair most easily mislabelled.
4409 #[test]
4410 fn each_request_kind_labels_the_message_its_helper_writes() {
4411 use crate::draft23::endpoint::Endpoint;
4412 use moqtap_codec::draft23::message::Setup;
4413
4414 let v = |n: u64| VarInt::from_u64(n).unwrap();
4415 let ns = TrackNamespace(vec![b"ns".to_vec()]);
4416
4417 let mut ep = Endpoint::new(Role::Client);
4418 ep.connect().unwrap();
4419 let _ = ep.send_setup(vec![]).unwrap();
4420 ep.receive_setup(&Setup { options: vec![] }).unwrap();
4421
4422 let (_, subscribe) = ep.subscribe(ns.clone(), b"t".to_vec(), vec![]).unwrap();
4423 let built = vec![
4424 (RequestKind::Subscribe, subscribe),
4425 // One FETCH: draft-23 Section 9.11 has no Fetch Type field, so there
4426 // is no second shape for this to label.
4427 (RequestKind::Fetch, ep.fetch(ns.clone(), b"t".to_vec(), vec![]).unwrap().1),
4428 (
4429 RequestKind::SubscribeNamespace,
4430 ep.subscribe_namespace(ns.clone(), vec![]).unwrap().1,
4431 ),
4432 (RequestKind::SubscribeTracks, ep.subscribe_tracks(ns.clone(), vec![]).unwrap().1),
4433 (RequestKind::PublishNamespace, ep.publish_namespace(ns.clone(), vec![]).unwrap().1),
4434 (
4435 RequestKind::TrackStatus,
4436 ep.track_status(ns.clone(), b"t".to_vec(), vec![]).unwrap().1,
4437 ),
4438 (
4439 RequestKind::Publish,
4440 ep.publish(ns.clone(), b"t".to_vec(), v(7), vec![], vec![]).unwrap().1,
4441 ),
4442 ];
4443
4444 for (kind, msg) in built {
4445 let mut wire = Vec::new();
4446 msg.encode(&mut wire).unwrap();
4447 let mut cursor = &wire[..];
4448 let on_the_wire =
4449 DraftVersion::Draft23.decode_varint(&mut cursor).unwrap().into_inner();
4450 assert_eq!(
4451 kind.message_type().id(),
4452 on_the_wire,
4453 "{kind:?} is labelled {} but its message leads with {on_the_wire}",
4454 kind.message_type().id(),
4455 );
4456 assert!(
4457 starts_a_request_stream(kind.message_type()),
4458 "{kind:?} labels a message type that may not begin a bidirectional stream",
4459 );
4460 }
4461 }
4462
4463 /// The classifier the accept path runs and the one
4464 /// [`Connection::send_control`] runs must answer alike for every message
4465 /// type this draft assigns, or a message could be refused on the control
4466 /// stream and refused again as the opening of a request stream — leaving
4467 /// no legal place for it.
4468 #[test]
4469 fn the_two_request_stream_classifiers_agree() {
4470 let mut classified = 0;
4471 for id in 0..=CONTROL_STREAM_TYPE {
4472 let Some(ty) = MessageType::from_id(id) else { continue };
4473 classified += 1;
4474 let kind = RequestKind::from_message_type(ty);
4475 assert_eq!(
4476 kind.is_some(),
4477 starts_a_request_stream(ty),
4478 "type {id} {} a request stream but from_message_type calls it {kind:?}",
4479 if starts_a_request_stream(ty) { "opens" } else { "does not open" },
4480 );
4481 if let Some(kind) = kind {
4482 assert_eq!(
4483 kind.message_type(),
4484 ty,
4485 "from_message_type sent type {id} to {kind:?}, which names a different message",
4486 );
4487 }
4488 }
4489 assert!(classified > 7, "the loop found only {classified} assigned message types");
4490 }
4491
4492 /// A stream this endpoint opened is cancelled when its handle is dropped;
4493 /// one the peer opened is reset as unserved. Both codes are on the wire,
4494 /// so they may not be the same number.
4495 #[test]
4496 fn the_two_abandonment_codes_are_distinct() {
4497 assert_eq!(REQUEST_CANCELLED, 0x1);
4498 assert_eq!(REQUEST_UNANSWERED, 0x0);
4499 assert_ne!(
4500 REQUEST_CANCELLED, REQUEST_UNANSWERED,
4501 "a peer cannot tell a rejected request from a dropped one if both reset with the same code",
4502 );
4503 }
4504
4505 #[test]
4506 fn transport_type_debug() {
4507 let quic = TransportType::Quic;
4508 assert!(format!("{quic:?}").contains("Quic"));
4509
4510 let wt = TransportType::WebTransport { url: "https://example.com".to_string() };
4511 assert!(format!("{wt:?}").contains("WebTransport"));
4512 }
4513}
4514
4515#[cfg(test)]
4516mod accept_on_the_wire {
4517 //! The accept path against a real QUIC peer.
4518 //!
4519 //! Everything the responder side does that a peer can see — which stream a
4520 //! response goes out on, which code an abandoned request stream is reset
4521 //! with, whether a FIN arrives before the messages that direction still
4522 //! owes, and which session error code a violation closes with — is
4523 //! observable only from the other end of a connection. A test holding an
4524 //! endpoint alone is satisfied by state that never reached the wire, so
4525 //! these hold the other end.
4526 //!
4527 //! The peer is raw `quinn` for everything the accept path decides. It does
4528 //! use [`FramedRecvStream`] to decode what comes back, because control
4529 //! framing is shared by both directions and gated elsewhere; the
4530 //! classification, registration, routing and stream-closing under test here
4531 //! are not part of it.
4532
4533 use super::*;
4534 use std::sync::Arc;
4535
4536 use std::net::SocketAddr;
4537 use std::time::Duration;
4538
4539 use moqtap_codec::draft23::message::{
4540 GoAway, Publish, PublishDone, Setup, Subscribe, SubscribeNamespace,
4541 };
4542
4543 /// Session termination codes, draft-23 Section 16.12.1.
4544 const UNEXPECTED_MESSAGE: u64 = 0x303;
4545 const INVALID_REQUEST_ID: u64 = 0x4;
4546 const UNKNOWN_AUTH_TOKEN_ALIAS: u64 = 0x17;
4547 const TRUNCATED_OBJECT: u64 = 0x203;
4548
4549 /// Long enough that a loaded machine cannot fail a test that would
4550 /// otherwise pass, short enough that a hang is reported rather than run to
4551 /// the harness timeout.
4552 const PATIENCE: Duration = Duration::from_secs(10);
4553
4554 /// How long to wait before concluding that nothing more is coming. Used
4555 /// only where the absence of a message is the assertion, so it is a floor
4556 /// on the test's run time and is kept small.
4557 const QUIET: Duration = Duration::from_millis(300);
4558
4559 fn v(n: u64) -> VarInt {
4560 VarInt::from_u64(n).unwrap()
4561 }
4562
4563 fn ns() -> TrackNamespace {
4564 TrackNamespace(vec![b"live".to_vec()])
4565 }
4566
4567 fn encode(msg: ControlMessage) -> Vec<u8> {
4568 let mut buf = Vec::new();
4569 AnyControlMessage::Draft23(msg).encode(&mut buf).expect("encode");
4570 buf
4571 }
4572
4573 /// A SUBSCRIBE carrying `id`. Odd ids are the ones a server allocates,
4574 /// which is what a client's peer must use.
4575 fn subscribe(id: u64) -> ControlMessage {
4576 ControlMessage::Subscribe(Subscribe {
4577 request_id: v(id),
4578 track_namespace: ns(),
4579 track_name: b"video".to_vec(),
4580 parameters: vec![],
4581 })
4582 }
4583
4584 fn request_update(id: u64) -> ControlMessage {
4585 ControlMessage::RequestUpdate(moqtap_codec::draft23::message::RequestUpdate {
4586 request_id: v(id),
4587 parameters: vec![],
4588 })
4589 }
4590
4591 fn peer_fetch(id: u64) -> ControlMessage {
4592 ControlMessage::Fetch(moqtap_codec::draft23::message::Fetch {
4593 request_id: v(id),
4594 track_namespace: ns(),
4595 track_name: b"video".to_vec(),
4596 parameters: vec![],
4597 })
4598 }
4599
4600 fn fetch_ok() -> RequestOk {
4601 RequestOk { parameters: vec![], track_properties: vec![] }
4602 }
4603
4604 fn subscribe_ok() -> SubscribeOk {
4605 SubscribeOk { track_alias: v(7), parameters: vec![], track_properties: vec![] }
4606 }
4607
4608 fn request_error() -> RequestError {
4609 RequestError {
4610 error_code: v(0x1),
4611 retry_interval: v(0),
4612 reason_phrase: b"no".to_vec(),
4613 error_details: vec![],
4614 }
4615 }
4616
4617 fn init_crypto() {
4618 let _ = rustls::crypto::ring::default_provider().install_default();
4619 }
4620
4621 /// A quinn server on a loopback port, offering this draft's ALPN.
4622 fn server_endpoint() -> (quinn::Endpoint, SocketAddr) {
4623 use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P256_SHA256};
4624 use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
4625
4626 let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).expect("keypair");
4627 let params = CertificateParams::new(vec!["localhost".into()]).expect("params");
4628 let cert = params.self_signed(&key_pair).expect("self-sign");
4629 let cert_der = CertificateDer::from(cert.der().to_vec());
4630 let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
4631
4632 let mut server_crypto = rustls::ServerConfig::builder()
4633 .with_no_client_auth()
4634 .with_single_cert(vec![cert_der], key_der)
4635 .expect("server cert");
4636 server_crypto.alpn_protocols = vec![DraftVersion::Draft23.quic_alpn().to_vec()];
4637 let server_crypto =
4638 quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto).expect("quic crypto");
4639 let server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto));
4640 let endpoint = quinn::Endpoint::server(server_config, "127.0.0.1:0".parse().unwrap())
4641 .expect("bind server");
4642 let addr = endpoint.local_addr().expect("local_addr");
4643 (endpoint, addr)
4644 }
4645
4646 async fn connect_client(addr: SocketAddr) -> Result<Connection, ConnectionError> {
4647 Connection::connect(
4648 &addr.to_string(),
4649 ClientConfig {
4650 draft: DraftVersion::Draft23,
4651 transport: TransportType::Quic,
4652 skip_cert_verification: true,
4653 ca_certs: Vec::new(),
4654 setup_parameters: Vec::new(),
4655 },
4656 )
4657 .await
4658 }
4659
4660 /// The peer's half of the setup exchange: read the client's SETUP off its
4661 /// unidirectional control stream, answer with one of our own.
4662 ///
4663 /// Both control streams are handed back so they stay open for the
4664 /// connection's life. Dropping a quinn receive stream sends STOP_SENDING
4665 /// and dropping a send stream resets it, either of which would look to the
4666 /// client like the control plane failing.
4667 async fn peer_handshake(
4668 endpoint: &quinn::Endpoint,
4669 ) -> (quinn::Connection, quinn::SendStream, quinn::RecvStream) {
4670 let conn = endpoint.accept().await.expect("accept").await.expect("tls handshake");
4671 let mut client_control = conn.accept_uni().await.expect("accept_uni");
4672 let mut seen = Vec::new();
4673 let mut chunk = [0u8; 1024];
4674 while seen.len() < 3 {
4675 match client_control.read(&mut chunk).await.expect("read SETUP") {
4676 Some(n) => seen.extend_from_slice(&chunk[..n]),
4677 None => break,
4678 }
4679 }
4680 assert!(!seen.is_empty(), "the client sent no SETUP");
4681 let mut ours = conn.open_uni().await.expect("open_uni");
4682 ours.write_all(&encode(ControlMessage::Setup(Setup { options: Vec::new() })))
4683 .await
4684 .expect("write SETUP");
4685 (conn, ours, client_control)
4686 }
4687
4688 /// A connected client and the peer holding the other end.
4689 struct Loopback {
4690 conn: Connection,
4691 peer: quinn::Connection,
4692 _endpoint: quinn::Endpoint,
4693 _control_send: quinn::SendStream,
4694 _control_recv: quinn::RecvStream,
4695 }
4696
4697 async fn loopback() -> Loopback {
4698 init_crypto();
4699 let (endpoint, addr) = server_endpoint();
4700 let (client, peer) = tokio::join!(connect_client(addr), peer_handshake(&endpoint));
4701 let (peer, control_send, control_recv) = peer;
4702 Loopback {
4703 conn: client.expect("client connect"),
4704 peer,
4705 _endpoint: endpoint,
4706 _control_send: control_send,
4707 _control_recv: control_recv,
4708 }
4709 }
4710
4711 fn framed(recv: quinn::RecvStream) -> FramedRecvStream {
4712 FramedRecvStream::new(RecvStream::Quic(recv), DraftVersion::Draft23)
4713 }
4714
4715 /// Read one control message the client wrote, failing rather than hanging.
4716 async fn next_control(recv: &mut FramedRecvStream) -> ControlMessage {
4717 let (any, _) = tokio::time::timeout(PATIENCE, recv.read_control(false))
4718 .await
4719 .expect("the client wrote nothing")
4720 .expect("read control");
4721 match any {
4722 AnyControlMessage::Draft23(msg) => msg,
4723 #[allow(unreachable_patterns)]
4724 other => panic!("expected a draft-23 message, got {other:?}"),
4725 }
4726 }
4727
4728 async fn assert_closed_with(peer: &quinn::Connection, code: u64, phrase: &str) {
4729 let reason = tokio::time::timeout(PATIENCE, peer.closed())
4730 .await
4731 .expect("the client reported the violation but never closed the connection");
4732 match reason {
4733 quinn::ConnectionError::ApplicationClosed(frame) => {
4734 assert_eq!(u64::from(frame.error_code), code, "the close carried the wrong code");
4735 let text = String::from_utf8_lossy(&frame.reason).to_string();
4736 assert!(
4737 text.contains(phrase),
4738 "the close reason should name the rule that was broken; got {text:?}",
4739 );
4740 }
4741 other => panic!("expected an application close, got {other:?}"),
4742 }
4743 }
4744
4745 /// draft-23 Section 11.3: "If a stream ends gracefully (i.e., the stream
4746 /// terminates with a FIN) in the middle of a serialized Object, the
4747 /// receiver SHOULD close the session with TRUNCATED_OBJECT." A FIN after a
4748 /// whole Object is the stream's end and closes nothing.
4749 #[tokio::test]
4750 async fn a_fin_inside_an_object_closes_with_truncated_object() {
4751 let lb = loopback().await;
4752
4753 // `subgroup-single-object`, then a FIN.
4754 let mut whole = lb.peer.open_uni().await.expect("open_uni");
4755 whole
4756 .write_all(&[0x10, 0x01, 0x00, 0x80, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef])
4757 .await
4758 .unwrap();
4759 whole.finish().unwrap();
4760 let (_, mut stream) = lb.conn.accept_subgroup_stream().await.expect("subgroup stream");
4761 stream.read_subgroup_object().await.expect("the whole Object");
4762 let end = stream.read_subgroup_object().await.expect_err("the stream ended");
4763 assert!(matches!(end, ConnectionError::UnexpectedEnd), "{end}");
4764 assert!(!lb.conn.close_for_data_stream(&end));
4765
4766 // The same Object cut off two bytes into its payload.
4767 let mut cut = lb.peer.open_uni().await.expect("open_uni");
4768 cut.write_all(&[0x10, 0x01, 0x00, 0x80, 0x00, 0x04, 0xde, 0xad]).await.unwrap();
4769 cut.finish().unwrap();
4770 let (_, mut stream) = lb.conn.accept_subgroup_stream().await.expect("subgroup stream");
4771 let end = stream.read_subgroup_object().await.expect_err("the stream ended");
4772 assert!(matches!(end, ConnectionError::TruncatedObject), "{end}");
4773 assert!(lb.conn.close_for_data_stream(&end));
4774 assert_closed_with(&lb.peer, TRUNCATED_OBJECT, "FIN in the middle of an Object").await;
4775 }
4776
4777 /// draft-23 Section 9: "An endpoint MUST NOT send a control message with
4778 /// a length larger than the peer's MAX_CONTROL_MESSAGE_SIZE (Section
4779 /// 9.1.8)." The server advertises 70000. A SUBSCRIBE between 65535 and
4780 /// that is sent, and a longer one is refused before it is written.
4781 #[tokio::test]
4782 async fn a_message_is_sent_under_the_servers_limit() {
4783 use moqtap_codec::varint::Moqt18;
4784
4785 init_crypto();
4786 let (endpoint, addr) = server_endpoint();
4787 let peer = async {
4788 let conn = endpoint.accept().await.expect("accept").await.expect("tls handshake");
4789 let mut value = Vec::new();
4790 VarInt::from_u64_moqt(70_000).encode_moqt::<Moqt18>(&mut value);
4791 // MAX_CONTROL_MESSAGE_SIZE (0x09): a Length, then one vi64.
4792 let mut body = vec![0x09, value.len() as u8];
4793 body.extend_from_slice(&value);
4794 let mut setup = vec![0xaf, 0x00, body.len() as u8];
4795 setup.extend_from_slice(&body);
4796 let mut ours = conn.open_uni().await.expect("open_uni");
4797 ours.write_all(&setup).await.expect("write SETUP");
4798 (conn, ours)
4799 };
4800 let (client, (_peer, _ours)) = tokio::join!(connect_client(addr), peer);
4801 let mut conn = client.expect("client connect");
4802
4803 // Two AUTHORIZATION TOKENs, Alias Type USE_VALUE (0x3), Token Type 0.
4804 let tokens = |len: usize| {
4805 let mut token = vec![0x03, 0x00];
4806 token.extend(std::iter::repeat_n(b't', len));
4807 let token =
4808 KeyValuePair { key: v(0x03), value: moqtap_codec::kvp::KvpValue::Bytes(token) };
4809 vec![token.clone(), token]
4810 };
4811 if let Err(e) = conn.subscribe(ns(), b"video".to_vec(), tokens(33_000)).await {
4812 panic!("a SUBSCRIBE of about 66000 bytes was refused under a limit of 70000: {e}");
4813 }
4814 match conn.subscribe(ns(), b"video".to_vec(), tokens(36_000)).await {
4815 Err(ConnectionError::Codec(CodecError::ControlMessageExceedsLimit {
4816 limit: 70_000,
4817 ..
4818 })) => {}
4819 Err(other) => panic!("expected ControlMessageExceedsLimit, got {other}"),
4820 Ok(_) => panic!("a SUBSCRIBE longer than the server's limit was sent"),
4821 }
4822 }
4823
4824 /// The server's SETUP names an Alias, which no SETUP can: none is
4825 /// registered yet. draft-23 Section 8.9: "The receiver of a message
4826 /// referencing an Alias that is not currently registered MUST reject the
4827 /// message with UNKNOWN_AUTH_TOKEN_ALIAS." The bytes are the corpus vector
4828 /// `with-auth-token-delete`.
4829 #[tokio::test]
4830 async fn a_server_setup_naming_an_alias_closes_with_unknown_auth_token_alias() {
4831 init_crypto();
4832 let (endpoint, addr) = server_endpoint();
4833 let peer = async {
4834 let conn = endpoint.accept().await.expect("accept").await.expect("tls handshake");
4835 let mut ours = conn.open_uni().await.expect("open_uni");
4836 ours.write_all(&[0xaf, 0x00, 0x04, 0x03, 0x02, 0x00, 0x00]).await.expect("write SETUP");
4837 (conn, ours)
4838 };
4839 let (client, (peer, _ours)) = tokio::join!(connect_client(addr), peer);
4840 match client {
4841 Err(ConnectionError::Codec(CodecError::TokenAliasInSetup { alias_type: 0 })) => {}
4842 Err(other) => panic!("expected TokenAliasInSetup, got {other}"),
4843 Ok(_) => panic!("the client accepted a SETUP that names an Alias"),
4844 }
4845 assert_closed_with(&peer, UNKNOWN_AUTH_TOKEN_ALIAS, "Alias Type").await;
4846 }
4847
4848 /// A peer's SUBSCRIBE is accepted, answered on the stream it arrived on,
4849 /// and cannot be finished until each message draft-23 Section 6.4.2.2
4850 /// requires has been sent.
4851 #[tokio::test]
4852 async fn a_peers_subscribe_is_answered_on_the_stream_it_arrived_on() {
4853 let mut lb = loopback().await;
4854
4855 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
4856 ps.write_all(&encode(subscribe(1))).await.expect("write SUBSCRIBE");
4857 let mut pr = framed(pr);
4858
4859 let (msg, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
4860 .await
4861 .expect("accept hung")
4862 .expect("accept");
4863 assert!(matches!(msg, ControlMessage::Subscribe(_)), "{msg:?}");
4864 assert_eq!(stream.origin(), RequestOrigin::Peer);
4865 assert_eq!(stream.kind(), RequestKind::Subscribe);
4866 assert_eq!(stream.request_id().into_inner(), 1);
4867 assert_eq!(lb.conn.endpoint().peer_request_count(), 1);
4868
4869 // Section 6.4.2.2: "an endpoint sending a response to a request MUST send
4870 // the corresponding response message ... before sending a FIN."
4871 let err = stream.finish().await.err();
4872 assert!(
4873 matches!(err, Some(ConnectionError::FinBeforeResponse(1))),
4874 "a FIN before the response must be refused: {err:?}",
4875 );
4876
4877 lb.conn.respond_subscribe_ok(&mut stream, subscribe_ok()).await.expect("respond");
4878 assert!(stream.responded());
4879 // On the request's own stream, not the control stream: this reader is
4880 // the peer's half of the bidirectional stream it opened.
4881 assert!(matches!(next_control(&mut pr).await, ControlMessage::SubscribeOk(_)));
4882
4883 // The rest of the same sentence: "the publisher of an Established
4884 // subscription MUST send PUBLISH_DONE, before sending a FIN."
4885 assert!(stream.owes_publish_done());
4886 let err = stream.finish().await.err();
4887 assert!(
4888 matches!(err, Some(ConnectionError::FinBeforePublishDone(1))),
4889 "a FIN before PUBLISH_DONE must be refused: {err:?}",
4890 );
4891
4892 lb.conn
4893 .publish_done_on(&mut stream, v(0), v(0), Vec::new())
4894 .await
4895 .expect("publish_done_on");
4896 assert!(matches!(next_control(&mut pr).await, ControlMessage::PublishDone(_)));
4897
4898 // And now the FIN, which is a clean end rather than a reset.
4899 let end = pr.read_control(false).await.expect_err("the stream should have ended");
4900 assert!(
4901 matches!(end, ConnectionError::UnexpectedEnd),
4902 "the completed request stream should end with a FIN, not {end}",
4903 );
4904 }
4905
4906 /// A refused update leaves the stream open for the termination it owes.
4907 ///
4908 /// Section 9.5.1: "When a REQUEST_UPDATE is unsuccessful, the publisher
4909 /// MUST also terminate the subscription by sending a PUBLISH_DONE with
4910 /// error code UPDATE_FAILED." That message has to go on the request's own
4911 /// stream, so a REQUEST_ERROR answering an update cannot finish the send
4912 /// half the way one answering the request does.
4913 #[tokio::test]
4914 async fn a_refused_update_leaves_the_stream_open_for_its_termination() {
4915 let mut lb = loopback().await;
4916
4917 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
4918 ps.write_all(&encode(subscribe(1))).await.expect("write SUBSCRIBE");
4919 let mut pr = framed(pr);
4920
4921 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
4922 .await
4923 .expect("accept hung")
4924 .expect("accept");
4925 lb.conn.respond_subscribe_ok(&mut stream, subscribe_ok()).await.expect("respond");
4926 assert!(matches!(next_control(&mut pr).await, ControlMessage::SubscribeOk(_)));
4927 assert!(stream.owes_publish_done());
4928
4929 // The peer updates the subscription and this endpoint refuses it.
4930 // The read is what puts the update on the endpoint's books; refusing
4931 // one it has not seen is a different error entirely.
4932 ps.write_all(&encode(request_update(1))).await.expect("write REQUEST_UPDATE");
4933 let update = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
4934 .await
4935 .expect("the update never arrived")
4936 .expect("read the update");
4937 assert!(matches!(update, ControlMessage::RequestUpdate(_)), "{update:?}");
4938 lb.conn.respond_error(&mut stream, request_error()).await.expect("refuse the update");
4939 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
4940
4941 // The subscription is still live and still owes its ending, so the
4942 // stream is neither finished nor relieved of the obligation.
4943 assert!(
4944 stream.owes_publish_done(),
4945 "a refused update retires nothing: the subscription still owes a PUBLISH_DONE",
4946 );
4947
4948 lb.conn
4949 .publish_done_on(&mut stream, v(0x8), v(0), Vec::new())
4950 .await
4951 .expect("the termination the refusal owes");
4952 assert!(matches!(next_control(&mut pr).await, ControlMessage::PublishDone(_)));
4953 }
4954
4955 /// A refused namespace update closes the stream rather than owing a message.
4956 ///
4957 /// Section 9.5.1 sorts a refused REQUEST_UPDATE by what was being
4958 /// updated, and a namespace subscription falls in the clause that ends
4959 /// with the transport rather than with a message: "When a REQUEST_UPDATE
4960 /// fails for a SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS or PUBLISH_NAMESPACE,
4961 /// the responder MUST close the bidi stream (see Section 6.4.2.2)." There is
4962 /// no subscription to terminate here and no PUBLISH_DONE that could go
4963 /// out, so a send half held open for one would stay open for good.
4964 #[tokio::test]
4965 async fn a_refused_namespace_update_closes_the_stream() {
4966 let mut lb = loopback().await;
4967
4968 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
4969 ps.write_all(&encode(ControlMessage::SubscribeNamespace(SubscribeNamespace {
4970 request_id: v(1),
4971 namespace_prefix: ns(),
4972 parameters: vec![],
4973 })))
4974 .await
4975 .expect("write SUBSCRIBE_NAMESPACE");
4976 let mut pr = framed(pr);
4977
4978 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
4979 .await
4980 .expect("accept hung")
4981 .expect("accept");
4982 lb.conn
4983 .respond_ok(&mut stream, RequestOk { parameters: vec![], track_properties: vec![] })
4984 .await
4985 .expect("respond_ok");
4986 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestOk(_)));
4987
4988 // The peer asks to move the prefix and this endpoint refuses.
4989 ps.write_all(&encode(request_update(1))).await.expect("write REQUEST_UPDATE");
4990 let update = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
4991 .await
4992 .expect("read hung")
4993 .expect("read REQUEST_UPDATE");
4994 assert!(matches!(update, ControlMessage::RequestUpdate(_)));
4995 lb.conn.respond_error(&mut stream, request_error()).await.expect("refuse the update");
4996 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
4997
4998 // The refusal ends this request stream, so the peer's next read finds
4999 // the send half closed rather than waiting on a message a namespace
5000 // subscription has no way to send.
5001 let ended = tokio::time::timeout(PATIENCE, pr.read_control(false))
5002 .await
5003 .expect("the peer is still reading: the refusal never closed the send half")
5004 .expect_err("a refused namespace update left the stream open");
5005 assert!(
5006 matches!(ended, ConnectionError::UnexpectedEnd),
5007 "the peer should have seen the FIN, got {ended:?}",
5008 );
5009 }
5010
5011 /// Draft-23 Section 6.4.2.3: "When an endpoint rejects a request without
5012 /// performing any application processing, it SHOULD send a REQUEST_ERROR
5013 /// and FIN the stream." The FIN is what tells the peer the request is over;
5014 /// without it the handle's [`Drop`] would reset the stream instead.
5015 #[tokio::test]
5016 async fn a_rejected_request_is_finished_rather_than_reset() {
5017 let mut lb = loopback().await;
5018
5019 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
5020 ps.write_all(&encode(subscribe(1))).await.expect("write SUBSCRIBE");
5021 let mut pr = framed(pr);
5022
5023 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5024 .await
5025 .expect("accept hung")
5026 .expect("accept");
5027 lb.conn.respond_error(&mut stream, request_error()).await.expect("respond_error");
5028 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
5029
5030 // Dropping the handle after the FIN must not turn a rejection into a
5031 // reset, so the end of stream is read after the handle is gone.
5032 drop(stream);
5033 let end = pr.read_control(false).await.expect_err("the stream should have ended");
5034 assert!(
5035 matches!(end, ConnectionError::UnexpectedEnd),
5036 "a rejected request stream should be finished, not {end}",
5037 );
5038 }
5039
5040 /// Draft-23 Section 6.3: a bidirectional stream that begins with a message
5041 /// type that opens no request "MUST close the Session with UNEXPECTED_MESSAGE" — on the wire,
5042 /// not only in the local state machine. The code is not the INVALID_REQUEST_ID
5043 /// of draft-23 Section 6.4.2.1.
5044 #[tokio::test]
5045 async fn a_stream_that_opens_no_request_closes_the_session() {
5046 let mut lb = loopback().await;
5047
5048 let (mut ps, _pr) = lb.peer.open_bi().await.expect("open_bi");
5049 ps.write_all(&encode(ControlMessage::GoAway(GoAway {
5050 new_session_uri: Vec::new(),
5051 timeout: v(0),
5052 })))
5053 .await
5054 .expect("write GOAWAY");
5055
5056 // `RequestStream` is not `Debug` — it owns two live stream halves — so
5057 // the accepted case is named rather than unwrapped.
5058 let err = match tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5059 .await
5060 .expect("accept hung")
5061 {
5062 Err(e) => e,
5063 Ok((msg, _)) => panic!("a GOAWAY opens no request stream, but {msg:?} was accepted"),
5064 };
5065 assert!(
5066 matches!(err, ConnectionError::NonRequestOnRequestStream(MessageType::GoAway)),
5067 "{err}",
5068 );
5069
5070 assert_closed_with(&lb.peer, UNEXPECTED_MESSAGE, "GoAway").await;
5071 }
5072
5073 /// Draft-23 Section 6.4.2.1: "If an endpoint receives a Request ID where the
5074 /// least significant bit is incorrect for the sender ... it MUST close the
5075 /// session with INVALID_REQUEST_ID." A different rule from the one above,
5076 /// and a different code, so a peer can tell them apart.
5077 #[tokio::test]
5078 async fn a_peer_id_with_the_wrong_parity_closes_the_session() {
5079 let mut lb = loopback().await;
5080
5081 let (mut ps, _pr) = lb.peer.open_bi().await.expect("open_bi");
5082 // 2 is even, so it is an id this client allocates for itself.
5083 ps.write_all(&encode(subscribe(2))).await.expect("write SUBSCRIBE");
5084
5085 let err = match tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5086 .await
5087 .expect("accept hung")
5088 {
5089 Err(e) => e,
5090 Ok((msg, _)) => {
5091 panic!("an even Request ID from a server peer must be refused, not {msg:?}")
5092 }
5093 };
5094 assert!(matches!(err, ConnectionError::Endpoint(EndpointError::RequestId(_))), "{err}");
5095
5096 assert_closed_with(&lb.peer, INVALID_REQUEST_ID, "parity").await;
5097 }
5098
5099 /// Dropping a request stream is one act with two meanings, and the peer
5100 /// can only tell them apart if the codes differ: a request this endpoint
5101 /// made was cancelled, a request made of it was never served.
5102 #[tokio::test]
5103 async fn an_unserved_request_and_a_cancelled_one_reset_with_different_codes() {
5104 let mut lb = loopback().await;
5105
5106 // Inbound: the peer asked, and the handle was dropped unanswered.
5107 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
5108 ps.write_all(&encode(subscribe(1))).await.expect("write SUBSCRIBE");
5109 let mut pr = framed(pr);
5110 let (_, stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5111 .await
5112 .expect("accept hung")
5113 .expect("accept");
5114 drop(stream);
5115 let err = tokio::time::timeout(PATIENCE, pr.read_control(false))
5116 .await
5117 .expect("the inbound stream was neither reset nor answered")
5118 .expect_err("the dropped handle should have reset the stream");
5119 assert!(
5120 matches!(
5121 err,
5122 ConnectionError::Transport(TransportError::StreamReset(c))
5123 if c == REQUEST_UNANSWERED
5124 ),
5125 "an unserved inbound request should reset with INTERNAL_ERROR (0x0): {err}",
5126 );
5127
5128 // Outbound: this endpoint asked, and walked away.
5129 let outbound = lb.conn.subscribe(ns(), b"audio".to_vec(), vec![]).await.expect("subscribe");
5130 let (_, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
5131 .await
5132 .expect("the client opened no request stream")
5133 .expect("accept_bi");
5134 let mut their_recv = framed(their_recv);
5135 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Subscribe(_)));
5136 drop(outbound);
5137 let err = tokio::time::timeout(PATIENCE, their_recv.read_control(false))
5138 .await
5139 .expect("the outbound stream was never reset")
5140 .expect_err("the dropped handle should have reset the stream");
5141 assert!(
5142 matches!(
5143 err,
5144 ConnectionError::Transport(TransportError::StreamReset(c))
5145 if c == REQUEST_CANCELLED
5146 ),
5147 "an abandoned outbound request should reset with CANCELLED (0x1): {err}",
5148 );
5149 }
5150
5151 /// A dropped accept future loses neither the stream nor the bytes of the
5152 /// request that had already arrived, which is what makes
5153 /// [`Connection::accept_request_stream`] safe to `select!` against anything
5154 /// else.
5155 #[tokio::test]
5156 async fn a_cancelled_accept_keeps_the_stream_and_what_had_arrived() {
5157 let mut lb = loopback().await;
5158
5159 let (mut ps, _pr) = lb.peer.open_bi().await.expect("open_bi");
5160 let bytes = encode(subscribe(1));
5161 // One byte: enough to open the stream and to be read as the start of a
5162 // message, not enough to complete one.
5163 ps.write_all(&bytes[..1]).await.expect("write the first byte");
5164
5165 assert!(
5166 tokio::time::timeout(QUIET, lb.conn.accept_request_stream()).await.is_err(),
5167 "a partial request must not complete an accept",
5168 );
5169 assert_eq!(
5170 lb.conn.pending_inbound_count(),
5171 1,
5172 "the cancelled accept dropped the peer's stream",
5173 );
5174
5175 ps.write_all(&bytes[1..]).await.expect("write the rest");
5176 let (msg, stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5177 .await
5178 .expect("the retried accept hung")
5179 .expect("accept");
5180 // The Request ID could not be read at all if the first byte had gone
5181 // with the cancelled future: the message would not decode.
5182 assert_eq!(stream.request_id().into_inner(), 1);
5183 assert!(matches!(msg, ControlMessage::Subscribe(_)), "{msg:?}");
5184 assert_eq!(lb.conn.pending_inbound_count(), 0);
5185 }
5186
5187 /// The `respond_*` helpers answer requests the peer made, and refuse the
5188 /// ones this endpoint made. The consequence is that nothing is written:
5189 /// the peer sees the request and then silence, not a response to itself.
5190 #[tokio::test]
5191 async fn a_respond_helper_writes_nothing_on_a_stream_this_endpoint_opened() {
5192 let mut lb = loopback().await;
5193
5194 let mut outbound =
5195 lb.conn.subscribe(ns(), b"video".to_vec(), vec![]).await.expect("subscribe");
5196 let (_, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
5197 .await
5198 .expect("the client opened no request stream")
5199 .expect("accept_bi");
5200 let mut their_recv = framed(their_recv);
5201 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Subscribe(_)));
5202
5203 let err = lb
5204 .conn
5205 .respond_error(&mut outbound, request_error())
5206 .await
5207 .expect_err("a request this endpoint made is not ours to answer");
5208 assert!(matches!(err, ConnectionError::RespondedToOwnRequest(0)), "{err}");
5209
5210 assert!(
5211 tokio::time::timeout(QUIET, their_recv.read_control(false)).await.is_err(),
5212 "the refused response reached the wire anyway",
5213 );
5214 }
5215
5216 /// A refused fetch update resets the stream the objects were going out on.
5217 ///
5218 /// Section 9.5.1: "When a REQUEST_UPDATE fails for a FETCH, the publisher
5219 /// MUST reset the FETCH data stream." The objects are not on the request
5220 /// stream, so refusing the update on that stream is only half of it; the
5221 /// data stream has to go too, and the connection can only reset a handle
5222 /// it still holds.
5223 #[tokio::test]
5224 async fn a_refused_fetch_update_resets_the_fetch_data_stream() {
5225 let mut lb = loopback().await;
5226
5227 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
5228 ps.write_all(&encode(peer_fetch(1))).await.expect("write FETCH");
5229 let mut pr = framed(pr);
5230
5231 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5232 .await
5233 .expect("accept hung")
5234 .expect("accept");
5235 assert_eq!(stream.kind(), RequestKind::Fetch);
5236 lb.conn.respond_ok(&mut stream, fetch_ok()).await.expect("respond_ok");
5237 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestOk(_)));
5238
5239 // The objects go out on a stream of their own, which the request now
5240 // holds.
5241 lb.conn
5242 .open_fetch_stream_on(
5243 &mut stream,
5244 &AnyFetchHeader::Draft23(FetchHeader { request_id: v(1) }),
5245 )
5246 .await
5247 .expect("open the fetch data stream");
5248 let data = tokio::time::timeout(PATIENCE, lb.peer.accept_uni())
5249 .await
5250 .expect("no data stream arrived")
5251 .expect("accept_uni");
5252 let mut data = framed(data);
5253 tokio::time::timeout(PATIENCE, data.read_fetch_header())
5254 .await
5255 .expect("the header never arrived")
5256 .expect("the data stream opens with a FETCH_HEADER");
5257
5258 // The peer updates the fetch and this endpoint refuses it.
5259 ps.write_all(&encode(request_update(1))).await.expect("write REQUEST_UPDATE");
5260 tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
5261 .await
5262 .expect("read hung")
5263 .expect("read REQUEST_UPDATE");
5264 lb.conn.respond_error(&mut stream, request_error()).await.expect("refuse the update");
5265 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
5266
5267 // The data stream went with it: the peer's next read on it fails
5268 // rather than waiting for objects that are not coming.
5269 let err = tokio::time::timeout(PATIENCE, data.read_control(false))
5270 .await
5271 .expect("the peer is still waiting on a stream that should have been reset")
5272 .expect_err("the data stream should have been reset");
5273 assert!(
5274 format!("{err}").to_lowercase().contains("reset"),
5275 "the peer should see a reset, got {err}",
5276 );
5277 }
5278
5279 /// A publisher can open the stream a FETCH's objects go on, and owns it.
5280 ///
5281 /// The objects answering a FETCH go on a unidirectional stream of their
5282 /// own that opens with a FETCH_HEADER.
5283 #[tokio::test]
5284 async fn a_fetch_data_stream_can_be_opened_through_the_connection() {
5285 let lb = loopback().await;
5286
5287 let mut send = lb
5288 .conn
5289 .open_fetch_stream(&AnyFetchHeader::Draft23(FetchHeader { request_id: v(4) }))
5290 .await
5291 .expect("open a fetch data stream");
5292
5293 let recv = tokio::time::timeout(PATIENCE, lb.peer.accept_uni())
5294 .await
5295 .expect("the client opened no data stream")
5296 .expect("accept_uni");
5297 let mut recv = framed(recv);
5298 let header = tokio::time::timeout(PATIENCE, recv.read_fetch_header())
5299 .await
5300 .expect("the header never arrived")
5301 .expect("the stream opens with a FETCH_HEADER");
5302 assert!(
5303 matches!(&header, AnyFetchHeader::Draft23(h) if h.request_id.into_inner() == 4),
5304 "the header names the request it answers: {header:?}",
5305 );
5306
5307 // The caller holds the stream, which is what lets a request that fails
5308 // later reset the objects it was serving.
5309 send.reset(0).expect("reset the fetch data stream");
5310 }
5311
5312 /// Draft-23 Table 5 gives NAMESPACE the Stream value "Request", and
5313 /// Section 9.15 puts it "on the response stream of a SUBSCRIBE_NAMESPACE
5314 /// request". It is checked by reading the peer's own half of the request
5315 /// stream, which a control-stream write could not reach.
5316 #[tokio::test]
5317 async fn a_namespace_is_announced_on_the_request_stream_that_asked_for_it() {
5318 let mut lb = loopback().await;
5319
5320 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
5321 ps.write_all(&encode(ControlMessage::SubscribeNamespace(SubscribeNamespace {
5322 request_id: v(1),
5323 namespace_prefix: ns(),
5324 parameters: vec![],
5325 })))
5326 .await
5327 .expect("write SUBSCRIBE_NAMESPACE");
5328 let mut pr = framed(pr);
5329
5330 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5331 .await
5332 .expect("accept hung")
5333 .expect("accept");
5334 assert_eq!(stream.kind(), RequestKind::SubscribeNamespace);
5335
5336 lb.conn
5337 .respond_ok(&mut stream, RequestOk { parameters: vec![], track_properties: vec![] })
5338 .await
5339 .expect("respond_ok");
5340 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestOk(_)));
5341
5342 lb.conn
5343 .namespace_on(&mut stream, Namespace { namespace_suffix: ns(), parameters: vec![] })
5344 .await
5345 .expect("namespace_on");
5346 assert!(matches!(next_control(&mut pr).await, ControlMessage::Namespace(_)));
5347
5348 lb.conn
5349 .namespace_done_on(&mut stream, NamespaceDone { namespace_suffix: ns() })
5350 .await
5351 .expect("namespace_done_on");
5352 assert!(matches!(next_control(&mut pr).await, ControlMessage::NamespaceDone(_)));
5353
5354 // Nothing further is owed on this direction, so the responder may
5355 // finish it — Section 6.4.2.2's guard covers the response and a
5356 // publisher's PUBLISH_DONE, not announcements that may never come.
5357 stream.finish().await.expect("finish an answered namespace subscription");
5358 }
5359
5360 /// An update on a PUBLISH this endpoint sent is answered here.
5361 ///
5362 /// Section 9.5 names the one case where a requester answers rather than
5363 /// asks: "A subscriber can also send REQUEST_UPDATE to modify parameters
5364 /// of a subscription established with PUBLISH." The receiver of that
5365 /// update "MUST respond with exactly one REQUEST_UPDATE_OK or
5366 /// REQUEST_UPDATE_ERROR message indicating if the update was successful" —
5367 /// a REQUEST_OK or a REQUEST_ERROR, under Section 1.5's names — and on a PUBLISH this
5368 /// endpoint sent, the receiver is this endpoint.
5369 #[tokio::test]
5370 async fn an_update_on_a_publish_we_sent_is_answered_here() {
5371 let mut lb = loopback().await;
5372
5373 let mut outbound =
5374 lb.conn.publish(ns(), b"video".to_vec(), v(7), vec![], vec![]).await.expect("publish");
5375 let (mut their_send, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
5376 .await
5377 .expect("the client opened no request stream")
5378 .expect("accept_bi");
5379 let mut their_recv = framed(their_recv);
5380 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Publish(_)));
5381
5382 // The subscriber accepts the publication, then updates it.
5383 their_send
5384 .write_all(&encode(ControlMessage::RequestOk(RequestOk {
5385 parameters: vec![],
5386 track_properties: vec![],
5387 })))
5388 .await
5389 .expect("write REQUEST_OK");
5390 let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
5391 .await
5392 .expect("no REQUEST_OK arrived")
5393 .expect("read REQUEST_OK");
5394 assert!(matches!(msg, ControlMessage::RequestOk(_)), "{msg:?}");
5395
5396 their_send.write_all(&encode(request_update(0))).await.expect("write REQUEST_UPDATE");
5397 let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
5398 .await
5399 .expect("no REQUEST_UPDATE arrived")
5400 .expect("read REQUEST_UPDATE");
5401 assert!(matches!(msg, ControlMessage::RequestUpdate(_)), "{msg:?}");
5402
5403 lb.conn
5404 .respond_ok(&mut outbound, RequestOk { parameters: vec![], track_properties: vec![] })
5405 .await
5406 .expect("the subscriber's update is this endpoint's to answer");
5407 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::RequestOk(_)));
5408
5409 // The publication is untouched by the update and still owes its ending.
5410 assert!(outbound.owes_publish_done());
5411 lb.conn
5412 .publish_done(&mut outbound, v(0), v(0), Vec::new())
5413 .await
5414 .expect("an accepted update leaves the ending free");
5415 }
5416
5417 /// Refusing that update owes the same ending as refusing any other.
5418 ///
5419 /// Section 9.5.1: "When a REQUEST_UPDATE is unsuccessful, the publisher
5420 /// MUST also terminate the subscription by sending a PUBLISH_DONE with
5421 /// error code UPDATE_FAILED." The publisher of a subscription established
5422 /// with PUBLISH is the endpoint that sent it, and the ending goes on the
5423 /// stream that endpoint opened rather than on one the peer opened.
5424 #[tokio::test]
5425 async fn a_refused_update_on_a_publish_we_sent_owes_its_ending() {
5426 let mut lb = loopback().await;
5427
5428 let mut outbound =
5429 lb.conn.publish(ns(), b"video".to_vec(), v(7), vec![], vec![]).await.expect("publish");
5430 let (mut their_send, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
5431 .await
5432 .expect("the client opened no request stream")
5433 .expect("accept_bi");
5434 let mut their_recv = framed(their_recv);
5435 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Publish(_)));
5436
5437 their_send
5438 .write_all(&encode(ControlMessage::RequestOk(RequestOk {
5439 parameters: vec![],
5440 track_properties: vec![],
5441 })))
5442 .await
5443 .expect("write REQUEST_OK");
5444 tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
5445 .await
5446 .expect("no REQUEST_OK arrived")
5447 .expect("read REQUEST_OK");
5448
5449 their_send.write_all(&encode(request_update(0))).await.expect("write REQUEST_UPDATE");
5450 tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
5451 .await
5452 .expect("no REQUEST_UPDATE arrived")
5453 .expect("read REQUEST_UPDATE");
5454
5455 lb.conn
5456 .respond_error(&mut outbound, request_error())
5457 .await
5458 .expect("refuse the subscriber's update");
5459 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::RequestError(_)));
5460 assert!(
5461 outbound.owes_publish_done(),
5462 "a refused update retires nothing: the subscription still owes a PUBLISH_DONE",
5463 );
5464
5465 // The ending is owed under one status, and asking for another leaves
5466 // the publication exactly where it was rather than half ended.
5467 let err = lb
5468 .conn
5469 .publish_done(&mut outbound, v(0), v(0), Vec::new())
5470 .await
5471 .expect_err("a refused update fixes the status of the ending");
5472 assert!(
5473 matches!(
5474 err,
5475 ConnectionError::Endpoint(EndpointError::WrongUpdateFailureStatus {
5476 request: 0,
5477 required: 0x8,
5478 })
5479 ),
5480 "{err}",
5481 );
5482 lb.conn
5483 .publish_done(&mut outbound, v(0x8), v(0), Vec::new())
5484 .await
5485 .expect("the termination the refusal owes");
5486 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::PublishDone(_)));
5487 }
5488
5489 /// Draft-23 Section 6.4.2.2 excludes the sender of a PUBLISH from the
5490 /// requesters that "MAY FIN immediately after sending a message", because
5491 /// an accepted PUBLISH makes it the publisher of an Established
5492 /// subscription. A **rejected** one establishes nothing, so the obligation
5493 /// has to end with the REQUEST_ERROR — otherwise the only way to close a
5494 /// refused publication would be to reset the stream, telling the peer the
5495 /// request was abandoned rather than answered.
5496 #[tokio::test]
5497 async fn a_refused_publish_can_still_be_finished_cleanly() {
5498 let mut lb = loopback().await;
5499
5500 let mut outbound =
5501 lb.conn.publish(ns(), b"video".to_vec(), v(7), vec![], vec![]).await.expect("publish");
5502 assert!(outbound.owes_publish_done(), "a PUBLISH sender owes a PUBLISH_DONE");
5503 let err = outbound.finish().await.err();
5504 assert!(
5505 matches!(err, Some(ConnectionError::FinBeforePublishDone(0))),
5506 "a PUBLISH may not be finished while its subscription could still be established: \
5507 {err:?}",
5508 );
5509
5510 let (mut their_send, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
5511 .await
5512 .expect("the client opened no request stream")
5513 .expect("accept_bi");
5514 let mut their_recv = framed(their_recv);
5515 assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Publish(_)));
5516
5517 their_send
5518 .write_all(&encode(ControlMessage::RequestError(request_error())))
5519 .await
5520 .expect("write REQUEST_ERROR");
5521 let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
5522 .await
5523 .expect("no REQUEST_ERROR arrived")
5524 .expect("a refused PUBLISH is an ordinary response");
5525 assert!(matches!(msg, ControlMessage::RequestError(_)), "{msg:?}");
5526 assert!(!outbound.owes_publish_done());
5527
5528 outbound.finish().await.expect("a refused publication owes nothing further");
5529 drop(outbound);
5530 let end = their_recv.read_control(false).await.expect_err("the stream should have ended");
5531 assert!(
5532 matches!(end, ConnectionError::UnexpectedEnd),
5533 "a refused publication should end with a FIN, not {end}",
5534 );
5535 }
5536
5537 /// A peer that PUBLISHes to us is the publisher, so PUBLISH_DONE arrives
5538 /// on the stream it opened rather than being sent on it. Routing a
5539 /// peer-opened stream's reads through the response dispatcher would look
5540 /// up a request this endpoint never made.
5541 #[tokio::test]
5542 async fn a_peers_publish_is_ended_by_the_peer_on_its_own_stream() {
5543 let mut lb = loopback().await;
5544
5545 let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
5546 ps.write_all(&encode(ControlMessage::Publish(Publish {
5547 request_id: v(1),
5548 track_namespace: ns(),
5549 track_name: b"video".to_vec(),
5550 track_alias: v(7),
5551 parameters: vec![],
5552 track_properties: vec![],
5553 })))
5554 .await
5555 .expect("write PUBLISH");
5556 let mut pr = framed(pr);
5557
5558 let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
5559 .await
5560 .expect("accept hung")
5561 .expect("accept");
5562 assert_eq!(stream.kind(), RequestKind::Publish);
5563
5564 lb.conn
5565 .respond_ok(&mut stream, RequestOk { parameters: vec![], track_properties: vec![] })
5566 .await
5567 .expect("respond_ok");
5568 assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestOk(_)));
5569
5570 ps.write_all(&encode(ControlMessage::PublishDone(PublishDone {
5571 status_code: v(0),
5572 stream_count: v(0),
5573 reason_phrase: Vec::new(),
5574 })))
5575 .await
5576 .expect("write PUBLISH_DONE");
5577
5578 let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
5579 .await
5580 .expect("no PUBLISH_DONE arrived")
5581 .expect("the publishing peer may end its own publication");
5582 assert!(matches!(msg, ControlMessage::PublishDone(_)), "{msg:?}");
5583 }
5584}