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