Skip to main content

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