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