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