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