moqtap_client/draft16/connection.rs
1use std::collections::VecDeque;
2use std::sync::Mutex;
3
4use bytes::{Buf, Bytes, BytesMut};
5
6use crate::draft16::endpoint::{Endpoint, EndpointError};
7use crate::draft16::event::{ClientEvent, Direction, StreamKind};
8use crate::draft16::observer::ConnectionObserver;
9use crate::draft16::session::request_id::Role;
10use crate::draft16::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::draft16::data_stream::{
18 FetchHeader, FetchObjectHeader, SubgroupObject, SubgroupObjectReader,
19};
20use moqtap_codec::draft16::error_codes::DataStreamResetErrorCode;
21use moqtap_codec::draft16::message::{ControlMessage, MessageType, RequestError, RequestOk};
22use moqtap_codec::error::CodecError;
23use moqtap_codec::kvp::KeyValuePair;
24use moqtap_codec::types::*;
25use moqtap_codec::varint::VarInt;
26use moqtap_codec::version::DraftVersion;
27
28/// The ALPN identifier draft-16 uses on raw QUIC, `moqt-16`.
29///
30/// Drafts 07 to 14 share one ALPN, `moq-00`, and a peer that offers it has
31/// said nothing about which of the eight it speaks. Draft-15 ended that:
32/// from there each draft has an ALPN of its own, so the version is settled
33/// by the TLS handshake before a byte of MoQT is written.
34///
35/// This is [`DraftVersion::Draft16`]'s own
36/// [`quic_alpn`](DraftVersion::quic_alpn), which is what
37/// [`ClientConfig::alpn`] offers; the test below holds the two together.
38pub const MOQT_ALPN: &[u8] = b"moqt-16";
39
40/// Errors from the connection layer.
41#[derive(Debug, thiserror::Error)]
42pub enum ConnectionError {
43 /// Endpoint state machine error.
44 #[error("endpoint error: {0}")]
45 Endpoint(#[from] EndpointError),
46 /// Wire codec error.
47 #[error("codec error: {0}")]
48 Codec(#[from] CodecError),
49 /// Transport-level error.
50 #[error("transport error: {0}")]
51 Transport(#[from] TransportError),
52 /// Variable-length integer decoding error.
53 #[error("varint error: {0}")]
54 VarInt(#[from] moqtap_codec::varint::VarIntError),
55 /// Control stream was not opened.
56 #[error("control stream not open")]
57 NoControlStream,
58 /// Stream ended before a complete message was read.
59 #[error("unexpected end of stream")]
60 UnexpectedEnd,
61 /// Stream was finished by the peer.
62 #[error("stream finished")]
63 StreamFinished,
64 /// Invalid server address string.
65 #[error("invalid server address: {0}")]
66 InvalidAddress(String),
67 /// TLS configuration error.
68 #[error("TLS config error: {0}")]
69 TlsConfig(String),
70 /// Data stream used out of order (e.g. object before header).
71 #[error("data stream state error: {0}")]
72 DataStreamState(&'static str),
73 /// A control message this build decoded for draft-16 and then could not
74 /// narrow to draft-16's own message type.
75 ///
76 /// Unreachable, and that is not the same as harmless. `read_control`
77 /// decodes with this connection's own draft, so the `AnyControlMessage` it
78 /// hands back can only carry this draft's variant — but the narrowing arm
79 /// is compiled in every configuration anyway, under
80 /// `#[allow(unreachable_patterns)]` rather than a `cfg` naming the other
81 /// drafts, because such a list has to be edited in every per-draft
82 /// module whenever a draft is added and a copy that omits one leaves the
83 /// match non-exhaustive.
84 ///
85 /// Spelled as `CodecError::UnknownMessageType(0)` it would not stay inert:
86 /// every draft's
87 /// [`codec_session_error_code`](Connection::codec_session_error_code)
88 /// answers that variant `Some(PROTOCOL_VIOLATION)`. So the day the
89 /// narrowing did fail, this build's own defect would reach a caller as *the
90 /// peer sent a control message type this draft does not assign, and the
91 /// session must be closed with a Protocol Violation* — carrying `0x00` as
92 /// the codepoint that proved it. A conformance report reading that
93 /// publishes a named, well-evidenced accusation against a relay for
94 /// something no relay did.
95 ///
96 /// A variant of its own is what stops that.
97 /// [`draft_specific_cause`](Connection::draft_specific_cause) answers it
98 /// [`LocalRefusal`], the facade turns that into [`ErrorCause::Facade`], and
99 /// nothing downstream can read a rule out of a cause that says nothing
100 /// reached the wire. What is pinned is the consequence rather than the
101 /// unreachability: nothing pins the arm's reachability, which is exactly
102 /// why the consequence must not be an accusation.
103 ///
104 /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
105 /// [`ErrorCause::Facade`]: crate::dispatch::ErrorCause::Facade
106 #[error(
107 "a control message decoded for draft-16 did not narrow to draft-16: a defect in this build, and evidence about nothing the peer did"
108 )]
109 ControlMessageNarrowing,
110 /// A bidirectional stream the peer opened began with a message type other
111 /// than SUBSCRIBE_NAMESPACE.
112 ///
113 /// Draft-16 Section 3.3: "This specification only specifies two uses of
114 /// bidirectional streams, the control stream, which begins with
115 /// CLIENT_SETUP, and SUBSCRIBE_NAMESPACE. Bidirectional streams MUST NOT
116 /// begin with any other message type unless negotiated. If they do, the
117 /// peer MUST close the Session with a Protocol Violation." The session has
118 /// already been closed on the wire by the time this is returned, and the
119 /// offending stream reset.
120 #[error(
121 "a bidirectional stream the peer opened began with {0:?}, which does not begin a namespace subscription; the session was closed"
122 )]
123 NonSubscribeNamespaceOnBidiStream(MessageType),
124 /// A `respond_*` helper was called on a namespace subscription this
125 /// endpoint opened.
126 ///
127 /// The answer to a SUBSCRIBE_NAMESPACE is owed by whoever received it, so
128 /// only a stream that arrived through
129 /// [`Connection::accept_namespace_stream`] can be answered here. Nothing
130 /// was written and no state moved.
131 #[error("request {0} was made by this endpoint, so there is nothing here to answer")]
132 NotOursToAnswer(u64),
133 /// An Object arrived carrying extension headers on a status that is not
134 /// Normal.
135 /// Draft-16 Section 10.2.1.2: "Any Object with status Normal can have
136 /// extension headers", with a reference to Section 2.5 inside the sentence,
137 /// and "If an endpoint receives extension headers on Objects with status
138 /// that is not Normal, it MUST close the session with a
139 /// PROTOCOL_VIOLATION."
140 ///
141 /// The codec's `read_object` refuses such an Object as a bare
142 /// `InvalidField`. Its `read_object_meta` frames it, which is how the
143 /// receive path names the rule here and stays in step with the wire.
144 ///
145 /// [`Connection::close_for_data_stream`] performs the close the sentence
146 /// above requires. It is a separate call because the reader that raises
147 /// this holds no connection, and because a deliberately permissive caller
148 /// should be able to read a violating stream and report it without tearing
149 /// the session down.
150 #[error(
151 "object {object_id} carries {extensions_len} bytes of extension headers on status {status:?}, which is not Normal"
152 )]
153 ExtensionsOnNonNormalStatus {
154 /// The Object ID the extension headers arrived on.
155 object_id: u64,
156 /// Length in bytes of the extension-header block.
157 extensions_len: usize,
158 /// The Object's status, resolved through the encoding's elision rule.
159 ///
160 /// Spelled out in full because the glob import of `moqtap_codec::types`
161 /// brings a different `ObjectStatus` into this module.
162 status: moqtap_codec::draft16::types::ObjectStatus,
163 },
164 /// A datagram whose Type announces extension headers arrived with an
165 /// Extension Headers Length of 0.
166 ///
167 /// draft-16 Section 10.3.1: "If an endpoint receives a datagram with the
168 /// EXTENSIONS bit set and an Extension Headers Length of 0, it MUST close
169 /// the session with a PROTOCOL_VIOLATION."
170 ///
171 /// The codec's header decoder refuses such a datagram as a bare
172 /// `InvalidField`. [`Connection::recv_datagram`] names it here, and
173 /// [`Connection::close_for_data_stream`] closes the session over it.
174 #[error(
175 "the datagram for object {object_id} announces extension headers and has an Extension Headers Length of 0"
176 )]
177 EmptyDatagramExtensions {
178 /// The Object ID the datagram named.
179 object_id: u64,
180 },
181}
182
183/// [`ConnectionError::EmptyDatagramExtensions`] for a datagram the header
184/// decoder refused as `InvalidField`, when its empty extension block was the
185/// fault.
186///
187/// draft-16 Section 10.3.1 gives a Type's fields by bit: 0x01 EXTENSIONS,
188/// 0x04 no Object ID, 0x08 no Publisher Priority. The codec refuses a Type
189/// spelled in more than one byte first, so only a one-byte Type is read.
190fn empty_datagram_extensions(data: &[u8]) -> Option<ConnectionError> {
191 use crate::datagram_prefix::{empty_extension_block, Prefix};
192 empty_extension_block(data, |datagram_type, width| {
193 (width == 1 && datagram_type & 0x01 != 0).then_some(Prefix {
194 object_id: datagram_type & 0x04 == 0,
195 priority: datagram_type & 0x08 == 0,
196 })
197 })
198 .map(|object_id| ConnectionError::EmptyDatagramExtensions { object_id })
199}
200
201impl From<crate::transport::DialError> for ConnectionError {
202 /// Maps a dial failure onto the variants this error already has, so a
203 /// caller matches `InvalidAddress` or `TlsConfig`.
204 ///
205 /// A `LocalSocket` failure maps to `InvalidAddress`, which the facade reads
206 /// as this side's failure
207 /// ([`ErrorCause::Facade`](crate::dispatch::ErrorCause::Facade)). The dial
208 /// phase stays on [`DialError::phase`](crate::transport::DialError::phase).
209 fn from(e: crate::transport::DialError) -> Self {
210 match e {
211 // Two variants, one arm, deliberately — see above.
212 crate::transport::DialError::InvalidAddress(s)
213 | crate::transport::DialError::LocalSocket(s) => ConnectionError::InvalidAddress(s),
214 crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
215 crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
216 }
217 }
218}
219
220/// Transport type for the connection.
221#[derive(Debug, Clone)]
222pub enum TransportType {
223 /// Raw QUIC via quinn. The `addr` field should be `host:port`.
224 Quic,
225 /// WebTransport via wtransport. The `url` field is the WebTransport URL.
226 WebTransport {
227 /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
228 url: String,
229 },
230}
231
232/// Configuration for a MoQT client connection.
233///
234/// Both `draft` and `transport` are required -- there is no `Default` impl.
235pub struct ClientConfig {
236 /// The MoQT draft version to use (primary, determines codec/framing).
237 pub draft: DraftVersion,
238 /// The transport type (QUIC or WebTransport).
239 pub transport: TransportType,
240 /// Whether to skip TLS certificate verification (for testing).
241 pub skip_cert_verification: bool,
242 /// Custom CA certificates to trust (DER-encoded).
243 pub ca_certs: Vec<Vec<u8>>,
244 /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
245 pub setup_parameters: Vec<KeyValuePair>,
246}
247
248impl ClientConfig {
249 /// Returns the ALPN protocol identifiers for the transport.
250 pub fn alpn(&self) -> Vec<Vec<u8>> {
251 match &self.transport {
252 TransportType::Quic => vec![self.draft.quic_alpn().to_vec()],
253 TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
254 }
255 }
256}
257
258/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
259pub struct FramedSendStream {
260 inner: SendStream,
261 draft: DraftVersion,
262 /// Stateful subgroup object writer.
263 subgroup_io: Option<SubgroupObjectReader>,
264}
265
266impl FramedSendStream {
267 /// Create a new framed send stream for the given draft version.
268 pub fn new(inner: SendStream, draft: DraftVersion) -> Self {
269 Self { inner, draft, subgroup_io: None }
270 }
271
272 /// Get the transport-level stream ID.
273 pub fn stream_id(&self) -> u64 {
274 self.inner.stream_id()
275 }
276
277 /// Write a control message to the stream with type+length framing.
278 /// Returns the raw bytes that were written (for event capture).
279 pub async fn write_control(
280 &mut self,
281 msg: &AnyControlMessage,
282 ) -> Result<Vec<u8>, ConnectionError> {
283 let mut buf = Vec::new();
284 msg.encode(&mut buf)?;
285 self.inner.write_all(&buf).await?;
286 Ok(buf)
287 }
288
289 /// Write a subgroup stream header. Also initializes the internal
290 /// delta-encoding state used by
291 /// [`FramedSendStream::write_subgroup_object`].
292 ///
293 /// The header is refused, and nothing is written, if its fields disagree
294 /// with its own stream type. That check has to happen here rather than at
295 /// the first object: the type is what every object after it is framed
296 /// against, so a header that went out saying the wrong thing cannot be
297 /// taken back.
298 pub async fn write_subgroup_header(
299 &mut self,
300 header: &AnySubgroupHeader,
301 ) -> Result<(), ConnectionError> {
302 let mut buf = Vec::new();
303 header.encode_stream_checked(&mut buf)?;
304 self.inner.write_all(&buf).await?;
305 // Clippy would rather see these two arms as an `if let`, and rustc rejects
306 // that in a single-draft build, where the pattern is irrefutable. Only a
307 // `match` satisfies both.
308 #[allow(clippy::single_match)]
309 match header {
310 AnySubgroupHeader::Draft16(ref d16) => {
311 self.subgroup_io = Some(SubgroupObjectReader::new(d16));
312 }
313 // Only this draft's header seeds the object reader. With draft 16 the only enabled
314 // draft `AnySubgroupHeader` has a single variant, the arm above is exhaustive and this
315 // one unreachable. Compiled in every configuration with the lint allowed, rather than
316 // gated on a `cfg` naming the other drafts: such a list has to be edited in
317 // every draft module whenever a draft is added, and a copy that omits one leaves this
318 // match non-exhaustive.
319 #[allow(unreachable_patterns)]
320 _ => {}
321 }
322 Ok(())
323 }
324
325 /// Write a fetch response header.
326 pub async fn write_fetch_header(
327 &mut self,
328 header: &AnyFetchHeader,
329 ) -> Result<(), ConnectionError> {
330 let mut buf = Vec::new();
331 header.encode_stream(&mut buf);
332 self.inner.write_all(&buf).await?;
333 Ok(())
334 }
335
336 /// Append a draft-16 subgroup object to the stream using the
337 /// stateful writer seeded from
338 /// [`FramedSendStream::write_subgroup_header`].
339 pub async fn write_subgroup_object(
340 &mut self,
341 object: &SubgroupObject,
342 ) -> Result<(), ConnectionError> {
343 let writer = self
344 .subgroup_io
345 .as_mut()
346 .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
347 let mut buf = Vec::new();
348 writer.write_object(object, &mut buf)?;
349 self.inner.write_all(&buf).await?;
350 Ok(())
351 }
352
353 /// Append a fetch object to the stream.
354 ///
355 /// The fetch stream had a header writer and no object writer, so a caller
356 /// could open one and put nothing on it through this type. The subgroup
357 /// stream has had both since the writer was introduced.
358 ///
359 /// The declared length comes from the payload rather than from the caller's
360 /// field: a header that disagrees with the bytes beside it desynchronises
361 /// every object after it on the stream, and nothing downstream can recover.
362 ///
363 /// # Errors
364 ///
365 /// [`ConnectionError::Codec`] if the header's fields disagree with the
366 /// Serialization Flags that announce them, which the encoder refuses rather
367 /// than writing a frame its own reader cannot take apart.
368 pub async fn write_fetch_object(
369 &mut self,
370 header: &FetchObjectHeader,
371 payload: &[u8],
372 ) -> Result<(), ConnectionError> {
373 let mut header = header.clone();
374 header.payload_length = VarInt::from_usize(payload.len());
375 let mut buf = Vec::new();
376 header.encode(&mut buf)?;
377 buf.extend_from_slice(payload);
378 self.inner.write_all(&buf).await?;
379 Ok(())
380 }
381
382 /// Finish the stream (send FIN).
383 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
384 self.inner.finish()?;
385 Ok(())
386 }
387
388 /// Abandon the stream, handing the peer `code` as the `RESET_STREAM`
389 /// application error code.
390 ///
391 /// Dropping a send stream sends a FIN, which claims the stream ended
392 /// cleanly; this is the only way to say the opposite. See
393 /// [`SendStream::reset`].
394 pub fn reset(&mut self, code: u64) -> Result<(), ConnectionError> {
395 self.inner.reset(code)?;
396 Ok(())
397 }
398
399 /// Returns the draft version this stream is framed for.
400 pub fn draft(&self) -> DraftVersion {
401 self.draft
402 }
403}
404
405/// What an Object Status makes of an object here.
406///
407/// Two answers where drafts 08 through 13 have three, and the missing one is
408/// the point: the end-of-track status settles where the track ended and is
409/// judged against nothing, because the rule about where one may be placed is
410/// not in this draft. `a_track_may_end_where_it_has_already_been.rs` asserts
411/// that acceptance.
412///
413/// Every other status is a statement about objects rather than one of them.
414fn object_role(status: Option<u64>) -> ObjectRole {
415 match status {
416 None | Some(0x0) => ObjectRole::Produced,
417 Some(0x4) => ObjectRole::EndsTrack(None),
418 _ => ObjectRole::Neither,
419 }
420}
421
422/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
423pub struct FramedRecvStream {
424 inner: RecvStream,
425 buf: BytesMut,
426 draft: DraftVersion,
427 /// Stateful subgroup object reader.
428 subgroup_io: Option<SubgroupObjectReader>,
429 /// The record this stream's objects are measured against, and the Group ID
430 /// its header named.
431 ///
432 /// One group for the whole stream: a subgroup header names it once and no
433 /// object header repeats it. `None` on a stream that was never given one -
434 /// a stream for an alias no live binding names, and every stream built
435 /// outside [`Connection::accept_subgroup_stream`] - and such a stream reads
436 /// without being measured, because `note_subgroup_object` has nothing to
437 /// measure it against.
438 tracking: Option<(TrackObjects, u64)>,
439 /// The bytes of the last control frame a capturing read refused.
440 refused: Option<Vec<u8>>,
441}
442
443impl FramedRecvStream {
444 /// Create a new framed receive stream for the given draft version.
445 pub fn new(inner: RecvStream, draft: DraftVersion) -> Self {
446 Self {
447 inner,
448 buf: BytesMut::with_capacity(4096),
449 draft,
450 subgroup_io: None,
451 tracking: None,
452 refused: None,
453 }
454 }
455
456 /// Get the transport-level stream ID.
457 pub fn stream_id(&self) -> u64 {
458 self.inner.stream_id()
459 }
460
461 /// The bytes of the control frame the last capturing read refused.
462 pub(crate) fn take_refused(&mut self) -> Option<Vec<u8>> {
463 self.refused.take()
464 }
465
466 /// Keep the first `len` buffered bytes as a refused frame when the read is
467 /// capturing, and hand `err` back.
468 fn refuse(&mut self, capture_raw: bool, len: usize, err: ConnectionError) -> ConnectionError {
469 if capture_raw {
470 self.refused = Some(self.buf[..len].to_vec());
471 }
472 err
473 }
474
475 /// Measure this stream's objects against `objects`, all of them in `group`.
476 ///
477 /// Called by [`Connection::accept_subgroup_stream`] once the header has
478 /// been read, which is the only point at which both the track and the group
479 /// are known.
480 fn measure_objects_against(&mut self, objects: TrackObjects, group: u64) {
481 self.tracking = Some((objects, group));
482 }
483
484 /// Judge one object this stream carried against where its track ended.
485 ///
486 /// The object's Group ID is the stream's and its Object ID is its own,
487 /// already resolved from the delta the wire carries; what they are measured
488 /// against is the end an end-of-track object settled on any stream.
489 fn note_subgroup_object(
490 &self,
491 object: u64,
492 status: Option<u64>,
493 ) -> Result<(), ConnectionError> {
494 let Some((objects, group)) = &self.tracking else { return Ok(()) };
495 let at = ObjectLocation { group: *group, object };
496 objects.note_past_final(at, object_role(status)).map_err(|end| {
497 ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject {
498 alias: objects.alias(),
499 group: at.group,
500 object: at.object,
501 final_group: end.group,
502 final_object: end.object,
503 })
504 })
505 }
506
507 /// Read more data from the stream into the internal buffer.
508 async fn fill(&mut self) -> Result<bool, ConnectionError> {
509 let mut tmp = [0u8; 4096];
510 match self.inner.read(&mut tmp).await {
511 Ok(Some(n)) => {
512 self.buf.extend_from_slice(&tmp[..n]);
513 Ok(true)
514 }
515 Ok(None) => Ok(false),
516 Err(e) => Err(ConnectionError::Transport(e)),
517 }
518 }
519
520 /// Ensure at least `n` bytes are available in the buffer.
521 async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
522 while self.buf.len() < n {
523 if !self.fill().await? {
524 return Err(ConnectionError::UnexpectedEnd);
525 }
526 }
527 Ok(())
528 }
529
530 /// Stop reading, telling the peer to stop transmitting with `code` as the
531 /// `STOP_SENDING` application error code, discarding anything unread.
532 ///
533 /// Dropping a receive stream also stops it, but with a hard-coded 0. See
534 /// [`RecvStream::stop`].
535 pub fn stop(&mut self, code: u64) -> Result<(), ConnectionError> {
536 self.inner.stop(code)?;
537 Ok(())
538 }
539
540 /// Wait for the peer to reset this stream, consuming nothing.
541 ///
542 /// See [`RecvStream::received_reset`] for what `Ok(None)` means and why a
543 /// caller must not re-poll after it.
544 pub async fn received_reset(&mut self) -> Result<Option<u64>, ConnectionError> {
545 Ok(self.inner.received_reset().await?)
546 }
547
548 /// Read the next control message, or report that the peer finished the
549 /// stream at a message boundary.
550 ///
551 /// `Ok(None)` is a clean end and not an error: on a namespace
552 /// subscription's stream it is one of the two ways Section 6.1 withdraws
553 /// the subscription — "closing the stream with either a FIN or
554 /// RESET_STREAM" — and the other is a reset, which surfaces as
555 /// [`TransportError::StreamReset`] out of the read below.
556 ///
557 /// A stream that ends *inside* a message is a different thing and stays
558 /// [`ConnectionError::UnexpectedEnd`]: the buffer is empty only at a
559 /// boundary.
560 pub async fn read_control_or_end(
561 &mut self,
562 capture_raw: bool,
563 ) -> Result<Option<(AnyControlMessage, Option<Vec<u8>>)>, ConnectionError> {
564 if self.buf.is_empty() && !self.fill().await? {
565 return Ok(None);
566 }
567 self.read_control(capture_raw).await.map(Some)
568 }
569
570 /// Read a control message from the stream.
571 ///
572 /// When `capture_raw` is true, the returned tuple includes a clone of the
573 /// framed wire bytes (for observer emission). When false, the second
574 /// element is `None` and the payload clone is skipped.
575 pub async fn read_control(
576 &mut self,
577 capture_raw: bool,
578 ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
579 self.refused = None;
580 // Read type ID varint
581 self.ensure(1).await?;
582 let type_len = varint_len(self.buf[0]);
583 self.ensure(type_len).await?;
584
585 let mut cursor = &self.buf[..type_len];
586 if let Err(e) = VarInt::decode(&mut cursor) {
587 return Err(self.refuse(capture_raw, type_len, e.into()));
588 }
589
590 // Draft-16: 16-bit BE payload length
591 let (payload_len, len_field_size) = if self.draft.uses_fixed_length_framing() {
592 self.ensure(type_len + 2).await?;
593 let hi = self.buf[type_len] as usize;
594 let lo = self.buf[type_len + 1] as usize;
595 ((hi << 8) | lo, 2)
596 } else {
597 self.ensure(type_len + 1).await?;
598 let payload_len_start = type_len;
599 let payload_len_varint_len = varint_len(self.buf[payload_len_start]);
600 self.ensure(type_len + payload_len_varint_len).await?;
601 let header_len = type_len + payload_len_varint_len;
602 let mut cursor = &self.buf[payload_len_start..header_len];
603 let payload_len = match VarInt::decode(&mut cursor) {
604 Ok(length) => length.into_inner() as usize,
605 Err(e) => return Err(self.refuse(capture_raw, header_len, e.into())),
606 };
607 (payload_len, payload_len_varint_len)
608 };
609
610 // Read full payload
611 let total = type_len + len_field_size + payload_len;
612 self.ensure(total).await?;
613
614 // Capture raw bytes only if requested (observer attached).
615 let raw = capture_raw.then(|| self.buf[..total].to_vec());
616
617 // Now decode the whole message
618 let mut frame = &self.buf[..total];
619 let msg = match AnyControlMessage::decode(self.draft, &mut frame) {
620 Ok(msg) => msg,
621 Err(e) => return Err(self.refuse(capture_raw, total, e.into())),
622 };
623 self.buf.advance(total);
624 Ok((msg, raw))
625 }
626
627 /// Read a subgroup stream header. Also initializes the internal
628 /// delta-decoding state.
629 pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
630 self.ensure(1).await?;
631 loop {
632 let mut cursor = &self.buf[..];
633 match AnySubgroupHeader::decode(self.draft, &mut cursor) {
634 Ok(header) => {
635 let consumed = self.buf.len() - cursor.remaining();
636 self.buf.advance(consumed);
637 // Clippy would rather see these two arms as an `if let`, and rustc rejects
638 // that in a single-draft build, where the pattern is irrefutable. Only a
639 // `match` satisfies both.
640 #[allow(clippy::single_match)]
641 match header {
642 AnySubgroupHeader::Draft16(ref d16) => {
643 self.subgroup_io = Some(SubgroupObjectReader::new(d16));
644 }
645 // Only this draft's header seeds the object reader. With draft 16 the only
646 // enabled draft `AnySubgroupHeader` has a single variant, the arm above is
647 // exhaustive and this one unreachable. Compiled in every configuration with
648 // the lint allowed, rather than gated on a `cfg` naming every other
649 // draft: such a list has to be edited in every draft module whenever a
650 // draft is added, and a copy that omits one leaves this match
651 // non-exhaustive.
652 #[allow(unreachable_patterns)]
653 _ => {}
654 }
655 return Ok(header);
656 }
657 Err(e) if e.is_incomplete() => {
658 if !self.fill().await? {
659 return Err(ConnectionError::UnexpectedEnd);
660 }
661 }
662 Err(e) => return Err(ConnectionError::Codec(e)),
663 }
664 }
665 }
666
667 /// Read a fetch response header.
668 pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
669 self.ensure(1).await?;
670 loop {
671 let mut cursor = &self.buf[..];
672 match AnyFetchHeader::decode(self.draft, &mut cursor) {
673 Ok(header) => {
674 let consumed = self.buf.len() - cursor.remaining();
675 self.buf.advance(consumed);
676 return Ok(header);
677 }
678 Err(e) if e.is_incomplete() => {
679 if !self.fill().await? {
680 return Err(ConnectionError::UnexpectedEnd);
681 }
682 }
683 Err(e) => return Err(ConnectionError::Codec(e)),
684 }
685 }
686 }
687
688 /// Read the next draft-16 subgroup object from this stream using
689 /// the stateful reader seeded by
690 /// [`FramedRecvStream::read_subgroup_header`].
691 ///
692 /// Errors with [`ConnectionError::ExtensionsOnNonNormalStatus`] on an
693 /// Object that carries extension headers on a status other than Normal,
694 /// which draft-16 Section 10.2.1.2 answers with a session close. The Object
695 /// is consumed from the stream before the check, so the reader stays in
696 /// step with the wire and a caller that reports the violation and reads on
697 /// sees the following Object rather than a re-parse of this one.
698 pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
699 if self.subgroup_io.is_none() {
700 return Err(ConnectionError::DataStreamState("subgroup header not read yet"));
701 }
702 loop {
703 let reader = self.subgroup_io.as_mut().unwrap();
704 let mut probe = reader.clone();
705 let mut cursor = &self.buf[..];
706 match probe.read_object(&mut cursor) {
707 Ok(obj) => {
708 let consumed = self.buf.len() - cursor.remaining();
709 self.buf.advance(consumed);
710 *reader = probe;
711 if !obj.extensions_permitted() {
712 return Err(ConnectionError::ExtensionsOnNonNormalStatus {
713 object_id: obj.object_id.into_inner(),
714 extensions_len: obj.extension_headers.len(),
715 status: obj.status(),
716 });
717 }
718 self.note_subgroup_object(
719 obj.object_id.into_inner(),
720 obj.object_status.map(|s| s as u64),
721 )?;
722 return Ok(obj);
723 }
724 Err(e) if e.is_incomplete() => {
725 if !self.fill().await? {
726 return Err(ConnectionError::UnexpectedEnd);
727 }
728 }
729 // The codec refuses extension headers on a non-Normal status as
730 // `InvalidField`, one of many readings of that variant. Its
731 // framing-only reader accepts them, which tells this case apart
732 // from the others and keeps the stream in step.
733 Err(CodecError::InvalidField) => {
734 let mut framing = reader.clone();
735 let mut cursor = &self.buf[..];
736 if let Ok(meta) = framing.read_object_meta(&mut cursor) {
737 let status = meta
738 .status
739 .and_then(moqtap_codec::draft16::types::ObjectStatus::from_u64);
740 if let Some(status) = status.filter(|s| {
741 meta.extension_headers_len > 0
742 && *s != moqtap_codec::draft16::types::ObjectStatus::Normal
743 }) {
744 let consumed = self.buf.len() - cursor.remaining();
745 self.buf.advance(consumed);
746 *reader = framing;
747 return Err(ConnectionError::ExtensionsOnNonNormalStatus {
748 object_id: meta.object_id,
749 extensions_len: meta.extension_headers_len as usize,
750 status,
751 });
752 }
753 }
754 return Err(ConnectionError::Codec(CodecError::InvalidField));
755 }
756 Err(e) => return Err(ConnectionError::Codec(e)),
757 }
758 }
759 }
760
761 /// Read the next draft-16 fetch header from this stream.
762 pub async fn read_fetch_stream_header(&mut self) -> Result<FetchHeader, ConnectionError> {
763 loop {
764 let mut cursor = &self.buf[..];
765 match FetchHeader::decode(&mut cursor) {
766 Ok(hdr) => {
767 let consumed = self.buf.len() - cursor.remaining();
768 self.buf.advance(consumed);
769 return Ok(hdr);
770 }
771 Err(e) if e.is_incomplete() => {
772 if !self.fill().await? {
773 return Err(ConnectionError::UnexpectedEnd);
774 }
775 }
776 Err(e) => return Err(ConnectionError::Codec(e)),
777 }
778 }
779 }
780
781 /// Read the next draft-16 fetch object's header and payload.
782 ///
783 /// The mirror of [`FramedSendStream::write_fetch_object`], and the payload
784 /// comes back with the header for the reason the codec leaves it on the
785 /// wire: `payload_length` says how many bytes follow, and a reader that
786 /// takes the wrong number of them desynchronises every later object on the
787 /// stream. Doing it here is the only place that count and the buffer are
788 /// both in hand.
789 ///
790 /// Stateless: the header comes back with its elided fields still absent —
791 /// `group_id`, `object_id` and the Subgroup ID mode say what each Object
792 /// inherited rather than what it is. Resolving them against the Object
793 /// before takes a running
794 /// [`FetchObjectReader`](moqtap_codec::draft16::data_stream::FetchObjectReader),
795 /// which this draft's codec does offer and which this stream does not hold —
796 /// where drafts 15 and 17 through 20 keep one on the stream itself.
797 ///
798 /// So a caller that wants Locations rather than inheritances carries the
799 /// reader beside the stream and calls
800 /// [`resolve`](moqtap_codec::draft16::data_stream::FetchObjectReader::resolve)
801 /// on each header. `AnyConnection::accept_fetch` is that caller, and
802 /// `Draft16FetchStream` is where it keeps the state — draft-16 is the one
803 /// arm of the facade's fetch reader that is not a bare stream.
804 ///
805 /// # Errors
806 ///
807 /// [`ConnectionError::UnexpectedEnd`] when the stream ends inside the header
808 /// or inside the payload it declared, and [`ConnectionError::Codec`] on a
809 /// Serialization Flags value the draft does not define.
810 pub async fn read_fetch_object(
811 &mut self,
812 ) -> Result<(FetchObjectHeader, Vec<u8>), ConnectionError> {
813 let header = loop {
814 let mut cursor = &self.buf[..];
815 match FetchObjectHeader::decode(&mut cursor) {
816 Ok(header) => {
817 let consumed = self.buf.len() - cursor.remaining();
818 self.buf.advance(consumed);
819 break header;
820 }
821 Err(e) if e.is_incomplete() => {
822 if !self.fill().await? {
823 return Err(ConnectionError::UnexpectedEnd);
824 }
825 }
826 Err(e) => return Err(ConnectionError::Codec(e)),
827 }
828 };
829 let payload = self.read_object_payload(&header.payload_length).await?;
830 Ok((header, payload))
831 }
832
833 /// Take the `length` payload bytes that follow a fetch object's header.
834 ///
835 /// Separate from the header read because the header is decoded from a probe
836 /// cursor that may have to be retried after a fill, and the payload is a
837 /// flat byte count that never is.
838 async fn read_object_payload(&mut self, length: &VarInt) -> Result<Vec<u8>, ConnectionError> {
839 let length = length.into_inner() as usize;
840 self.ensure(length).await?;
841 let payload = self.buf[..length].to_vec();
842 self.buf.advance(length);
843 Ok(payload)
844 }
845
846 /// Returns the draft version this stream is framed for.
847 pub fn draft(&self) -> DraftVersion {
848 self.draft
849 }
850}
851
852/// Which side opened the bidirectional stream a namespace subscription
853/// travels on.
854///
855/// Section 6.1 does not say who may subscribe to a namespace, and a relay
856/// subscribing to what a client publishes is the ordinary case, so the stream
857/// arrives in both directions. The two are not symmetric — one side owes an
858/// answer and the other is waiting for it — so a [`NamespaceStream`] carries
859/// this to say which side of that it is on.
860#[derive(Debug, Clone, Copy, PartialEq, Eq)]
861pub enum RequestOrigin {
862 /// This endpoint opened the stream and wrote the SUBSCRIBE_NAMESPACE on
863 /// it. What comes back is the answer and the namespaces that follow it.
864 Local,
865 /// The peer opened the stream; this endpoint owes it an answer and writes
866 /// the namespaces on it afterwards.
867 Peer,
868}
869
870/// The application error code a namespace subscription's stream is abandoned
871/// with when this endpoint never served it: `INTERNAL_ERROR`, 0x0.
872///
873/// Draft-16 assigns no code for this. Its only registry of stream error codes
874/// is Section 13.4.4, "Data Stream Reset Error Codes", and every entry there is
875/// specified by Section 10.4.3, which is about closing subgroup streams — a
876/// namespace subscription's stream is not a data stream. Section 6.1 offers a
877/// FIN as the alternative and names no number for the other form.
878///
879/// So this is a choice rather than a citation, and it is the one that claims
880/// least: `INTERNAL_ERROR` is "an implementation specific error", which is
881/// exactly what a stream abandoned mid-accept is. It is taken from the codec's
882/// own registry rather than written as a literal so a renumbering in a later
883/// draft cannot be missed here.
884///
885/// Only the paths that give up on a stream before it carries a subscription
886/// use it — a request that could not be built, a first message that could not
887/// be read, a Request ID the peer may not use. A caller cancelling a live
888/// subscription picks its own code, or uses the FIN form and picks none.
889const STREAM_ABANDONED: u64 = DataStreamResetErrorCode::InternalError as u64;
890
891/// The largest value a QUIC application error code can carry, `2^62 - 1`.
892///
893/// Checked by [`NamespaceStream::cancel`] before either half of the stream is
894/// touched, so an unrepresentable code cannot half-cancel a subscription.
895const MAX_QUIC_VARINT: u64 = (1u64 << 62) - 1;
896
897/// One SUBSCRIBE_NAMESPACE and everything answering it, on a bidirectional
898/// stream of their own.
899///
900/// Draft-16 Section 3.3: "This specification only specifies two uses of
901/// bidirectional streams, the control stream, which begins with CLIENT_SETUP,
902/// and SUBSCRIBE_NAMESPACE." This is the second use, and the only request on
903/// this draft that has a stream at all — every other one is still written on
904/// the control stream and identified by its Request ID.
905///
906/// The stream matters because two of the four messages that travel on it carry
907/// no Request ID. Section 9.21 puts NAMESPACE "on the response stream of a
908/// SUBSCRIBE_NAMESPACE request" and Section 9.23 says the same of
909/// NAMESPACE_DONE; both carry a Track Namespace **Suffix**, relative to a
910/// prefix only this subscription knows. Without the stream they name nothing.
911///
912/// # Reading and writing go through the connection
913///
914/// This handle owns both halves of the stream but not the session, so the
915/// endpoint state machine and the observer stay where they were. Read with
916/// [`Connection::recv_on_namespace_stream`], answer the peer with
917/// [`Connection::respond_ok_on_namespace_stream`] or
918/// [`Connection::respond_error_on_namespace_stream`], report namespaces with
919/// [`Connection::send_on_namespace_stream`], and withdraw with
920/// [`Connection::cancel_namespace_stream`] or
921/// [`Connection::finish_namespace_stream`].
922///
923/// [`cancel`](Self::cancel), [`finish`](Self::finish) and
924/// [`peer_cancelled`](Self::peer_cancelled) are on the handle because a caller
925/// may hold one without the connection. None of them moves the endpoint's
926/// record of the subscription, which is why the connection carries a wrapper
927/// for each.
928///
929/// # Dropping this cancels the subscription, and correctly
930///
931/// Section 6.1: "A SUBSCRIBE_NAMESPACE can be cancelled by closing the stream
932/// with either a FIN or RESET_STREAM." Dropping a send stream sends a FIN and
933/// dropping a receive stream sends `STOP_SENDING`, so a handle that falls out
934/// of scope performs the first of those two forms exactly. That is why there
935/// is no [`Drop`] impl here: on this draft the default *is* the cancellation,
936/// and drafts 17 to 19 need one only because they made a FIN mean something
937/// else.
938///
939/// What a drop cannot do is say so at the endpoint. It holds the stream and
940/// not the session, so the subscription stays where it was in the endpoint's
941/// record while the stream it travelled on is gone. Call
942/// [`Connection::finish_namespace_stream`] wherever that record matters.
943///
944/// All fields are private so the shape can grow without breaking callers.
945#[must_use = "dropping a namespace stream cancels the subscription; hold it while it is live"]
946pub struct NamespaceStream {
947 send: FramedSendStream,
948 recv: FramedRecvStream,
949 request_id: VarInt,
950 draft: DraftVersion,
951 stream_id: u64,
952 origin: RequestOrigin,
953 /// Whether this handle has already closed the stream, by either form.
954 closed: bool,
955 /// Whether a `respond_*` helper has written the answer on this stream.
956 /// Only ever true on a [`RequestOrigin::Peer`] stream.
957 responded: bool,
958}
959
960impl NamespaceStream {
961 /// The Request ID the SUBSCRIBE_NAMESPACE on this stream carries.
962 pub fn request_id(&self) -> VarInt {
963 self.request_id
964 }
965
966 /// The transport-level stream identifier, the same one
967 /// [`ClientEvent::StreamOpened`] reports.
968 pub fn stream_id(&self) -> u64 {
969 self.stream_id
970 }
971
972 /// The draft version this stream is framed for.
973 pub fn draft(&self) -> DraftVersion {
974 self.draft
975 }
976
977 /// Which side opened this stream.
978 ///
979 /// [`RequestOrigin::Peer`] means this endpoint owes the answer and the
980 /// `respond_*` helpers apply; [`RequestOrigin::Local`] means it is waiting
981 /// for one.
982 pub fn origin(&self) -> RequestOrigin {
983 self.origin
984 }
985
986 /// Whether the answer has been written on this stream by one of the
987 /// `respond_*` helpers.
988 ///
989 /// Always false on a [`RequestOrigin::Local`] stream, which is answered by
990 /// the peer rather than here.
991 pub fn responded(&self) -> bool {
992 self.responded
993 }
994
995 /// Whether [`cancel`](Self::cancel) or [`finish`](Self::finish) has
996 /// already run on this handle.
997 ///
998 /// Says nothing about the peer: a peer's cancel is learned from
999 /// [`peer_cancelled`](Self::peer_cancelled) or from the next read.
1000 pub fn is_closed(&self) -> bool {
1001 self.closed
1002 }
1003
1004 /// Cancel the subscription by resetting the stream, handing the peer
1005 /// `code`.
1006 ///
1007 /// The second of the two forms Section 6.1 allows. Both halves are shut —
1008 /// a QUIC bidirectional stream has two independent halves, so resetting
1009 /// only the send half would leave the peer free to keep writing namespaces
1010 /// nobody will read. The send half is reset with `code` and the receive
1011 /// half is stopped with the same value.
1012 ///
1013 /// `code` is a plain `u64` and has no default here, because draft-16
1014 /// assigns none: its only registry of stream error codes is titled "Data
1015 /// Stream Reset Error Codes" and every entry in it is specified by Section
1016 /// 10.4.3, which is about closing subgroup streams. A namespace
1017 /// subscription's stream is not a data stream, so a caller that wants to
1018 /// end one without choosing a number should use [`finish`](Self::finish),
1019 /// the form that carries none.
1020 ///
1021 /// **This is the stream and nothing else.** The endpoint's record of the
1022 /// subscription does not move, so a namespace already in flight is still
1023 /// accepted after this returns. [`Connection::cancel_namespace_stream`]
1024 /// does both and is what a caller holding a connection should reach for.
1025 ///
1026 /// Idempotent, and errors from a stream that was already reset or finished
1027 /// are swallowed: the subscription is cancelled either way.
1028 ///
1029 /// # Errors
1030 ///
1031 /// [`ConnectionError::Transport`] carrying [`TransportError::Write`] if
1032 /// `code` is outside the QUIC varint range (`0..2^62`). Nothing is sent in
1033 /// that case and the handle is *not* marked closed, so a caller can retry
1034 /// with a representable code.
1035 pub fn cancel(&mut self, code: u64) -> Result<(), ConnectionError> {
1036 if self.closed {
1037 return Ok(());
1038 }
1039 // Rejected before either half is touched, so a failed call leaves the
1040 // stream exactly as it was.
1041 if code > MAX_QUIC_VARINT {
1042 return Err(ConnectionError::Transport(TransportError::Write(format!(
1043 "error code {code} exceeds the varint range"
1044 ))));
1045 }
1046 self.closed = true;
1047 // Already-finished or already-reset halves report StreamClosed; the
1048 // subscription ends regardless, so neither is worth raising.
1049 let _ = self.send.reset(code);
1050 let _ = self.recv.stop(code);
1051 Ok(())
1052 }
1053
1054 /// Cancel the subscription by finishing the send half cleanly.
1055 ///
1056 /// The first of the two forms Section 6.1 allows, and the one that needs
1057 /// no error code. The receive half is left open on purpose: a publisher
1058 /// that has already written namespaces has them in flight, and stopping
1059 /// the half they arrive on would discard what was sent before the FIN.
1060 ///
1061 /// Like [`cancel`](Self::cancel), this is the stream and nothing else.
1062 /// [`Connection::finish_namespace_stream`] is the same act with the
1063 /// endpoint's record attached.
1064 ///
1065 /// Idempotent.
1066 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
1067 if self.closed {
1068 return Ok(());
1069 }
1070 self.closed = true;
1071 self.send.finish().await
1072 }
1073
1074 /// Wait for the peer to reset this stream, consuming nothing.
1075 ///
1076 /// This sees one of Section 6.1's two forms and not the other: a reset
1077 /// arrives here, a FIN arrives as `Ok(None)` from
1078 /// [`Connection::recv_on_namespace_stream`]. A caller that wants to
1079 /// observe both has to read.
1080 ///
1081 /// Returns `Ok(Some(code))` with the peer's application error code, or
1082 /// `Ok(None)` meaning **no reset is observable, now or ever — stop
1083 /// asking**. A caller that re-polls after `Ok(None)` spins.
1084 ///
1085 /// Records nothing at the endpoint;
1086 /// [`Connection::peer_cancelled_on_namespace_stream`] is the same wait
1087 /// with the record attached. Cancel-safe, and it grants no flow-control
1088 /// credit.
1089 ///
1090 /// On WebTransport this always answers `Ok(None)`: `wtransport` exposes no
1091 /// reset-only observable, so a WebTransport caller learns of a peer reset
1092 /// on its next read and not before.
1093 pub async fn peer_cancelled(&mut self) -> Result<Option<u64>, ConnectionError> {
1094 self.recv.received_reset().await
1095 }
1096}
1097
1098/// Holds a peer-opened stream pair while its first message is being read, and
1099/// puts it back on the connection's queue if that read is abandoned.
1100///
1101/// [`Connection::accept_namespace_stream`] awaits a whole control message, and
1102/// a caller may drop that future — a `select!` against a shutdown signal is
1103/// the ordinary reason. Without this the stream, and every byte already read
1104/// off it into the reader's buffer, would go with the future: the peer would
1105/// see its subscription reset for no reason it could act on.
1106///
1107/// [`Drop`] is the only place this can run, because a cancelled future is
1108/// never polled again. Every path that finishes — success or error — takes the
1109/// pair out first, so a pair still present when this drops was cancelled.
1110struct PendingInbound<'a> {
1111 pair: Option<(FramedSendStream, FramedRecvStream)>,
1112 queue: &'a Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1113}
1114
1115impl Drop for PendingInbound<'_> {
1116 fn drop(&mut self) {
1117 if let Some(pair) = self.pair.take() {
1118 // Front, not back: this stream arrived before anything still
1119 // queued behind it, and a partially read message must not be
1120 // handed out after a stream that arrived later.
1121 self.queue.lock().unwrap_or_else(|p| p.into_inner()).push_front(pair);
1122 }
1123 }
1124}
1125
1126/// A live MoQT connection over QUIC or WebTransport, combining the endpoint
1127/// state machine with actual network I/O.
1128pub struct Connection {
1129 transport: Transport,
1130 endpoint: Endpoint,
1131 draft: DraftVersion,
1132 /// The control stream's write half, behind an async lock.
1133 ///
1134 /// A lock rather than `&mut self` because Section 2.4.2's answer to a
1135 /// Malformed Track is a control message, and the condition is detected
1136 /// where objects arrive - on a datagram read that takes `&self`, and on a
1137 /// stream the caller holds, whose reader has no connection at all.
1138 control_send: Option<tokio::sync::Mutex<FramedSendStream>>,
1139 control_recv: Option<FramedRecvStream>,
1140 observer: Option<Box<dyn ConnectionObserver>>,
1141 /// Setup events buffered during `connect()` and replayed when an
1142 /// observer attaches via `set_observer` — without this, an observer
1143 /// attached after `connect` returns would never see the handshake.
1144 pending_events: Vec<ClientEvent>,
1145 /// The server's half of the setup handshake, kept whole.
1146 ///
1147 /// The endpoint acts on the parameters it recognises and retains none of
1148 /// them, and which parameters a server sends — in what order, with what
1149 /// values — is the sharpest thing a session says about the implementation
1150 /// behind it.
1151 server_setup: AnyControlMessage,
1152 /// The framed wire bytes of [`Self::server_setup`].
1153 server_setup_raw: Option<Vec<u8>>,
1154 /// Bidirectional streams the peer opened that
1155 /// [`accept_namespace_stream`](Connection::accept_namespace_stream) took
1156 /// off the transport but did not finish reading a first message from,
1157 /// because its future was dropped. In arrival order.
1158 ///
1159 /// Without this a caller could not put `accept_namespace_stream` in a
1160 /// `select!` at all: losing the race would lose a stream the peer had
1161 /// already opened and, with it, whatever of the request had arrived.
1162 ///
1163 /// Behind a mutex because the lock is only ever held for a push or a pop,
1164 /// never across an await.
1165 pending_inbound: Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1166}
1167
1168impl Connection {
1169 /// Connect to a MoQT server as a client.
1170 ///
1171 /// Establishes a QUIC or WebTransport connection (based on
1172 /// `config.transport`), opens a bidirectional control stream,
1173 /// performs the CLIENT_SETUP / SERVER_SETUP handshake, and returns
1174 /// a ready-to-use connection.
1175 pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
1176 // PATH is for native QUIC only, and the transport is known here and
1177 // nowhere further in. Refusing before dialling means a session that
1178 // the server would close on sight is never opened.
1179 setup::validate_client_path_transport(
1180 &config.setup_parameters,
1181 matches!(config.transport, TransportType::WebTransport { .. }),
1182 )
1183 .map_err(EndpointError::from)?;
1184
1185 let transport = match &config.transport {
1186 TransportType::Quic => Self::connect_quic(addr, &config).await?,
1187 TransportType::WebTransport { url } => {
1188 let url = url.clone();
1189 Self::connect_webtransport(&url, &config).await?
1190 }
1191 };
1192
1193 Self::adopt(transport, config).await
1194 }
1195
1196 /// Run the MoQT setup handshake over a transport somebody else established.
1197 ///
1198 /// For choosing the draft from what the server selected: dial once through
1199 /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
1200 /// connection to the module its answer names. [`Self::connect`] cannot do
1201 /// this — it derives its single ALPN from the draft it was given.
1202 ///
1203 /// `config.draft` must match this module. The transport is adopted as
1204 /// given; nothing here re-checks the ALPN it was negotiated with.
1205 pub async fn adopt(
1206 transport: Transport,
1207 config: ClientConfig,
1208 ) -> Result<Self, ConnectionError> {
1209 Self::adopt_with(transport, config, None).await
1210 }
1211
1212 /// [`Self::adopt`] with `observer` attached before the handshake.
1213 ///
1214 /// The SETUP this side sends is reported as it is written, and a SETUP from
1215 /// the peer that this side refuses as [`ClientEvent::ControlMessageRefused`].
1216 /// [`Self::set_observer`] after `adopt` sees the same handshake events, but
1217 /// only once the handshake has succeeded.
1218 pub async fn adopt_observed(
1219 transport: Transport,
1220 config: ClientConfig,
1221 observer: Box<dyn ConnectionObserver>,
1222 ) -> Result<Self, ConnectionError> {
1223 Self::adopt_with(transport, config, Some(observer)).await
1224 }
1225
1226 /// [`Self::adopt`], reporting to `observer` from the first byte written.
1227 pub(crate) async fn adopt_with(
1228 transport: Transport,
1229 config: ClientConfig,
1230 observer: Option<Box<dyn ConnectionObserver>>,
1231 ) -> Result<Self, ConnectionError> {
1232 let draft = config.draft;
1233 // PATH is for native QUIC only, and the transport is known here and
1234 // nowhere further in. Refusing before dialling means a session that
1235 // the server would close on sight is never opened.
1236 setup::validate_client_path_transport(
1237 &config.setup_parameters,
1238 matches!(config.transport, TransportType::WebTransport { .. }),
1239 )
1240 .map_err(EndpointError::from)?;
1241
1242 // Open bidirectional control stream
1243 let (send, recv) = transport.open_bi().await?;
1244 let mut control_send = FramedSendStream::new(send, draft);
1245 let mut control_recv = FramedRecvStream::new(recv, draft);
1246
1247 // Perform setup handshake (draft-16: no versions)
1248 let mut endpoint = Endpoint::new(Role::Client);
1249 endpoint.connect()?;
1250 let setup_msg = endpoint.send_client_setup(config.setup_parameters.clone())?;
1251 let any_setup = AnyControlMessage::Draft16(setup_msg);
1252 let raw_setup = control_send.write_control(&any_setup).await?;
1253 // Reported as written, so a peer that never answers still leaves it
1254 // observed.
1255 if let Some(obs) = &observer {
1256 obs.on_event_owned(ClientEvent::ControlMessage {
1257 direction: Direction::Send,
1258 message: any_setup.clone(),
1259 stream_id: None,
1260 raw: Some(raw_setup.clone()),
1261 });
1262 }
1263
1264 let (server_setup, raw_server_setup) = match control_recv.read_control(true).await {
1265 Ok(read) => read,
1266 Err(e) => {
1267 if let Some(obs) = &observer {
1268 if let Some(event) = Self::refused_event(&e, None, control_recv.take_refused())
1269 {
1270 obs.on_event_owned(event);
1271 }
1272 }
1273 return Err(e);
1274 }
1275 };
1276 // Unwrap to draft-16 for the endpoint
1277 match &server_setup {
1278 AnyControlMessage::Draft16(ControlMessage::ServerSetup(ref ss)) => {
1279 if let Err(e) = endpoint.receive_server_setup(ss) {
1280 let err = ConnectionError::from(e);
1281 Self::refuse_setup(observer.as_deref(), &err, &server_setup, &raw_server_setup);
1282 return Err(err);
1283 }
1284 }
1285 _ => {
1286 return Err(ConnectionError::Endpoint(EndpointError::NotActive));
1287 }
1288 }
1289
1290 let mut pending_events = vec![
1291 ClientEvent::ControlMessage {
1292 direction: Direction::Send,
1293 message: any_setup,
1294 stream_id: None,
1295 raw: Some(raw_setup),
1296 },
1297 ClientEvent::ControlMessage {
1298 direction: Direction::Receive,
1299 message: server_setup.clone(),
1300 stream_id: None,
1301 raw: raw_server_setup.clone(),
1302 },
1303 ClientEvent::SetupComplete { negotiated_version: 0xff000000 + 16 },
1304 ];
1305
1306 if let Some(obs) = &observer {
1307 // The SETUP this side sent was reported as it was written.
1308 for event in pending_events.drain(..).skip(1) {
1309 obs.on_event_owned(event);
1310 }
1311 }
1312
1313 Ok(Self {
1314 transport,
1315 endpoint,
1316 draft,
1317 control_send: Some(tokio::sync::Mutex::new(control_send)),
1318 control_recv: Some(control_recv),
1319 observer,
1320 pending_events,
1321 server_setup,
1322 server_setup_raw: raw_server_setup,
1323 pending_inbound: Mutex::new(VecDeque::new()),
1324 })
1325 }
1326
1327 /// Establish a raw QUIC connection.
1328 ///
1329 /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
1330 /// TLS and endpoint setup.
1331 async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
1332 let (transport, _negotiated) = crate::transport::dial_quic(
1333 addr,
1334 &crate::transport::QuicDialOptions {
1335 skip_cert_verification: config.skip_cert_verification,
1336 ca_certs: config.ca_certs.clone(),
1337 ..crate::transport::QuicDialOptions::new(config.alpn())
1338 },
1339 )
1340 .await?;
1341 Ok(transport)
1342 }
1343
1344 /// Establish a WebTransport connection.
1345 ///
1346 /// [`crate::transport::dial_webtransport`] holds the TLS and endpoint
1347 /// setup, exactly as `connect_quic` above defers its own. That is not
1348 /// only deduplication: both dials must trust the same roots. Settling trust
1349 /// at this call site instead — from `wtransport`'s own builder settings, or
1350 /// from a second config of this draft's own — puts the decision in two
1351 /// places, where it can stop matching what the QUIC dial trusts, so one
1352 /// relay would pass on one transport and fail on the other and a caller's
1353 /// private CA would reach only the dials whose call site installed it.
1354 /// Both ask the same function what to trust.
1355 #[cfg(feature = "webtransport")]
1356 async fn connect_webtransport(
1357 url: &str,
1358 config: &ClientConfig,
1359 ) -> Result<Transport, ConnectionError> {
1360 Ok(crate::transport::dial_webtransport(
1361 url,
1362 &crate::transport::QuicDialOptions {
1363 skip_cert_verification: config.skip_cert_verification,
1364 ca_certs: config.ca_certs.clone(),
1365 // The draft's own protocol identifier. Section 3 gives this
1366 // draft two version-negotiation channels and one of them per
1367 // transport: an ALPN over QUIC, and the WT-Available-Protocols
1368 // header over WebTransport. `config.alpn()` above is `h3`,
1369 // which is the HTTP/3 name and settles no version, so without
1370 // this the draft would be named nowhere.
1371 wt_protocols: vec![config.draft.quic_alpn().to_vec()],
1372 ..crate::transport::QuicDialOptions::new(config.alpn())
1373 },
1374 )
1375 .await?)
1376 }
1377
1378 /// Stub for when the webtransport feature is not enabled.
1379 #[cfg(not(feature = "webtransport"))]
1380 async fn connect_webtransport(
1381 _url: &str,
1382 _config: &ClientConfig,
1383 ) -> Result<Transport, ConnectionError> {
1384 Err(ConnectionError::Transport(TransportError::Connect(
1385 "webtransport feature not enabled".into(),
1386 )))
1387 }
1388
1389 // -- Observer ---------------------------------------------------
1390
1391 /// Attach an observer. Buffered handshake events from `connect()` are
1392 /// flushed in arrival order before this returns.
1393 pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
1394 self.observer = Some(observer);
1395 for event in self.pending_events.drain(..) {
1396 if let Some(ref obs) = self.observer {
1397 obs.on_event_owned(event);
1398 }
1399 }
1400 }
1401
1402 /// Remove the observer.
1403 pub fn clear_observer(&mut self) {
1404 self.observer = None;
1405 }
1406
1407 /// How the QUIC connection was closed, once it has been. See
1408 /// [`TransportClose`](crate::transport::TransportClose).
1409 pub fn transport_close(&self) -> Option<crate::transport::TransportClose> {
1410 self.transport.transport_close()
1411 }
1412
1413 /// Emit an event to the observer, if one is attached.
1414 fn emit(&self, event: ClientEvent) {
1415 if let Some(ref obs) = self.observer {
1416 obs.on_event_owned(event);
1417 }
1418 }
1419
1420 /// Report a received frame this endpoint refused, when an observer is
1421 /// attached and the refusal is the peer's: see [`Self::refused_event`].
1422 fn emit_refused(&self, err: &ConnectionError, stream_id: Option<u64>, raw: Option<Vec<u8>>) {
1423 if self.observer.is_some() {
1424 if let Some(event) = Self::refused_event(err, stream_id, raw) {
1425 self.emit(event);
1426 }
1427 }
1428 }
1429
1430 /// The [`ClientEvent::ControlMessageRefused`] for `raw`, when `err` refuses
1431 /// what the peer sent: the decoder refused it, or it broke a rule
1432 /// [`EndpointError::fault`] or [`Self::draft_specific_cause`] lays on the
1433 /// peer.
1434 fn refused_event(
1435 err: &ConnectionError,
1436 stream_id: Option<u64>,
1437 raw: Option<Vec<u8>>,
1438 ) -> Option<ClientEvent> {
1439 use crate::above_codec_rules::{DraftSpecificCause, EndpointFault};
1440 let peers = match err {
1441 ConnectionError::Codec(_) | ConnectionError::VarInt(_) => true,
1442 ConnectionError::Endpoint(e) => matches!(e.fault(), EndpointFault::Peer(_)),
1443 other => matches!(
1444 Self::draft_specific_cause(other),
1445 Some(DraftSpecificCause::PeerViolation { .. } | DraftSpecificCause::Codec { .. })
1446 ),
1447 };
1448 let raw = raw.filter(|_| peers)?;
1449 Some(ClientEvent::ControlMessageRefused { stream_id, raw })
1450 }
1451
1452 /// Report a peer's SETUP the handshake refused to an observer attached
1453 /// before it: the message as received, then the refusal.
1454 fn refuse_setup(
1455 observer: Option<&dyn ConnectionObserver>,
1456 err: &ConnectionError,
1457 setup: &AnyControlMessage,
1458 raw: &Option<Vec<u8>>,
1459 ) {
1460 let Some(obs) = observer else { return };
1461 obs.on_event_owned(ClientEvent::ControlMessage {
1462 direction: Direction::Receive,
1463 message: setup.clone(),
1464 stream_id: None,
1465 raw: raw.clone(),
1466 });
1467 if let Some(event) = Self::refused_event(err, None, raw.clone()) {
1468 obs.on_event_owned(event);
1469 }
1470 }
1471
1472 // -- Control message I/O ----------------------------------------
1473
1474 /// Send a control message on the control stream.
1475 ///
1476 /// Wraps the draft-16 message in `AnyControlMessage::Draft16` for
1477 /// framing.
1478 pub async fn send_control(&self, msg: &ControlMessage) -> Result<(), ConnectionError> {
1479 let any = AnyControlMessage::Draft16(msg.clone());
1480 let mut send =
1481 self.control_send.as_ref().ok_or(ConnectionError::NoControlStream)?.lock().await;
1482 let raw = send.write_control(&any).await?;
1483 drop(send);
1484 self.emit(ClientEvent::ControlMessage {
1485 direction: Direction::Send,
1486 message: any,
1487 stream_id: None,
1488 raw: Some(raw),
1489 });
1490 Ok(())
1491 }
1492
1493 /// Read the next control message from the control stream.
1494 ///
1495 /// Returns the `AnyControlMessage` and also extracts the draft-16
1496 /// `ControlMessage` for internal endpoint dispatch.
1497 pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
1498 self.recv_control_raw().await.map(|(msg, _)| msg)
1499 }
1500
1501 /// [`Self::recv_control`], keeping the frame's wire bytes when an observer
1502 /// is attached.
1503 async fn recv_control_raw(
1504 &mut self,
1505 ) -> Result<(ControlMessage, Option<Vec<u8>>), ConnectionError> {
1506 let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
1507 let capture_raw = self.observer.is_some();
1508 let (any, raw) = match recv.read_control(capture_raw).await {
1509 Ok(v) => v,
1510 Err(e) => {
1511 let raw = recv.take_refused();
1512 let err = self.close_for_codec(e);
1513 self.emit_refused(&err, None, raw);
1514 return Err(err);
1515 }
1516 };
1517 if capture_raw {
1518 self.emit(ClientEvent::ControlMessage {
1519 direction: Direction::Receive,
1520 message: any.clone(),
1521 stream_id: None,
1522 raw: raw.clone(),
1523 });
1524 }
1525 // Unwrap to draft-16 for the endpoint
1526 match any {
1527 AnyControlMessage::Draft16(msg) => Ok((msg, raw)),
1528 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 16 the
1529 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
1530 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
1531 // naming the other drafts: such a list has to be edited in every draft module
1532 // whenever a draft is added, and a copy that omits one leaves this match
1533 // non-exhaustive.
1534 #[allow(unreachable_patterns)]
1535 _ => Err(ConnectionError::ControlMessageNarrowing),
1536 }
1537 }
1538
1539 /// Read and dispatch the next incoming control message through the
1540 /// endpoint state machine. Returns the decoded message for inspection.
1541 pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
1542 let (msg, raw) = self.recv_control_raw().await?;
1543 if let Err(e) = self.endpoint.receive_message(msg.clone()) {
1544 let err = self.close_if_session_fatal(e);
1545 self.emit_refused(&err, None, raw);
1546 return Err(err);
1547 }
1548
1549 // Emit draining event if this was a GoAway
1550 if let ControlMessage::GoAway(ref ga) = msg {
1551 self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
1552 }
1553
1554 Ok(msg)
1555 }
1556
1557 // -- Subscribe flow ---------------------------------------------
1558
1559 /// Send a SUBSCRIBE and return the allocated request ID.
1560 pub async fn subscribe(
1561 &mut self,
1562 track_namespace: TrackNamespace,
1563 track_name: Vec<u8>,
1564 parameters: Vec<KeyValuePair>,
1565 ) -> Result<VarInt, ConnectionError> {
1566 let (req_id, msg) = self.endpoint.subscribe(track_namespace, track_name, parameters)?;
1567 self.send_control(&msg).await?;
1568 Ok(req_id)
1569 }
1570
1571 /// Send an UNSUBSCRIBE for the given request ID.
1572 pub async fn unsubscribe(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1573 let msg = self.endpoint.unsubscribe(request_id)?;
1574 self.send_control(&msg).await
1575 }
1576
1577 /// Accept a subscription the peer opened, sending SUBSCRIBE_OK and giving
1578 /// its track a Track Alias.
1579 ///
1580 /// The endpoint refuses an alias a live track of its own already holds and
1581 /// refuses a second answer to one SUBSCRIBE, so nothing is written on the
1582 /// wire when it does either.
1583 pub async fn subscribe_ok(
1584 &mut self,
1585 request_id: VarInt,
1586 track_alias: VarInt,
1587 track_extensions: Vec<KeyValuePair>,
1588 parameters: Vec<KeyValuePair>,
1589 ) -> Result<(), ConnectionError> {
1590 let msg = self.endpoint.send_subscribe_ok(
1591 request_id,
1592 track_alias,
1593 track_extensions,
1594 parameters,
1595 )?;
1596 self.send_control(&msg).await
1597 }
1598
1599 /// Refuse a request the peer opened, sending REQUEST_ERROR.
1600 ///
1601 /// One message refuses a SUBSCRIBE or a FETCH, and the endpoint finds
1602 /// which by the identifier. It refuses a second answer to either, and
1603 /// refuses a Joining Fetch's refusal under any code but the one the draft
1604 /// names for it, so nothing is written on the wire when it does.
1605 pub async fn request_error(
1606 &mut self,
1607 request_id: VarInt,
1608 error_code: VarInt,
1609 retry_interval: VarInt,
1610 reason_phrase: Vec<u8>,
1611 ) -> Result<(), ConnectionError> {
1612 let msg = self.endpoint.send_request_error(
1613 request_id,
1614 error_code,
1615 retry_interval,
1616 reason_phrase,
1617 )?;
1618 self.send_control(&msg).await
1619 }
1620
1621 /// Narrow a subscription this endpoint opened, sending REQUEST_UPDATE, and
1622 /// return the Request ID the update itself spent.
1623 pub async fn request_update(
1624 &mut self,
1625 existing_request_id: VarInt,
1626 parameters: Vec<KeyValuePair>,
1627 ) -> Result<VarInt, ConnectionError> {
1628 let (request_id, msg) = self.endpoint.request_update(existing_request_id, parameters)?;
1629 self.send_control(&msg).await?;
1630 Ok(request_id)
1631 }
1632
1633 /// Accept a PUBLISH the peer sent, which establishes the subscription it
1634 /// opened.
1635 ///
1636 /// The endpoint refuses a second answer to one PUBLISH, so nothing is
1637 /// written on the wire when it does.
1638 pub async fn publish_ok(
1639 &mut self,
1640 request_id: VarInt,
1641 parameters: Vec<KeyValuePair>,
1642 ) -> Result<(), ConnectionError> {
1643 let msg = self.endpoint.send_publish_ok(request_id, parameters)?;
1644 self.send_control(&msg).await
1645 }
1646
1647 /// Reject a PUBLISH the peer sent, which ends the subscription it opened
1648 /// before it was established.
1649 ///
1650 /// The endpoint refuses a second answer to one PUBLISH, so nothing is
1651 /// written on the wire when it does.
1652 pub async fn publish_error(
1653 &mut self,
1654 request_id: VarInt,
1655 error_code: VarInt,
1656 retry_interval: VarInt,
1657 reason_phrase: Vec<u8>,
1658 ) -> Result<(), ConnectionError> {
1659 let msg = self.endpoint.send_publish_error(
1660 request_id,
1661 error_code,
1662 retry_interval,
1663 reason_phrase,
1664 )?;
1665 self.send_control(&msg).await
1666 }
1667
1668 // -- Fetch flow -------------------------------------------------
1669
1670 /// Send a standalone FETCH and return the allocated request ID.
1671 #[allow(clippy::too_many_arguments)]
1672 pub async fn fetch(
1673 &mut self,
1674 track_namespace: TrackNamespace,
1675 track_name: Vec<u8>,
1676 start_group: VarInt,
1677 start_object: VarInt,
1678 end_group: VarInt,
1679 end_object: VarInt,
1680 parameters: Vec<KeyValuePair>,
1681 ) -> Result<VarInt, ConnectionError> {
1682 let (req_id, msg) = self.endpoint.fetch(
1683 track_namespace,
1684 track_name,
1685 start_group,
1686 start_object,
1687 end_group,
1688 end_object,
1689 parameters,
1690 )?;
1691 self.send_control(&msg).await?;
1692 Ok(req_id)
1693 }
1694
1695 /// Send a Relative Joining Fetch and return the allocated request ID.
1696 ///
1697 /// `joining_start` counts groups back from the subscription's largest
1698 /// group. To name the starting group outright, use
1699 /// [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1700 pub async fn joining_fetch(
1701 &mut self,
1702 joining_request_id: VarInt,
1703 joining_start: VarInt,
1704 parameters: Vec<KeyValuePair>,
1705 ) -> Result<VarInt, ConnectionError> {
1706 let (req_id, msg) =
1707 self.endpoint.joining_fetch(joining_request_id, joining_start, parameters)?;
1708 self.send_control(&msg).await?;
1709 Ok(req_id)
1710 }
1711
1712 /// Send an Absolute Joining Fetch and return the allocated request ID.
1713 ///
1714 /// Here `joining_start` is the group to begin at rather than an offset,
1715 /// which is what an application that knows the group it wants has: draft-16
1716 /// Section 9.16.2.1 has the publisher set the Start Location to
1717 /// {Joining Start, 0}.
1718 pub async fn absolute_joining_fetch(
1719 &mut self,
1720 joining_request_id: VarInt,
1721 joining_start: VarInt,
1722 parameters: Vec<KeyValuePair>,
1723 ) -> Result<VarInt, ConnectionError> {
1724 let (req_id, msg) =
1725 self.endpoint.absolute_joining_fetch(joining_request_id, joining_start, parameters)?;
1726 self.send_control(&msg).await?;
1727 Ok(req_id)
1728 }
1729
1730 /// Send a FETCH_CANCEL for the given request ID.
1731 pub async fn fetch_cancel(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1732 let msg = self.endpoint.fetch_cancel(request_id)?;
1733 self.send_control(&msg).await
1734 }
1735
1736 /// Accept a fetch the peer opened, sending FETCH_OK.
1737 ///
1738 /// The endpoint refuses a Joining Fetch naming a subscription this session
1739 /// cannot join and refuses a second answer to one FETCH, so nothing is
1740 /// written on the wire when it does either.
1741 pub async fn fetch_ok(
1742 &mut self,
1743 request_id: VarInt,
1744 end_of_track: u8,
1745 end_group: VarInt,
1746 end_object: VarInt,
1747 parameters: Vec<KeyValuePair>,
1748 track_extensions: Vec<KeyValuePair>,
1749 ) -> Result<(), ConnectionError> {
1750 let msg = self.endpoint.send_fetch_ok(
1751 request_id,
1752 end_of_track,
1753 end_group,
1754 end_object,
1755 parameters,
1756 track_extensions,
1757 )?;
1758 self.send_control(&msg).await
1759 }
1760
1761 // -- Namespace flows --------------------------------------------
1762
1763 /// Send a SUBSCRIBE_NAMESPACE on a bidirectional stream of its own.
1764 ///
1765 /// Section 6.1: "The subscriber sends SUBSCRIBE_NAMESPACE on a new
1766 /// bidirectional stream and the publisher MUST send a single REQUEST_OK or
1767 /// REQUEST_ERROR as the first message on the bidirectional stream in
1768 /// response to a SUBSCRIBE_NAMESPACE." Every other draft-16 request is
1769 /// still written on the control stream; this is the one that is not.
1770 ///
1771 /// The returned [`NamespaceStream`] **must be held while the subscription
1772 /// is live**. Section 6.1 makes closing the stream the cancellation, so
1773 /// letting the handle fall out of scope withdraws the subscription — see
1774 /// the type's own note.
1775 ///
1776 /// `subscribe_options` selects what the publisher reports back: PUBLISH
1777 /// (0x00), NAMESPACE (0x01) or both (0x02), per Section 9.25.
1778 ///
1779 /// # Ordering
1780 ///
1781 /// The stream is opened before the Request ID is allocated, because a
1782 /// failed open would otherwise burn an id the endpoint cannot retract. If
1783 /// the endpoint refuses the request the stream is reset rather than
1784 /// dropped: dropping would FIN it, which on this draft says a
1785 /// subscription that was never made has been withdrawn.
1786 pub async fn subscribe_namespace(
1787 &mut self,
1788 namespace_prefix: TrackNamespace,
1789 subscribe_options: VarInt,
1790 parameters: Vec<KeyValuePair>,
1791 ) -> Result<NamespaceStream, ConnectionError> {
1792 let (send, recv) = self.transport.open_bi().await?;
1793 let mut send = FramedSendStream::new(send, self.draft);
1794 let mut recv = FramedRecvStream::new(recv, self.draft);
1795
1796 let (req_id, msg) = match self.endpoint.subscribe_namespace(
1797 namespace_prefix,
1798 subscribe_options,
1799 parameters,
1800 ) {
1801 Ok(built) => built,
1802 Err(e) => {
1803 let _ = send.reset(STREAM_ABANDONED);
1804 let _ = recv.stop(STREAM_ABANDONED);
1805 return Err(ConnectionError::Endpoint(e));
1806 }
1807 };
1808
1809 let stream_id = send.stream_id();
1810 self.emit(ClientEvent::StreamOpened {
1811 direction: Direction::Send,
1812 stream_kind: StreamKind::NamespaceSubscription,
1813 stream_id,
1814 });
1815 let any = AnyControlMessage::Draft16(msg);
1816 let raw = match send.write_control(&any).await {
1817 Ok(raw) => raw,
1818 Err(e) => {
1819 let _ = send.reset(STREAM_ABANDONED);
1820 let _ = recv.stop(STREAM_ABANDONED);
1821 return Err(e);
1822 }
1823 };
1824 self.emit(ClientEvent::ControlMessage {
1825 direction: Direction::Send,
1826 message: any,
1827 stream_id: Some(stream_id),
1828 raw: Some(raw),
1829 });
1830 Ok(NamespaceStream {
1831 send,
1832 recv,
1833 request_id: req_id,
1834 draft: self.draft,
1835 stream_id,
1836 origin: RequestOrigin::Local,
1837 closed: false,
1838 responded: false,
1839 })
1840 }
1841
1842 /// Accept the next bidirectional stream the peer opened, read the
1843 /// SUBSCRIBE_NAMESPACE it begins with, and hand back that request and a
1844 /// handle to answer it on.
1845 ///
1846 /// The mirror of [`subscribe_namespace`](Self::subscribe_namespace).
1847 /// Section 3.3 does not say who may open the second kind of bidirectional
1848 /// stream, and a relay subscribing to what a client publishes is the
1849 /// ordinary case, so a client that never calls this can never be asked for
1850 /// its namespaces.
1851 ///
1852 /// The returned [`NamespaceStream`] carries [`RequestOrigin::Peer`].
1853 /// Answer it with
1854 /// [`respond_ok_on_namespace_stream`](Self::respond_ok_on_namespace_stream)
1855 /// or
1856 /// [`respond_error_on_namespace_stream`](Self::respond_error_on_namespace_stream),
1857 /// and **hold it for as long as the subscription lasts** — every NAMESPACE
1858 /// and NAMESPACE_DONE is written on it, and dropping it ends the
1859 /// subscription.
1860 ///
1861 /// # Two refusals, two codes
1862 ///
1863 /// Section 3.3, on a stream that begins with the wrong type:
1864 /// "Bidirectional streams MUST NOT begin with any other message type
1865 /// unless negotiated. If they do, the peer MUST close the Session with a
1866 /// Protocol Violation." Section 9.1, on the Request ID: "If an endpoint
1867 /// receives a Request ID that is not valid for the peer, or a new request
1868 /// with a Request ID that is not the next in sequence or exceeds the
1869 /// received MAX_REQUEST_ID, it MUST close the session with
1870 /// INVALID_REQUEST_ID." Both are closes of the session on the
1871 /// wire, with different codes, and both happen before this returns — the
1872 /// error handed back reports a session that is already gone, not one the
1873 /// caller must remember to close.
1874 ///
1875 /// The refusal cannot be built without the acceptance. An endpoint that
1876 /// took a bidirectional stream only to refuse everything on it would close
1877 /// sessions over the SUBSCRIBE_NAMESPACE the same sentence permits.
1878 ///
1879 /// # Cancelling this future loses nothing
1880 ///
1881 /// A stream taken off the transport but not yet read is put back on an
1882 /// internal queue, and the next call takes it before accepting anything
1883 /// new — including whatever bytes of the request had already arrived,
1884 /// which live in the stream's own reader. So this is safe to `select!`
1885 /// against a shutdown signal or a timer. See
1886 /// [`pending_inbound_count`](Self::pending_inbound_count).
1887 ///
1888 /// What it is **not** safe to do is run concurrently with another method
1889 /// on the same connection: this takes `&mut self` because registering the
1890 /// peer's request moves endpoint state.
1891 ///
1892 /// # Ordering
1893 ///
1894 /// The endpoint is told about the request last, after every step that can
1895 /// fail or be cancelled, and building the handle afterwards cannot fail.
1896 /// Registering earlier would let a cancelled accept leave a state machine
1897 /// keyed to a stream nobody holds, with the peer's Request ID spent on a
1898 /// request nobody answers.
1899 ///
1900 /// # Errors
1901 ///
1902 /// - [`ConnectionError::NonSubscribeNamespaceOnBidiStream`] — the session
1903 /// has been closed with PROTOCOL_VIOLATION and the stream reset.
1904 /// - [`ConnectionError::Endpoint`] carrying `RequestId` — the session has
1905 /// been closed with INVALID_REQUEST_ID and the stream reset.
1906 /// - [`ConnectionError::Endpoint`] carrying `NotActive` or `Draining` —
1907 /// the stream is reset, the session is left alone.
1908 /// - [`ConnectionError::Transport`] — the stream is reset, the session is
1909 /// left alone.
1910 /// - [`ConnectionError::Codec`] — the stream is reset, and the session is
1911 /// closed with the code [`Self::codec_session_error_code`] names, where it
1912 /// names one. Section 9.25: the subscriber sends "a SUBSCRIBE_NAMESPACE
1913 /// control message on a new bidirectional stream", so the control message
1914 /// rules hold on this stream too.
1915 pub async fn accept_namespace_stream(
1916 &mut self,
1917 ) -> Result<(ControlMessage, NamespaceStream), ConnectionError> {
1918 let pair = match self.take_pending_inbound() {
1919 Some(pair) => pair,
1920 None => {
1921 let (send, recv) = self.transport.accept_bi().await?;
1922 (FramedSendStream::new(send, self.draft), FramedRecvStream::new(recv, self.draft))
1923 }
1924 };
1925 let capture_raw = self.observer.is_some();
1926
1927 let (any, raw, mut send, mut recv) = {
1928 let mut pending = PendingInbound { pair: Some(pair), queue: &self.pending_inbound };
1929 let read = {
1930 let (_, recv) = pending.pair.as_mut().expect("set on construction");
1931 recv.read_control(capture_raw).await
1932 };
1933 // Taken out before anything can return, so the guard's Drop puts
1934 // the pair back for exactly one reason: this future was cancelled.
1935 let (mut send, mut recv) = pending.pair.take().expect("set on construction");
1936 match read {
1937 Ok((any, raw)) => (any, raw, send, recv),
1938 Err(e) => {
1939 // A stream whose first message could not be read is not
1940 // worth queueing: the next accept would fail on it the
1941 // same way. Reset rather than FIN — nothing was served.
1942 let _ = send.reset(STREAM_ABANDONED);
1943 let _ = recv.stop(STREAM_ABANDONED);
1944 let raw = recv.take_refused();
1945 let err = self.close_for_codec(e);
1946 self.emit_refused(&err, Some(send.stream_id()), raw);
1947 return Err(err);
1948 }
1949 }
1950 };
1951
1952 // Reported once the request has actually arrived rather than when the
1953 // stream came off the transport, so a cancelled accept that is retried
1954 // does not report the same stream twice.
1955 let stream_id = send.stream_id();
1956 self.emit(ClientEvent::StreamOpened {
1957 direction: Direction::Receive,
1958 stream_kind: StreamKind::NamespaceSubscription,
1959 stream_id,
1960 });
1961 if capture_raw {
1962 self.emit(ClientEvent::ControlMessage {
1963 direction: Direction::Receive,
1964 message: any.clone(),
1965 stream_id: Some(stream_id),
1966 raw: raw.clone(),
1967 });
1968 }
1969
1970 let msg = match any {
1971 AnyControlMessage::Draft16(msg) => msg,
1972 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 16 the
1973 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
1974 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
1975 // naming the other drafts: such a list has to be edited in every draft module
1976 // whenever a draft is added, and a copy that omits one leaves this match
1977 // non-exhaustive.
1978 #[allow(unreachable_patterns)]
1979 _ => {
1980 let _ = send.reset(STREAM_ABANDONED);
1981 let _ = recv.stop(STREAM_ABANDONED);
1982 return Err(ConnectionError::ControlMessageNarrowing);
1983 }
1984 };
1985
1986 let ty = msg.message_type();
1987 if ty != MessageType::SubscribeNamespace {
1988 let err = self.endpoint.refuse_non_subscribe_namespace(ty);
1989 self.close_for(&err);
1990 let _ = send.reset(STREAM_ABANDONED);
1991 let _ = recv.stop(STREAM_ABANDONED);
1992 let refused = ConnectionError::NonSubscribeNamespaceOnBidiStream(ty);
1993 self.emit_refused(&refused, Some(stream_id), raw);
1994 return Err(refused);
1995 }
1996
1997 let request_id = match self.endpoint.receive_subscribe_namespace_on_stream(&msg) {
1998 Ok(request_id) => request_id,
1999 Err(e) => {
2000 let _ = send.reset(STREAM_ABANDONED);
2001 let _ = recv.stop(STREAM_ABANDONED);
2002 let err = self.close_if_session_fatal(e);
2003 self.emit_refused(&err, Some(stream_id), raw);
2004 return Err(err);
2005 }
2006 };
2007
2008 Ok((
2009 msg,
2010 NamespaceStream {
2011 send,
2012 recv,
2013 request_id,
2014 draft: self.draft,
2015 stream_id,
2016 origin: RequestOrigin::Peer,
2017 closed: false,
2018 responded: false,
2019 },
2020 ))
2021 }
2022
2023 /// Take the oldest stream pair a cancelled
2024 /// [`accept_namespace_stream`](Self::accept_namespace_stream) put back, if
2025 /// any.
2026 ///
2027 /// Synchronous on purpose: the guard is dropped before the caller awaits,
2028 /// so the lock is never held across a suspension point.
2029 fn take_pending_inbound(&self) -> Option<(FramedSendStream, FramedRecvStream)> {
2030 self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
2031 }
2032
2033 /// How many peer-opened namespace streams a cancelled
2034 /// [`accept_namespace_stream`](Self::accept_namespace_stream) put back and
2035 /// a later call has not yet taken.
2036 ///
2037 /// Zero unless an accept future was dropped mid-read.
2038 pub fn pending_inbound_count(&self) -> usize {
2039 self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
2040 }
2041
2042 /// Read the next message off a namespace subscription's stream and
2043 /// dispatch it through the endpoint.
2044 ///
2045 /// Three things can come back, and each is one of the shapes Section 6.1
2046 /// and Section 9.25 describe:
2047 ///
2048 /// - `Ok(Some(msg))` — a REQUEST_OK or REQUEST_ERROR answering the
2049 /// subscription, or a NAMESPACE or NAMESPACE_DONE reporting on it.
2050 /// - `Ok(None)` — the peer finished its half with a FIN. Section 6.1 makes
2051 /// that a cancellation, and it is recorded here.
2052 /// - `Err` carrying [`TransportError::StreamReset`] — the peer reset the
2053 /// stream, the other form of the same cancellation, also recorded.
2054 ///
2055 /// This blocks until a whole message has arrived. Backpressure is per
2056 /// subscription: a stream nobody reads stays unread, and the peer stays
2057 /// flow controlled on it alone.
2058 ///
2059 /// # A stream the peer opened reports but does not dispatch
2060 ///
2061 /// Draft-16 places nothing after the SUBSCRIBE_NAMESPACE on the
2062 /// subscriber's half, so on a [`RequestOrigin::Peer`] stream there is no
2063 /// state for a second message to move and none is attempted; what a
2064 /// responder reads for is the peer's FIN. A message that arrives anyway is
2065 /// handed back rather than refused, because no sentence in this draft
2066 /// forbids it.
2067 ///
2068 /// # Errors
2069 ///
2070 /// [`ConnectionError::Endpoint`] if the message does not fit the
2071 /// subscription's state, or names a different request than this stream
2072 /// carries. The message has already been emitted to the observer by
2073 /// then — what arrived is reported whether or not the endpoint accepts it.
2074 ///
2075 /// [`ConnectionError::Codec`] if the decoder refused the frame. The session is
2076 /// closed with the code [`Self::codec_session_error_code`] names, where it
2077 /// names one, as on the control stream.
2078 pub async fn recv_on_namespace_stream(
2079 &mut self,
2080 stream: &mut NamespaceStream,
2081 ) -> Result<Option<ControlMessage>, ConnectionError> {
2082 let capture_raw = self.observer.is_some();
2083 let read = match stream.recv.read_control_or_end(capture_raw).await {
2084 Ok(read) => read,
2085 Err(e) => {
2086 // A peer that reset this stream cancelled the subscription on
2087 // it, and this is where a caller reading normally learns of
2088 // it. The record is made and its verdict dropped: the read's
2089 // own error is what the caller has to act on, and returning a
2090 // state error in its place would hide a reset behind it.
2091 if matches!(e, ConnectionError::Transport(TransportError::StreamReset(_))) {
2092 let _ = self.endpoint.cancel_namespace_subscription(stream.request_id);
2093 }
2094 let raw = stream.recv.take_refused();
2095 let err = self.close_for_codec(e);
2096 self.emit_refused(&err, Some(stream.stream_id), raw);
2097 return Err(err);
2098 }
2099 };
2100 let Some((any, raw)) = read else {
2101 // The other half of Section 6.1's sentence. Recorded the same way
2102 // and for the same reason as the reset above.
2103 let _ = self.endpoint.cancel_namespace_subscription(stream.request_id);
2104 return Ok(None);
2105 };
2106 if capture_raw {
2107 self.emit(ClientEvent::ControlMessage {
2108 direction: Direction::Receive,
2109 message: any.clone(),
2110 stream_id: Some(stream.stream_id),
2111 raw: raw.clone(),
2112 });
2113 }
2114 let msg = match any {
2115 AnyControlMessage::Draft16(msg) => Ok::<_, ConnectionError>(msg),
2116 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 16 the
2117 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
2118 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
2119 // naming the other drafts: such a list has to be edited in every draft module
2120 // whenever a draft is added, and a copy that omits one leaves this match
2121 // non-exhaustive.
2122 #[allow(unreachable_patterns)]
2123 _ => Err(ConnectionError::ControlMessageNarrowing),
2124 }?;
2125 if stream.origin == RequestOrigin::Local {
2126 if let Err(e) = self.endpoint.receive_on_namespace_stream(stream.request_id, &msg) {
2127 let err = self.close_if_session_fatal(e);
2128 self.emit_refused(&err, Some(stream.stream_id), raw);
2129 return Err(err);
2130 }
2131 }
2132 Ok(Some(msg))
2133 }
2134
2135 /// Write a message on an open namespace subscription stream.
2136 ///
2137 /// This is for what follows the answer: Section 9.25 says the publisher
2138 /// "will send matching NAMESPACE messages on the response stream if they
2139 /// are requested", and NAMESPACE_DONE withdraws one of them on the same
2140 /// stream. The answer itself has its own helpers, which drive the endpoint
2141 /// as well as the wire.
2142 ///
2143 /// It does not refuse any message type: which messages may follow the
2144 /// answer is not something this implementation can settle, so the choice
2145 /// is left to the caller rather than guessed at.
2146 pub async fn send_on_namespace_stream(
2147 &mut self,
2148 stream: &mut NamespaceStream,
2149 msg: &ControlMessage,
2150 ) -> Result<(), ConnectionError> {
2151 let any = AnyControlMessage::Draft16(msg.clone());
2152 let raw = stream.send.write_control(&any).await?;
2153 self.emit(ClientEvent::ControlMessage {
2154 direction: Direction::Send,
2155 message: any,
2156 stream_id: Some(stream.stream_id),
2157 raw: Some(raw),
2158 });
2159 Ok(())
2160 }
2161
2162 /// Accept the peer's SUBSCRIBE_NAMESPACE with a REQUEST_OK on its own
2163 /// stream.
2164 ///
2165 /// Section 6.1: "the publisher MUST send a single REQUEST_OK or
2166 /// REQUEST_ERROR as the first message on the bidirectional stream in
2167 /// response to a SUBSCRIBE_NAMESPACE." The Request ID is taken from the
2168 /// stream rather than from the caller, which is what makes the correlation
2169 /// unforgeable.
2170 ///
2171 /// The endpoint goes first and the message is written only if it agrees.
2172 ///
2173 /// # Errors
2174 ///
2175 /// [`ConnectionError::NotOursToAnswer`] if `stream` was opened by this
2176 /// endpoint, and [`ConnectionError::Endpoint`] if the subscription has
2177 /// already been answered or has ended.
2178 pub async fn respond_ok_on_namespace_stream(
2179 &mut self,
2180 stream: &mut NamespaceStream,
2181 parameters: Vec<KeyValuePair>,
2182 ) -> Result<(), ConnectionError> {
2183 self.respond_on_namespace_stream(
2184 stream,
2185 ControlMessage::RequestOk(RequestOk { request_id: stream.request_id, parameters }),
2186 )
2187 .await
2188 }
2189
2190 /// Refuse the peer's SUBSCRIBE_NAMESPACE with a REQUEST_ERROR on its own
2191 /// stream, and finish the stream.
2192 ///
2193 /// Section 9.25 says what follows the refusal: "If it is an error, the
2194 /// stream will be immediately closed via FIN." So this writes and then
2195 /// finishes, and the handle is closed when it returns.
2196 ///
2197 /// # Errors
2198 ///
2199 /// As [`respond_ok_on_namespace_stream`](Self::respond_ok_on_namespace_stream).
2200 pub async fn respond_error_on_namespace_stream(
2201 &mut self,
2202 stream: &mut NamespaceStream,
2203 error_code: VarInt,
2204 retry_interval: VarInt,
2205 reason_phrase: Vec<u8>,
2206 ) -> Result<(), ConnectionError> {
2207 self.respond_on_namespace_stream(
2208 stream,
2209 ControlMessage::RequestError(RequestError {
2210 request_id: stream.request_id,
2211 error_code,
2212 retry_interval,
2213 reason_phrase,
2214 }),
2215 )
2216 .await?;
2217 stream.finish().await
2218 }
2219
2220 /// Drive the endpoint, then write the answer.
2221 ///
2222 /// The order is the one every request path here uses: a caller acts on a
2223 /// stream after the endpoint has accepted the step, never before.
2224 async fn respond_on_namespace_stream(
2225 &mut self,
2226 stream: &mut NamespaceStream,
2227 msg: ControlMessage,
2228 ) -> Result<(), ConnectionError> {
2229 if stream.origin != RequestOrigin::Peer {
2230 return Err(ConnectionError::NotOursToAnswer(stream.request_id.into_inner()));
2231 }
2232 // Which of the two answers this is, and the code it carries, are both
2233 // in the message, so nothing else has to be told them.
2234 let refusal_code = match &msg {
2235 ControlMessage::RequestError(e) => Some(e.error_code),
2236 _ => None,
2237 };
2238 let driven = self.endpoint.respond_on_namespace_stream(stream.request_id, refusal_code);
2239 driven.map_err(|e| self.close_if_session_fatal(e))?;
2240 let any = AnyControlMessage::Draft16(msg);
2241 let raw = stream.send.write_control(&any).await?;
2242 stream.responded = true;
2243 self.emit(ClientEvent::ControlMessage {
2244 direction: Direction::Send,
2245 message: any,
2246 stream_id: Some(stream.stream_id),
2247 raw: Some(raw),
2248 });
2249 Ok(())
2250 }
2251
2252 /// Withdraw a namespace subscription by resetting its stream: record it at
2253 /// the endpoint, then reset.
2254 ///
2255 /// Section 6.1 puts the withdrawal at the stream — "A SUBSCRIBE_NAMESPACE
2256 /// can be cancelled by closing the stream with either a FIN or
2257 /// RESET_STREAM" — while the subscription's own state lives in the
2258 /// endpoint, so the two have to move together. This and
2259 /// [`finish_namespace_stream`](Self::finish_namespace_stream) are the only
2260 /// places that move both.
2261 ///
2262 /// The endpoint goes first and the stream is reset only if it agrees. A
2263 /// refused withdrawal therefore leaves the stream exactly as it was, and
2264 /// [`NamespaceStream::cancel`] is still there for a caller that wants the
2265 /// stream reset regardless.
2266 ///
2267 /// Idempotent from both ends: a subscription that has already ended
2268 /// accepts it and stays where it is, and a handle that is already closed
2269 /// resets nothing a second time.
2270 ///
2271 /// # Errors
2272 ///
2273 /// [`ConnectionError::Endpoint`] if no namespace subscription carries this
2274 /// stream's id or nothing was ever written on it, and
2275 /// [`ConnectionError::Transport`] if `code` is outside the QUIC varint
2276 /// range — see [`NamespaceStream::cancel`], which is what sends it.
2277 pub fn cancel_namespace_stream(
2278 &mut self,
2279 stream: &mut NamespaceStream,
2280 code: u64,
2281 ) -> Result<(), ConnectionError> {
2282 let recorded = self.endpoint.cancel_namespace_subscription(stream.request_id);
2283 recorded.map_err(|e| self.close_if_session_fatal(e))?;
2284 stream.cancel(code)
2285 }
2286
2287 /// Withdraw a namespace subscription by finishing its stream: record it at
2288 /// the endpoint, then FIN.
2289 ///
2290 /// The other form Section 6.1 allows, and the one that needs no error
2291 /// code. See [`cancel_namespace_stream`](Self::cancel_namespace_stream)
2292 /// for the ordering and the idempotence, which are the same.
2293 pub async fn finish_namespace_stream(
2294 &mut self,
2295 stream: &mut NamespaceStream,
2296 ) -> Result<(), ConnectionError> {
2297 let recorded = self.endpoint.cancel_namespace_subscription(stream.request_id);
2298 recorded.map_err(|e| self.close_if_session_fatal(e))?;
2299 stream.finish().await
2300 }
2301
2302 /// Wait for the peer to reset this subscription's stream, and record it if
2303 /// it does.
2304 ///
2305 /// [`NamespaceStream::peer_cancelled`] with the endpoint's record
2306 /// attached. A caller applying backpressure is deliberately not calling
2307 /// [`recv_on_namespace_stream`](Self::recv_on_namespace_stream), which is
2308 /// the other place a peer reset surfaces, so without this the subscription
2309 /// would end on the wire and stay open in the endpoint's record for as
2310 /// long as the backpressure lasts.
2311 ///
2312 /// It sees a reset and not a FIN — see
2313 /// [`NamespaceStream::peer_cancelled`]. Cancel-safe, and it grants no
2314 /// flow-control credit.
2315 pub async fn peer_cancelled_on_namespace_stream(
2316 &mut self,
2317 stream: &mut NamespaceStream,
2318 ) -> Result<Option<u64>, ConnectionError> {
2319 let code = stream.peer_cancelled().await?;
2320 if code.is_some() {
2321 // Discarded for the reason the read path discards it: the peer has
2322 // ended the subscription whatever the record said, and a state
2323 // error here would replace the answer the caller asked for.
2324 let _ = self.endpoint.cancel_namespace_subscription(stream.request_id);
2325 }
2326 Ok(code)
2327 }
2328
2329 /// Send a PUBLISH_NAMESPACE and return the request ID.
2330 pub async fn publish_namespace(
2331 &mut self,
2332 track_namespace: TrackNamespace,
2333 parameters: Vec<KeyValuePair>,
2334 ) -> Result<VarInt, ConnectionError> {
2335 let (req_id, msg) = self.endpoint.publish_namespace(track_namespace, parameters)?;
2336 self.send_control(&msg).await?;
2337 Ok(req_id)
2338 }
2339
2340 /// Accept a request the peer opened, sending REQUEST_OK.
2341 ///
2342 /// The endpoint refuses a second answer to one request, so nothing is
2343 /// written on the wire when it does. On this draft an announcement and a
2344 /// track status are the requests REQUEST_OK accepts; a subscription, a
2345 /// publication and a fetch each have an acceptance of their own that
2346 /// carries more than this one can.
2347 pub async fn request_ok(
2348 &mut self,
2349 request_id: VarInt,
2350 parameters: Vec<KeyValuePair>,
2351 ) -> Result<(), ConnectionError> {
2352 let msg = self.endpoint.send_request_ok(request_id, parameters)?;
2353 self.send_control(&msg).await
2354 }
2355
2356 /// Revoke an acceptance, sending PUBLISH_NAMESPACE_CANCEL.
2357 ///
2358 /// The endpoint refuses one for an announcement it never accepted, so
2359 /// nothing is written on the wire when it does.
2360 pub async fn publish_namespace_cancel(
2361 &mut self,
2362 request_id: VarInt,
2363 error_code: VarInt,
2364 reason_phrase: Vec<u8>,
2365 ) -> Result<(), ConnectionError> {
2366 let msg = self.endpoint.publish_namespace_cancel(request_id, error_code, reason_phrase)?;
2367 self.send_control(&msg).await
2368 }
2369
2370 /// Withdraw an announcement this endpoint made, sending
2371 /// PUBLISH_NAMESPACE_DONE.
2372 ///
2373 /// The mirror of [`Self::publish_namespace`], and the counterpart of
2374 /// [`Self::publish_namespace_cancel`]: this one ends an announcement of
2375 /// this endpoint's, that one revokes the acceptance of one the peer made.
2376 pub async fn publish_namespace_done(
2377 &mut self,
2378 request_id: VarInt,
2379 ) -> Result<(), ConnectionError> {
2380 let msg = self.endpoint.publish_namespace_done(request_id)?;
2381 self.send_control(&msg).await
2382 }
2383 // -- Track Status flow ------------------------------------------
2384
2385 /// Send a TRACK_STATUS and return the allocated request ID.
2386 pub async fn track_status(
2387 &mut self,
2388 track_namespace: TrackNamespace,
2389 track_name: Vec<u8>,
2390 parameters: Vec<KeyValuePair>,
2391 ) -> Result<VarInt, ConnectionError> {
2392 let (req_id, msg) = self.endpoint.track_status(track_namespace, track_name, parameters)?;
2393 self.send_control(&msg).await?;
2394 Ok(req_id)
2395 }
2396
2397 // -- Publish flow (publisher side) ------------------------------
2398
2399 /// Send a PUBLISH and return the allocated request ID.
2400 pub async fn publish(
2401 &mut self,
2402 track_namespace: TrackNamespace,
2403 track_name: Vec<u8>,
2404 track_alias: VarInt,
2405 track_extensions: Vec<KeyValuePair>,
2406 parameters: Vec<KeyValuePair>,
2407 ) -> Result<VarInt, ConnectionError> {
2408 let (req_id, msg) = self.endpoint.publish(
2409 track_namespace,
2410 track_name,
2411 track_alias,
2412 track_extensions,
2413 parameters,
2414 )?;
2415 self.send_control(&msg).await?;
2416 Ok(req_id)
2417 }
2418
2419 /// Send a PUBLISH_DONE for the given request ID.
2420 pub async fn publish_done(
2421 &mut self,
2422 request_id: VarInt,
2423 status_code: VarInt,
2424 stream_count: VarInt,
2425 reason_phrase: Vec<u8>,
2426 ) -> Result<(), ConnectionError> {
2427 let msg = self.endpoint.send_publish_done(
2428 request_id,
2429 status_code,
2430 stream_count,
2431 reason_phrase,
2432 )?;
2433 self.send_control(&msg).await
2434 }
2435
2436 // -- Data streams -----------------------------------------------
2437
2438 /// Open a new unidirectional stream for sending subgroup data.
2439 pub async fn open_subgroup_stream(
2440 &self,
2441 header: &AnySubgroupHeader,
2442 ) -> Result<FramedSendStream, ConnectionError> {
2443 let send = self.transport.open_uni().await?;
2444 let mut framed = FramedSendStream::new(send, self.draft);
2445 let sid = framed.stream_id();
2446 framed.write_subgroup_header(header).await?;
2447 self.emit(ClientEvent::StreamOpened {
2448 direction: Direction::Send,
2449 stream_kind: StreamKind::Subgroup,
2450 stream_id: sid,
2451 });
2452 self.emit(ClientEvent::DataStreamHeader {
2453 stream_id: sid,
2454 direction: Direction::Send,
2455 header: header.clone(),
2456 });
2457 Ok(framed)
2458 }
2459
2460 /// Open a new unidirectional stream for sending a FETCH's objects.
2461 ///
2462 /// The objects answering a FETCH do not go on the request's own stream:
2463 /// they go on a unidirectional stream of their own, which opens with a
2464 /// FETCH_HEADER naming the request they belong to. This writes that header
2465 /// and hands back the stream, the same way
2466 /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
2467 /// subgroup.
2468 ///
2469 /// The caller owns the stream that comes back. Nothing here remembers
2470 /// which request it belongs to, so an endpoint serving several fetches at
2471 /// once keeps its own map from Request ID to stream.
2472 pub async fn open_fetch_stream(
2473 &self,
2474 header: &AnyFetchHeader,
2475 ) -> Result<FramedSendStream, ConnectionError> {
2476 let send = self.transport.open_uni().await?;
2477 let mut framed = FramedSendStream::new(send, self.draft);
2478 let sid = framed.stream_id();
2479 framed.write_fetch_header(header).await?;
2480 self.emit(ClientEvent::StreamOpened {
2481 direction: Direction::Send,
2482 stream_kind: StreamKind::Fetch,
2483 stream_id: sid,
2484 });
2485 Ok(framed)
2486 }
2487
2488 /// Accept the next unidirectional stream and read its fetch header.
2489 ///
2490 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
2491 /// are separate because the header decides how every object after it is
2492 /// framed, so a caller has to know which it is expecting before the first
2493 /// byte is read.
2494 ///
2495 /// Objects come off the returned stream with
2496 /// [`FramedRecvStream::read_fetch_object`].
2497 pub async fn accept_fetch_stream(
2498 &self,
2499 ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
2500 let recv = self.transport.accept_uni().await?;
2501 let mut framed = FramedRecvStream::new(recv, self.draft);
2502 let sid = framed.stream_id();
2503 let header = framed.read_fetch_header().await?;
2504 self.emit(ClientEvent::StreamOpened {
2505 direction: Direction::Receive,
2506 stream_kind: StreamKind::Fetch,
2507 stream_id: sid,
2508 });
2509 self.emit(ClientEvent::FetchStreamHeader {
2510 stream_id: sid,
2511 direction: Direction::Receive,
2512 header: header.clone(),
2513 });
2514 // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
2515 // carries an `AnySubgroupHeader` and cannot express one. What
2516 // `accept_subgroup_stream` does beyond this - the forwarding-preference
2517 // note, the object measurement - is about a subgroup and has no
2518 // counterpart on a fetch stream.
2519 Ok((header, framed))
2520 }
2521
2522 /// Accept an incoming unidirectional data stream and read its subgroup
2523 /// header.
2524 pub async fn accept_subgroup_stream(
2525 &self,
2526 ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
2527 let recv = self.transport.accept_uni().await?;
2528 let mut framed = FramedRecvStream::new(recv, self.draft);
2529 let sid = framed.stream_id();
2530 let header = framed.read_subgroup_header().await?;
2531 self.emit(ClientEvent::StreamOpened {
2532 direction: Direction::Receive,
2533 stream_kind: StreamKind::Subgroup,
2534 stream_id: sid,
2535 });
2536 self.emit(ClientEvent::DataStreamHeader {
2537 stream_id: sid,
2538 direction: Direction::Receive,
2539 header: header.clone(),
2540 });
2541 // The track is resolved here and not inside the stream: it takes the
2542 // endpoint's alias table, which a stream handle has no way back to.
2543 // Handed over rather than offered, so measuring is not something a
2544 // caller has to remember to ask for.
2545 if let Some(objects) = self.endpoint.track_objects(header.track_alias()) {
2546 framed.measure_objects_against(objects, header.group_id());
2547 }
2548 Ok((header, framed))
2549 }
2550
2551 /// Send an object via datagram.
2552 ///
2553 /// The header goes through `AnyDatagramHeader::encode`, which refuses a
2554 /// header whose Object Status the framing it names cannot carry. Such a
2555 /// header errors here and nothing is sent, rather than going out as an
2556 /// ordinary payload datagram with the status quietly dropped.
2557 pub fn send_datagram(
2558 &self,
2559 header: &AnyDatagramHeader,
2560 payload: &[u8],
2561 ) -> Result<(), ConnectionError> {
2562 let mut buf = Vec::new();
2563 header.encode(&mut buf)?;
2564 buf.extend_from_slice(payload);
2565 self.emit(ClientEvent::DatagramReceived {
2566 direction: Direction::Send,
2567 header: header.clone(),
2568 payload_len: payload.len(),
2569 });
2570 self.transport.send_datagram(bytes::Bytes::from(buf))?;
2571 Ok(())
2572 }
2573
2574 /// Receive a datagram and decode it whole.
2575 ///
2576 /// An extension block announced with a length of 0 is returned as
2577 /// [`ConnectionError::EmptyDatagramExtensions`], which
2578 /// [`Connection::close_for_data_stream`] closes the session over. Extension
2579 /// headers on a status other than Normal are returned as
2580 /// [`ConnectionError::ExtensionsOnNonNormalStatus`], which it also closes over.
2581 ///
2582 /// The codec's `DatagramHeader::decode_object` then reads the whole datagram,
2583 /// and its refusals are returned as [`ConnectionError::Codec`]: an extension
2584 /// block that does not parse, a payload after a status, and a Prior Group ID
2585 /// Gap or Prior Object ID Gap fault. A gap fault,
2586 /// [`CodecError::MalformedTrack`], and an extension block that does not
2587 /// parse, [`CodecError::MalformedObjectProperties`] (draft-16 Section 11.2),
2588 /// make the track malformed, and this withdraws from it before returning,
2589 /// as draft-16 Section 2.4.2 requires: "When a subscriber detects a
2590 /// Malformed Track, it MUST UNSUBSCRIBE any subscription and FETCH_CANCEL
2591 /// any fetch for that Track from that publisher, and SHOULD deliver an
2592 /// error to the application." The session stays open.
2593 pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
2594 let data = self.transport.recv_datagram().await?;
2595 let mut cursor = &data[..];
2596 let header = match AnyDatagramHeader::decode(self.draft, &mut cursor) {
2597 Ok(header) => header,
2598 Err(CodecError::InvalidField) => {
2599 return Err(empty_datagram_extensions(&data)
2600 .unwrap_or(ConnectionError::Codec(CodecError::InvalidField)));
2601 }
2602 Err(err) => return Err(err.into()),
2603 };
2604 let consumed = data.len() - cursor.len();
2605 let payload = data.slice(consumed..);
2606 self.emit(ClientEvent::DatagramReceived {
2607 direction: Direction::Receive,
2608 header: header.clone(),
2609 payload_len: payload.len(),
2610 });
2611 // Refutable only in a build with more than one draft enabled.
2612 #[allow(irrefutable_let_patterns)]
2613 if let AnyDatagramHeader::Draft16(h) = &header {
2614 // draft-16 Section 10.2.1.2: "If an endpoint receives extension
2615 // headers on Objects with status that is not Normal, it MUST close
2616 // the session with a PROTOCOL_VIOLATION." The header decoder
2617 // reports it through `extensions_permitted` and does not refuse it.
2618 if !h.extensions_permitted() {
2619 return Err(ConnectionError::ExtensionsOnNonNormalStatus {
2620 object_id: h.object_id.into_inner(),
2621 extensions_len: h.extension_headers.len(),
2622 // Not permitted only beside a status that is present.
2623 status: h
2624 .object_status
2625 .unwrap_or(moqtap_codec::draft16::types::ObjectStatus::Normal),
2626 });
2627 }
2628 let whole =
2629 moqtap_codec::draft16::data_stream::DatagramHeader::decode_object(&mut &data[..]);
2630 if let Err(err) = whole {
2631 if let Some(condition) =
2632 malformed_track_condition(&ConnectionError::Codec(err.clone()))
2633 {
2634 self.withdraw_malformed_track(header.meta().track_alias, condition).await;
2635 }
2636 return Err(err.into());
2637 }
2638 }
2639 // A datagram is a whole object, so the connection can measure it
2640 // without help from the caller - and answer the condition itself,
2641 // because an UNSUBSCRIBE takes the connection an object on a stream
2642 // cannot reach.
2643 let meta = header.meta();
2644 if let Err(err) = self.endpoint.note_received_object(
2645 meta.track_alias,
2646 ObjectLocation { group: meta.group_id, object: meta.object_id },
2647 object_role(meta.status),
2648 ) {
2649 self.withdraw_malformed_track(
2650 meta.track_alias,
2651 MalformedTrackCondition::ObjectPastFinalObject,
2652 )
2653 .await;
2654 return Err(err.into());
2655 }
2656 Ok((header, payload))
2657 }
2658
2659 /// Close the session on the wire when the endpoint says a violation is
2660 /// fatal to it, and hand the error back unchanged.
2661 ///
2662 /// [`EndpointError::session_error_code`] answers `Some` for exactly the
2663 /// errors this draft ends the session over, and the endpoint has already
2664 /// moved its own state machine to Closed by the time this runs. Without
2665 /// this step that move is purely internal: the local endpoint refuses to
2666 /// start anything new while the peer, which is the one that broke the
2667 /// rule, sees a session that is still open and goes on sending. A rule
2668 /// that names a session termination code is a statement about the wire,
2669 /// so it takes a CONNECTION_CLOSE to satisfy it.
2670 ///
2671 /// The reason phrase is the error's own `Display` text, which names the
2672 /// rule rather than repeating the numeric code the close already carries.
2673 ///
2674 /// Errors that answer `None` are recoverable and nothing is sent.
2675 fn close_for(&self, err: &EndpointError) {
2676 if let Some(code) = err.session_error_code() {
2677 // QUIC application error codes are 62-bit; every code in this
2678 // registry is far below `u32::MAX`, and saturating rather than
2679 // truncating means a future code that is not could never be
2680 // reported as a different, assigned one.
2681 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2682 self.close(wire_code, err.to_string().as_bytes());
2683 }
2684 }
2685
2686 /// [`close_for`](Self::close_for), then the error unchanged, for the
2687 /// common case where the endpoint's error is also what the caller returns.
2688 fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
2689 self.close_for(&err);
2690 ConnectionError::Endpoint(err)
2691 }
2692
2693 /// Send the messages Section 2.4.2 asks for when a track is found
2694 /// Send the messages Section 2.4.2 asks for when a track is found
2695 /// malformed, and stop at the first one the control stream refuses.
2696 ///
2697 /// "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE any
2698 /// subscription and FETCH_CANCEL any fetch for that Track from that
2699 /// publisher" - one message per request, in Request ID order, and the
2700 /// endpoint decides which message each request takes.
2701 ///
2702 /// A write that fails is not reported. The caller is on its way to
2703 /// returning an error that says what went wrong with the track, and a
2704 /// control stream that will not take an UNSUBSCRIBE is a session on its way
2705 /// out for a reason of its own; replacing the condition's report with a
2706 /// transport error would lose the only account of why the track was
2707 /// withdrawn. The rest of the withdrawal is abandoned, because a stream
2708 /// that refused one message will refuse the next.
2709 async fn withdraw_malformed_track(&self, alias: u64, condition: MalformedTrackCondition) {
2710 for msg in self.endpoint.withdraw_malformed_track(alias, condition) {
2711 if self.send_control(&msg).await.is_err() {
2712 break;
2713 }
2714 }
2715 }
2716
2717 /// [`Self::withdraw_malformed_track`] for the track the FETCH `request_id`
2718 /// asked for.
2719 async fn withdraw_malformed_fetch_track(
2720 &self,
2721 request_id: u64,
2722 condition: MalformedTrackCondition,
2723 ) {
2724 for msg in self.endpoint.withdraw_malformed_fetch_track(request_id, condition) {
2725 if self.send_control(&msg).await.is_err() {
2726 break;
2727 }
2728 }
2729 }
2730
2731 /// Withdraw from a track a data stream found malformed, reporting whether
2732 /// it did.
2733 ///
2734 /// The Malformed Track twin of [`Connection::close_for_data_stream`], and
2735 /// separate from it for the same reason and one more. The same one: a
2736 /// [`FramedRecvStream`] holds no connection, so the reader that finds the
2737 /// fault is not the object that can send an UNSUBSCRIBE. The one more: the
2738 /// two answers are opposites - that call ends the session, this one gives
2739 /// up a track and leaves it running - and a single entry point would have
2740 /// to decide between them from the error alone, which is exactly the
2741 /// decision a caller reproducing a capture wants to make itself.
2742 ///
2743 /// The datagram path needs none of this. It is read through the connection,
2744 /// so [`Connection::recv_datagram`] answers the condition where it finds
2745 /// it, and this is only for the objects that arrive on a stream the caller
2746 /// holds.
2747 pub async fn withdraw_for_data_stream(&self, err: &ConnectionError) -> bool {
2748 let ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject { alias, .. }) = err
2749 else {
2750 return false;
2751 };
2752 self.withdraw_malformed_track(*alias, MalformedTrackCondition::ObjectPastFinalObject).await;
2753 true
2754 }
2755
2756 /// [`Connection::withdraw_for_data_stream`] for an error read from the
2757 /// subgroup stream `header` opened, which also withdraws from the track
2758 /// over the [`CodecError::MalformedTrack`] that
2759 /// [`FramedRecvStream::read_subgroup_object`] returns for a Prior Group ID
2760 /// Gap or Prior Object ID Gap fault, and over the
2761 /// [`CodecError::MalformedObjectProperties`] it returns for an extension
2762 /// block that does not parse (draft-16 Section 11.2). Those errors name no
2763 /// track, so the header supplies its alias.
2764 ///
2765 /// draft-16 Section 2.4.2: "When a subscriber detects a Malformed Track, it
2766 /// MUST UNSUBSCRIBE any subscription and FETCH_CANCEL any fetch for that
2767 /// Track from that publisher, and SHOULD deliver an error to the
2768 /// application."
2769 pub async fn withdraw_for_subgroup_stream(
2770 &self,
2771 header: &AnySubgroupHeader,
2772 err: &ConnectionError,
2773 ) -> bool {
2774 let Some(condition) = malformed_track_condition(err) else {
2775 return self.withdraw_for_data_stream(err).await;
2776 };
2777 self.withdraw_malformed_track(header.track_alias(), condition).await;
2778 true
2779 }
2780
2781 /// [`Connection::withdraw_for_data_stream`] for an error read from the fetch
2782 /// stream `header` opened, which also withdraws from the track the FETCH
2783 /// asked for over the [`CodecError::MalformedTrack`] that the codec's
2784 /// `FetchObjectReader::resolve` returns for a Prior Group ID Gap or Prior
2785 /// Object ID Gap fault, and over the
2786 /// [`CodecError::MalformedObjectProperties`] it returns for an extension
2787 /// block that does not parse (draft-16 Section 11.2). Those errors name no
2788 /// track, so the header's Request ID supplies it.
2789 ///
2790 /// This draft's [`FramedRecvStream::read_fetch_object`] does not resolve an
2791 /// Object's Location, so the caller passes the error `resolve` returned as
2792 /// [`ConnectionError::Codec`].
2793 ///
2794 /// draft-16 Section 2.4.2: "When a subscriber detects a Malformed Track, it
2795 /// MUST UNSUBSCRIBE any subscription and FETCH_CANCEL any fetch for that
2796 /// Track from that publisher, and SHOULD deliver an error to the
2797 /// application."
2798 pub async fn withdraw_for_fetch_stream(
2799 &self,
2800 header: &AnyFetchHeader,
2801 err: &ConnectionError,
2802 ) -> bool {
2803 let Some(condition) = malformed_track_condition(err) else {
2804 return self.withdraw_for_data_stream(err).await;
2805 };
2806 self.withdraw_malformed_fetch_track(header.request_id(), condition).await;
2807 true
2808 }
2809
2810 /// Close the session when a failure raised while reading a *data* stream is
2811 /// one draft-16 answers with a close. Reports whether it closed.
2812 ///
2813 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream) hands the caller
2814 /// a [`FramedRecvStream`], which holds no connection and so cannot close
2815 /// one, and the read that raises this failure happens there. The caller is
2816 /// the only party holding both halves, which is what this is for.
2817 ///
2818 /// Splitting it this way rather than closing inside the reader keeps a
2819 /// caller that is deliberately permissive — a tool reproducing a capture,
2820 /// say — able to read a violating stream and report it without tearing the
2821 /// session down. The rule is stated at endpoints, and this is where an
2822 /// endpoint decides it is one.
2823 ///
2824 /// Answers the extension-header rule of Section 10.2.1.2, and any decode
2825 /// failure `codec_session_error_code` recognises, so a rule is answered
2826 /// with one code whichever stream carried it.
2827 pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
2828 use crate::above_codec_rules::DraftSpecificCause;
2829
2830 match err {
2831 ConnectionError::Codec(inner) => {
2832 let Some(code) = Self::codec_session_error_code(inner) else { return false };
2833 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2834 self.close(wire_code, inner.to_string().as_bytes());
2835 true
2836 }
2837 // Not a `Codec` failure: the codec refuses such an Object as a bare
2838 // `InvalidField`, which `read_subgroup_object` narrows to this
2839 // crate's own variant, so the mapping table above never sees it.
2840 //
2841 // The code comes from `draft_specific_cause` rather than from a
2842 // constant here, so this draft's reading of its own sentence is
2843 // written down once and a caller who reads the error as a value
2844 // sees the same code the peer was sent.
2845 ConnectionError::ExtensionsOnNonNormalStatus { .. } => {
2846 let Some(DraftSpecificCause::PeerViolation { close: Some(code), .. }) =
2847 Self::draft_specific_cause(err)
2848 else {
2849 return false;
2850 };
2851 // Saturate rather than truncate, so a future code above
2852 // `u32::MAX` is never reported as a different assigned one.
2853 self.close(u32::try_from(code).unwrap_or(u32::MAX), err.to_string().as_bytes());
2854 true
2855 }
2856 // Not a `Codec` failure: the codec refuses the datagram as a bare
2857 // `InvalidField`, which `recv_datagram` names, so the code comes
2858 // from `draft_specific_cause`.
2859 ConnectionError::EmptyDatagramExtensions { .. } => {
2860 let Some(crate::above_codec_rules::DraftSpecificCause::Codec {
2861 close: Some(code),
2862 ..
2863 }) = Self::draft_specific_cause(err)
2864 else {
2865 return false;
2866 };
2867 self.close(u32::try_from(code).unwrap_or(u32::MAX), err.to_string().as_bytes());
2868 true
2869 }
2870 _ => false,
2871 }
2872 }
2873
2874 // -- Accessors --------------------------------------------------
2875
2876 /// Access the underlying endpoint state machine.
2877 pub fn endpoint(&self) -> &Endpoint {
2878 &self.endpoint
2879 }
2880
2881 /// Mutable access to the endpoint state machine.
2882 pub fn endpoint_mut(&mut self) -> &mut Endpoint {
2883 &mut self.endpoint
2884 }
2885
2886 /// The SETUP message the server answered the handshake with.
2887 ///
2888 /// `SERVER_SETUP` through draft-16, the server's half of the unified
2889 /// `SETUP` from draft-17. [`AnyControlMessage::fields`] renders it under
2890 /// this draft's own parameter names, in the order they arrived.
2891 pub fn server_setup(&self) -> &AnyControlMessage {
2892 &self.server_setup
2893 }
2894
2895 /// The framed wire bytes of [`Self::server_setup`], as they arrived.
2896 ///
2897 /// Kept beside the decoded form because the encoding is evidence the
2898 /// decoding discards: two relays sending the same parameter can still
2899 /// disagree on how wide a varint they wrote it in.
2900 pub fn server_setup_raw(&self) -> Option<&[u8]> {
2901 self.server_setup_raw.as_deref()
2902 }
2903
2904 /// Returns the draft version this connection is using.
2905 pub fn draft(&self) -> DraftVersion {
2906 self.draft
2907 }
2908
2909 /// Which of this draft's *own* `ConnectionError` variants this error is,
2910 /// and which kind of thing it says.
2911 ///
2912 /// The ten every draft carries answer `None` here: [`AnyConnectionError`]
2913 /// classifies those itself, once, and never asks a draft about them. What
2914 /// is left splits two ways, and the split is the reason this function
2915 /// exists — before it, both halves reached a caller as a sentence and read
2916 /// exactly alike. A [`LocalRefusal`] is this endpoint declining to write
2917 /// something, so nothing reached the wire and no relay is implicated; a
2918 /// [`PeerViolation`] is a peer having done something draft-16 forbids, and
2919 /// carries the session error code draft-16's own text answers it with.
2920 ///
2921 /// Matched exhaustively, with no wildcard arm and deliberately so: a
2922 /// variant added to this draft's error type has to arrive here as a compile
2923 /// error, beside the doc comment quoting the sentence it enforces, rather
2924 /// than as a silent [`ErrorCause::Unclassified`] in the facade.
2925 ///
2926 /// [`AnyConnectionError`]: crate::dispatch::AnyConnectionError
2927 /// [`ErrorCause::Unclassified`]: crate::dispatch::ErrorCause::Unclassified
2928 /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
2929 /// [`PeerViolation`]: crate::above_codec_rules::DraftSpecificCause::PeerViolation
2930 pub fn draft_specific_cause(
2931 err: &ConnectionError,
2932 ) -> Option<crate::above_codec_rules::DraftSpecificCause> {
2933 use crate::above_codec_rules::{AboveCodecRule, DraftSpecificCause};
2934 use moqtap_codec::draft16::error_codes::SessionErrorCode;
2935
2936 match err {
2937 ConnectionError::Endpoint(_)
2938 | ConnectionError::Codec(_)
2939 | ConnectionError::Transport(_)
2940 | ConnectionError::VarInt(_)
2941 | ConnectionError::NoControlStream
2942 | ConnectionError::UnexpectedEnd
2943 | ConnectionError::StreamFinished
2944 | ConnectionError::InvalidAddress(_)
2945 | ConnectionError::TlsConfig(_)
2946 | ConnectionError::DataStreamState(_) => None,
2947 // This build decoding a message and then failing to narrow it to
2948 // its own draft. Nothing reached the wire and no peer is
2949 // implicated, which is the whole reason it is not
2950 // `ConnectionError::Codec`: under that name it would carry
2951 // `Some(PROTOCOL_VIOLATION)` out of `codec_session_error_code` and
2952 // publish a relay for this build's defect. See the variant's own
2953 // doc.
2954 ConnectionError::ControlMessageNarrowing => {
2955 Some(crate::above_codec_rules::DraftSpecificCause::LocalRefusal)
2956 }
2957 // Section 3.3: a bidirectional stream may begin with CLIENT_SETUP
2958 // or SUBSCRIBE_NAMESPACE and nothing else, "unless negotiated. If
2959 // they do, the peer MUST close the Session with a Protocol
2960 // Violation." The session has already been closed on the wire by
2961 // the time this is returned, so the code is carried here for a
2962 // caller to read which rule was answered, not for it to answer one
2963 // again.
2964 ConnectionError::NonSubscribeNamespaceOnBidiStream(_) => {
2965 Some(DraftSpecificCause::PeerViolation {
2966 rule: AboveCodecRule::BidiStreamOpener,
2967 close: Some(SessionErrorCode::ProtocolViolation.as_u64()),
2968 })
2969 }
2970 // A `respond_*` helper pointed at a namespace subscription this
2971 // endpoint opened. Nothing was written and no state moved: the
2972 // answer to a SUBSCRIBE_NAMESPACE is owed by whoever received it.
2973 ConnectionError::NotOursToAnswer(_) => Some(DraftSpecificCause::LocalRefusal),
2974 // Section 10.2.1.2 states the rule and names the code in the same
2975 // sentence. The codec refuses such an Object only as a bare
2976 // `InvalidField`, so this layer is the one that names the rule,
2977 // and `close_for_data_stream` performs the close by
2978 // reading this same answer.
2979 ConnectionError::ExtensionsOnNonNormalStatus { .. } => {
2980 Some(DraftSpecificCause::PeerViolation {
2981 rule: AboveCodecRule::PropertiesOnNonNormalStatus,
2982 close: Some(SessionErrorCode::ProtocolViolation.as_u64()),
2983 })
2984 }
2985 // The codec refuses the datagram as a bare `InvalidField`, and
2986 // `recv_datagram` names the rule, so `close_for_data_stream` reads
2987 // the close here. See the variant's own doc.
2988 ConnectionError::EmptyDatagramExtensions { .. } => {
2989 Some(crate::above_codec_rules::DraftSpecificCause::Codec {
2990 error: CodecError::InvalidField,
2991 close: Some(
2992 moqtap_codec::draft16::error_codes::SessionErrorCode::ProtocolViolation
2993 .as_u64(),
2994 ),
2995 })
2996 }
2997 }
2998 }
2999
3000 /// The code to close the session with when a control message could not be
3001 /// decoded because the peer broke a rule draft-16 answers with a close.
3002 ///
3003 /// Every variant listed here comes from a sentence in this draft that names
3004 /// the consequence, and the list is deliberately shorter than draft-17's:
3005 /// the bounds are per draft, and answering one this draft does not state
3006 /// would close a session over traffic a conforming peer may send.
3007 ///
3008 /// - Reason Phrase, maximum 1024 bytes: "If an endpoint receives a length
3009 /// exceeding the maximum, it MUST close the session with a
3010 /// PROTOCOL_VIOLATION."
3011 /// - KVP value, maximum 2^16-1 bytes, with the same sentence.
3012 /// - Track Namespace field count: "If an endpoint receives a Track
3013 /// Namespace consisting of 0 or greater than 32 Track Namespace Fields,
3014 /// it MUST close the session with a PROTOCOL_VIOLATION." Note the lower
3015 /// bound — an empty tuple is refused here, where drafts 17 and later
3016 /// permit it.
3017 /// - Full Track Name, maximum 4,096 bytes. This draft widened the rule from
3018 /// draft-15's: "If an endpoint receives a Track Namespace or a Full
3019 /// Track Name exceeding 4,096 bytes".
3020 /// - Duplicate parameters, a SHOULD rather than a MUST: "Receivers SHOULD
3021 /// check that there are no unexpected duplicate parameters and close the
3022 /// session as a PROTOCOL_VIOLATION"
3023 /// - A Track Namespace Field of length zero, which draft-15 does not
3024 /// state: "Each Track Namespace Field Value MUST contain at least one
3025 /// byte."
3026 /// - The delta-encoded parameter type overflow, which arrives with this
3027 /// draft along with delta encoding itself.
3028 ///
3029 /// - GOAWAY New Session URI, maximum 8,192 bytes: "If an endpoint
3030 /// receives a length exceeding the maximum, it MUST close the session
3031 /// with a PROTOCOL_VIOLATION." Every draft from 11 to 19 states it; 07
3032 /// through 10 state no maximum for the field at all.
3033 /// - Unknown control message type: "An endpoint that receives an unknown
3034 /// message type MUST close the session." All the drafts state it,
3035 /// in the same words, and the sentence names no code, so Protocol
3036 /// Violation is what carries it.
3037 ///
3038 /// `None` for everything else, including [`CodecError::InvalidField`]. That
3039 /// variant is shared by a dozen unrelated malformations, only some of which
3040 /// the draft answers with a close, so treating it as fatal would close
3041 /// sessions the draft does not ask to be closed. Splitting it is the way to
3042 /// bring the rest of those rules under this function; widening the match is
3043 /// not.
3044 pub fn codec_session_error_code(
3045 err: &CodecError,
3046 ) -> Option<moqtap_codec::draft16::error_codes::SessionErrorCode> {
3047 use moqtap_codec::draft16::error_codes::SessionErrorCode;
3048 use moqtap_codec::kvp::KvpError;
3049 match err {
3050 // The declared Length disagreeing with the fields, which every
3051 // draft answers with a close. Drafts 07 through 10 name no code for
3052 // it, so it takes the one their other unnamed rules take.
3053 // A Filter Type outside the four this draft assigns, Section 5.1.2:
3054 // "An endpoint that receives a filter type other than the above MUST
3055 // close the session with PROTOCOL_VIOLATION."
3056 //
3057 // Drafts 07 through 14 carried the Filter Type as a field of
3058 // SUBSCRIBE. From draft-15 it is the first field inside the
3059 // length-prefixed filter parameter, where a codec that carries the
3060 // value as opaque bytes never reads it — the rule did not change and
3061 // the place it has to be enforced did.
3062 CodecError::InvalidFilterType(_) => Some(SessionErrorCode::ProtocolViolation),
3063 // A filter parameter whose value is not a filter, Section 9.2.2.5:
3064 // "It is a length-prefixed Subscription Filter... If the length of
3065 // the Subscription Filter does not match the parameter length, the
3066 // publisher MUST close the session with PROTOCOL_VIOLATION."
3067 //
3068 // The one key-value malformation this draft answers with something
3069 // other than KEY_VALUE_FORMATTING_ERROR. The general rule covers the
3070 // same bytes and names that code; the sentence above is the specific
3071 // one, so it governs. Drafts 17 and later drop it and leave only the
3072 // general rule, which is why the same malformation ends a session
3073 // there under a different code.
3074 CodecError::SubscriptionFilterMalformed { .. } => {
3075 Some(SessionErrorCode::ProtocolViolation)
3076 }
3077 // A Fetch Type outside the three this draft assigns: "An endpoint
3078 // that receives a Fetch Type other than 0x1, 0x2 or 0x3 MUST close
3079 // the session with a PROTOCOL_VIOLATION." The value decides which
3080 // fields follow it — a Standalone fetch carries a track name and a
3081 // range where a joining fetch carries a Request ID and an offset —
3082 // so a reader that cannot name the type cannot find the end of the
3083 // message.
3084 CodecError::InvalidFetchType(_) => Some(SessionErrorCode::ProtocolViolation),
3085 CodecError::ControlMessageLengthMismatch { .. } => {
3086 Some(SessionErrorCode::ProtocolViolation)
3087 }
3088 CodecError::KeyDeltaOverflow(..)
3089 | CodecError::DuplicateParameter(_)
3090 | CodecError::TrackNameTooLong
3091 | CodecError::InvalidNamespaceTupleSize(_)
3092 | CodecError::ReasonPhraseTooLong
3093 | CodecError::GoAwayUriTooLong
3094 | CodecError::UnknownMessageType(_)
3095 | CodecError::Kvp(KvpError::ValueTooLong(_))
3096 | CodecError::EmptyNamespaceField => Some(SessionErrorCode::ProtocolViolation),
3097 // An unknown data-plane type, Section 10: "An endpoint that
3098 // receives an unknown stream or datagram type MUST close the
3099 // session." One sentence covering two tables, which is why both
3100 // variants sit here.
3101 // A Message Parameter whose value is outside the range its type
3102 // allows: DELIVERY_TIMEOUT in Section 9.2.2.2, FORWARD in Section
3103 // 9.2.2.8, GROUP_ORDER in Section 9.2.2.4 and SUBSCRIBER_PRIORITY in
3104 // Section 9.2.2.3.
3105 // Each states that a receiver "MUST close the session with
3106 // PROTOCOL_VIOLATION".
3107 CodecError::ParameterValueOutOfRange { .. } => {
3108 Some(SessionErrorCode::ProtocolViolation)
3109 }
3110 // A Track Extension or Track Property whose value is outside the
3111 // range its type allows: DELIVERY_TIMEOUT in Section 11.1,
3112 // DEFAULT_PUBLISHER_GROUP_ORDER in Section 11.1.1.2 and DYNAMIC_GROUPS in
3113 // Section 11.1.1.3.
3114 // Each states that a receiver "MUST close the session with
3115 // PROTOCOL_VIOLATION".
3116 //
3117 // A separate arm from the parameter rule above because the two
3118 // registries are separate: 0x22 is GROUP_ORDER as a parameter and
3119 // DEFAULT_PUBLISHER_GROUP_ORDER as a Track Extension, and a log that
3120 // named only the number would not say which.
3121 CodecError::TrackPropertyValueOutOfRange { .. } => {
3122 Some(SessionErrorCode::ProtocolViolation)
3123 }
3124 CodecError::UnknownStreamType(_) | CodecError::UnknownDatagramType(_) => {
3125 Some(SessionErrorCode::ProtocolViolation)
3126 }
3127 // A Type inside a form this draft defines but on a list it names as
3128 // invalid: Section 10.4.2 for a subgroup header whose SUBGROUP_ID_MODE
3129 // is the reserved 0b11, Section 10.3.1 for a datagram asking to be both
3130 // an object status and an end-of-group marker. Unlike the rule above,
3131 // these two name their code outright.
3132 CodecError::InvalidStreamTypeValue { .. }
3133 | CodecError::InvalidDatagramTypeValue { .. } => {
3134 Some(SessionErrorCode::ProtocolViolation)
3135 }
3136 // A key-value pair whose value is not the serialization its own
3137 // Type defines, Section 1.4.2: "If a receiver understands a Type,
3138 // and the following Value or Length/Value does not match the
3139 // serialization defined by that Type, the receiver MUST close the
3140 // session with error code KEY_VALUE_FORMATTING_ERROR."
3141 //
3142 // Section 9.2.2.1 states the same answer for the one structure this
3143 // draft spells out: "If the Token structure cannot be decoded, the
3144 // receiver MUST close the Session with KEY_VALUE_FORMATTING_ERROR."
3145 //
3146 // The one rule in this table that names a code other than Protocol
3147 // Violation.
3148 CodecError::KeyValueFormatting { .. } => {
3149 Some(SessionErrorCode::KeyValueFormattingError)
3150 }
3151 // A Message Parameter whose type this draft does not define, Section
3152 // 9.2: "All Message Parameters MUST be defined in the negotiated
3153 // version of MOQT or negotiated via Setup Parameters. An endpoint that
3154 // receives an unknown Message Parameter MUST close the session with
3155 // PROTOCOL_VIOLATION."
3156 //
3157 // One namespace only. This draft also says a receiver ignores an
3158 // unrecognised Setup Parameter, so an unknown type in a SETUP is carried and
3159 // the codec never raises this for one.
3160 CodecError::UnknownMessageParameter(_) => Some(SessionErrorCode::ProtocolViolation),
3161 // Everything this draft does not answer, named rather than swept up
3162 // by a wildcard. The arm is exhaustive deliberately: a new
3163 // `CodecError` variant will not compile until it has been placed on
3164 // one side or the other, on this draft, which is the decision a `_`
3165 // arm makes silently and invisibly in every draft module at once.
3166 //
3167 // Adding one variant to `CodecError` produces an `E0004` in every
3168 // draft module that matches it exhaustively, each naming the
3169 // variant that has nowhere to go. That is the whole mechanism.
3170 //
3171 // The nesting stops at `VarInt`, whose variants report how the bytes
3172 // ran out rather than a rule an endpoint states, so there is nothing
3173 // in it for a draft to answer. `Kvp` is spelled out because it does
3174 // carry one.
3175 // Neither field exists from draft-15 on. Forwarding became the
3176 // FORWARD parameter, which carries the same rule in a different
3177 // shape and is answered above under its own variant; Content Exists
3178 // became the presence or absence of a LARGEST_OBJECT parameter.
3179 CodecError::InvalidForward(_)
3180 | CodecError::InvalidContentExists(_)
3181 | CodecError::UnexpectedEnd
3182 | CodecError::MessageTooLong(_)
3183 | CodecError::VarInt(_)
3184 | CodecError::InvalidField
3185 | CodecError::InvalidRange(..)
3186 | CodecError::ParameterLengthMismatch(_)
3187 | CodecError::EndOfTrackObjectId(_)
3188 | CodecError::ParametersOutOfOrder(..)
3189 | CodecError::ObjectIdOverflow(..)
3190 | CodecError::ExtensionsOnNonExistentObject(_)
3191 | CodecError::InvalidRequiredRequestIdDelta(..)
3192 // Not `ParameterOutOfScope`, even though this draft is the one that
3193 // starts closing over an unknown Message Parameter above. The scope
3194 // rule is a separate sentence and it keeps the older answer. Section
3195 // 9.2.2: "Each message parameter definition indicates the message types
3196 // in which it can appear. If it appears in some other type of message,
3197 // it MUST be ignored." The two halves part company at draft-17, which
3198 // is where the second sentence becomes a close, so this draft carries an
3199 // out-of-scope parameter and the codec never raises the variant here.
3200 | CodecError::ParameterOutOfScope { .. }
3201 // The End Group is written out in full on this draft, so there is
3202 // nothing to add and nothing to overflow. Drafts 17 and later
3203 // replaced it with a delta measured from the Start Location's Group,
3204 // and 18 and 19 close the session when the sum leaves the range.
3205 | CodecError::FilterEndGroupOverflow { .. }
3206 // The object payload rule, Section 10.2.1.1: "Any object with a status
3207 // code other than zero MUST have an empty payload." A MUST on the
3208 // sender with no receiver action named anywhere — the "SHOULD be
3209 // treated as a protocol error" in the same paragraph belongs to the
3210 // sentence before it, which is about a status value this draft does
3211 // not assign — so an object carrying a payload it may not is refused
3212 // and the session stays open.
3213 | CodecError::PayloadNotPermitted { .. }
3214 // A property block that does not parse makes the Track malformed,
3215 // Section 11.2.
3216 | CodecError::MalformedObjectProperties { .. }
3217 // A Malformed Track (Section 2.4.2) costs the track and not the
3218 // session.
3219 | CodecError::MalformedTrack { .. }
3220 // Raised only by the draft-23 codec.
3221 | CodecError::ControlMessageExceedsLimit { .. }
3222 | CodecError::MissingErrorDetail { .. }
3223 | CodecError::MalformedErrorDetail { .. }
3224 | CodecError::TokenAliasInSetup { .. }
3225 | CodecError::EmptyTrackName
3226 | CodecError::MalformedAuthToken { .. }
3227 | CodecError::TrackPropertiesNotPermitted
3228 | CodecError::DeltaDecodingOverflow { .. }
3229 | CodecError::MalformedObject { .. }
3230 | CodecError::UnknownObjectStatus(_)
3231 | CodecError::UnsupportedDraft(_)
3232 | CodecError::Kvp(
3233 KvpError::MissingLength | KvpError::UnexpectedEnd | KvpError::VarInt(_),
3234 ) => None,
3235 }
3236 }
3237
3238 /// Close the session on the wire when a decode failure is one draft-16
3239 /// answers with a close, and hand the error back unchanged.
3240 /// Without it every bound the decoder enforces would stop at *this endpoint
3241 /// refused the frame* while the peer, which is the one that broke the rule,
3242 /// saw a session that was still open and went on sending. "MUST close the
3243 /// session with a PROTOCOL_VIOLATION" is a statement about the wire.
3244 fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
3245 if let ConnectionError::Codec(inner) = &err {
3246 if let Some(code) = Self::codec_session_error_code(inner) {
3247 // QUIC application error codes are 62-bit; every code in this
3248 // registry is far below `u32::MAX`, and saturating rather than
3249 // truncating means a future code that is not could never be
3250 // reported as a different, assigned one.
3251 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
3252 self.close(wire_code, inner.to_string().as_bytes());
3253 }
3254 }
3255 err
3256 }
3257
3258 /// Close the connection.
3259 pub fn close(&self, code: u32, reason: &[u8]) {
3260 self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
3261 self.transport.close(code, reason);
3262 }
3263}
3264
3265/// The Malformed Track condition a codec refusal on a data path is, on this
3266/// draft, or `None` for a refusal that is not one.
3267fn malformed_track_condition(err: &ConnectionError) -> Option<MalformedTrackCondition> {
3268 match err {
3269 ConnectionError::Codec(CodecError::MalformedTrack { .. }) => {
3270 Some(MalformedTrackCondition::PriorGap)
3271 }
3272 // draft-16 Section 11.2: "A Key-Value-Pair cannot be parsed."
3273 ConnectionError::Codec(CodecError::MalformedObjectProperties { .. }) => {
3274 Some(MalformedTrackCondition::MalformedPropertyBlock)
3275 }
3276 _ => None,
3277 }
3278}
3279
3280/// Determine the encoded length of a varint from its first byte.
3281fn varint_len(first_byte: u8) -> usize {
3282 1 << (first_byte >> 6)
3283}
3284
3285#[cfg(test)]
3286mod tests {
3287 use super::*;
3288
3289 /// Only a refusal of what the peer sent is reported as a refused frame,
3290 /// and only with the bytes it was about.
3291 #[test]
3292 fn only_a_refusal_of_the_peers_frame_is_reported() {
3293 let raw = Some(vec![0x01]);
3294 let decoder = ConnectionError::Codec(CodecError::UnexpectedEnd);
3295 assert!(Connection::refused_event(&decoder, None, raw.clone()).is_some());
3296 assert!(Connection::refused_event(&decoder, None, None).is_none());
3297 let local = ConnectionError::Endpoint(EndpointError::NotActive);
3298 assert!(Connection::refused_event(&local, None, raw).is_none());
3299 }
3300
3301 /// This build failing to narrow a message it decoded is never a finding
3302 /// about the peer.
3303 ///
3304 /// The arm that raises `ControlMessageNarrowing` is unreachable — this
3305 /// draft's decoder can only hand back this draft's variant — and nothing
3306 /// pins that. What is pinned here is the half that matters.
3307 /// `CodecError::UnknownMessageType(0)` is what the arm must not raise:
3308 /// `codec_session_error_code` answers it `Some(PROTOCOL_VIOLATION)` on
3309 /// every draft in range, so the day the narrowing failed a conformance
3310 /// probe would publish a relay for sending a control message type this
3311 /// draft does not assign — with `0x00` attached as the codepoint that
3312 /// proved it, which is an accusation better evidenced than any real one
3313 /// this build makes. The section stating that rule is numbered differently
3314 /// on every draft, and the point does not turn on the number.
3315 #[test]
3316 fn a_message_this_build_could_not_narrow_names_nobody() {
3317 use crate::dispatch::{AnyConnectionError, ErrorCause};
3318
3319 let err: AnyConnectionError = ConnectionError::ControlMessageNarrowing.into();
3320 assert!(err.is_local(), "a narrowing this build could not do is this build's");
3321 assert_eq!(
3322 err.cause(),
3323 &ErrorCause::Facade,
3324 "nothing reached the wire, so there is no rule and no close code to read"
3325 );
3326 }
3327
3328 #[test]
3329 fn varint_len_single_byte() {
3330 assert_eq!(varint_len(0x00), 1);
3331 assert_eq!(varint_len(0x3F), 1);
3332 }
3333
3334 #[test]
3335 fn varint_len_two_bytes() {
3336 assert_eq!(varint_len(0x40), 2);
3337 assert_eq!(varint_len(0x7F), 2);
3338 }
3339
3340 #[test]
3341 fn varint_len_four_bytes() {
3342 assert_eq!(varint_len(0x80), 4);
3343 assert_eq!(varint_len(0xBF), 4);
3344 }
3345
3346 #[test]
3347 fn varint_len_eight_bytes() {
3348 assert_eq!(varint_len(0xC0), 8);
3349 assert_eq!(varint_len(0xFF), 8);
3350 }
3351
3352 #[test]
3353 fn client_config_alpn_quic_draft16() {
3354 let config = ClientConfig {
3355 draft: DraftVersion::Draft16,
3356 transport: TransportType::Quic,
3357 skip_cert_verification: false,
3358 ca_certs: Vec::new(),
3359 setup_parameters: Vec::new(),
3360 };
3361 assert_eq!(config.alpn(), vec![b"moqt-16".to_vec()]);
3362 }
3363
3364 #[test]
3365 fn client_config_alpn_webtransport() {
3366 let config = ClientConfig {
3367 draft: DraftVersion::Draft16,
3368 transport: TransportType::WebTransport { url: "https://example.com".to_string() },
3369 skip_cert_verification: false,
3370 ca_certs: Vec::new(),
3371 setup_parameters: Vec::new(),
3372 };
3373 assert_eq!(config.alpn(), vec![b"h3".to_vec()]);
3374 }
3375
3376 /// `MOQT_ALPN` is the ALPN a client configured for this draft offers.
3377 #[test]
3378 fn moqt_alpn_is_the_one_a_client_offers() {
3379 // Checked against what a client configured for this draft puts on the
3380 // wire, and only then against the literal.
3381 let config = ClientConfig {
3382 draft: DraftVersion::Draft16,
3383 transport: TransportType::Quic,
3384 skip_cert_verification: false,
3385 ca_certs: Vec::new(),
3386 setup_parameters: Vec::new(),
3387 };
3388 assert_eq!(
3389 config.alpn(),
3390 vec![MOQT_ALPN.to_vec()],
3391 "MOQT_ALPN is {:?}; a draft-{} client offers {:?}",
3392 String::from_utf8_lossy(MOQT_ALPN),
3393 16,
3394 config
3395 .alpn()
3396 .iter()
3397 .map(|a| String::from_utf8_lossy(a).into_owned())
3398 .collect::<Vec<_>>(),
3399 );
3400 assert_eq!(MOQT_ALPN, b"moqt-16");
3401 }
3402
3403 #[test]
3404 fn transport_type_debug() {
3405 let quic = TransportType::Quic;
3406 assert!(format!("{quic:?}").contains("Quic"));
3407
3408 let wt = TransportType::WebTransport { url: "https://example.com".to_string() };
3409 assert!(format!("{wt:?}").contains("WebTransport"));
3410 }
3411}