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