Skip to main content

moqtap_client/draft11/
connection.rs

1use bytes::{Buf, Bytes, BytesMut};
2
3use crate::draft11::endpoint::{Endpoint, EndpointError};
4use crate::draft11::event::{ClientEvent, Direction, FetchObject, StreamKind, SubgroupObject};
5use crate::draft11::observer::ConnectionObserver;
6use crate::draft11::session::request_id::Role;
7use crate::draft11::session::setup;
8use crate::forwarding_preference::ObjectForwardingPreference;
9use crate::track_locations::{EndOfTrackForm, ObjectLocation, ObjectRole, TrackObjects};
10use crate::transport::{RecvStream, SendStream, Transport, TransportError};
11use moqtap_codec::dispatch::{
12    AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
13};
14use moqtap_codec::draft11::data_stream::{FetchObjectHeader, ObjectHeader, SubgroupGaps};
15use moqtap_codec::draft11::message::ControlMessage;
16use moqtap_codec::error::CodecError;
17use moqtap_codec::kvp::KeyValuePair;
18use moqtap_codec::types::*;
19use moqtap_codec::varint::VarInt;
20use moqtap_codec::version::DraftVersion;
21
22/// MoQT ALPN identifier (used by raw QUIC transport).
23pub const MOQT_ALPN: &[u8] = b"moq-00";
24
25/// Errors from the draft-11 connection layer.
26#[derive(Debug, thiserror::Error)]
27pub enum ConnectionError {
28    /// Endpoint state machine error.
29    #[error("endpoint error: {0}")]
30    Endpoint(#[from] EndpointError),
31    /// Wire codec error.
32    #[error("codec error: {0}")]
33    Codec(#[from] CodecError),
34    /// Transport-level error.
35    #[error("transport error: {0}")]
36    Transport(#[from] TransportError),
37    /// Variable-length integer decoding error.
38    #[error("varint error: {0}")]
39    VarInt(#[from] moqtap_codec::varint::VarIntError),
40    /// Control stream was not opened.
41    #[error("control stream not open")]
42    NoControlStream,
43    /// Stream ended before a complete message was read.
44    #[error("unexpected end of stream")]
45    UnexpectedEnd,
46    /// Stream was finished by the peer.
47    #[error("stream finished")]
48    StreamFinished,
49    /// Invalid server address string.
50    #[error("invalid server address: {0}")]
51    InvalidAddress(String),
52    /// TLS configuration error.
53    #[error("TLS config error: {0}")]
54    TlsConfig(String),
55    /// Data stream used out of order: an object before its header, or an
56    /// Object ID that does not advance on the last one written.
57    #[error("data stream state error: {0}")]
58    DataStreamState(&'static str),
59    /// A control message this build decoded for draft-11 and then could not
60    /// narrow to draft-11's own message type.
61    ///
62    /// Unreachable, and that is not the same as harmless. `read_control`
63    /// decodes with this connection's own draft, so the `AnyControlMessage` it
64    /// hands back can only carry this draft's variant — but the narrowing arm
65    /// is compiled in every configuration anyway, under
66    /// `#[allow(unreachable_patterns)]` rather than a `cfg` naming the other
67    /// drafts, because such a list has to be edited in every draft
68    /// module whenever a draft is added, and a copy that omits one leaves the
69    /// match non-exhaustive.
70    ///
71    /// Spelled as `CodecError::UnknownMessageType(0)` it would not stay inert:
72    /// every draft's
73    /// [`codec_session_error_code`](Connection::codec_session_error_code)
74    /// answers that variant `Some(PROTOCOL_VIOLATION)`. So the day the
75    /// narrowing did fail, this build's own defect would reach a caller as *the
76    /// peer sent a control message type this draft does not assign, and the
77    /// session must be closed with a Protocol Violation* — carrying `0x00` as
78    /// the codepoint that proved it. A conformance report reading that
79    /// publishes a named, well-evidenced accusation against a relay for
80    /// something no relay did.
81    ///
82    /// A variant of its own is what stops that.
83    /// [`draft_specific_cause`](Connection::draft_specific_cause) answers it
84    /// [`LocalRefusal`], the facade turns that into [`ErrorCause::Facade`], and
85    /// nothing downstream can read a rule out of a cause that says nothing
86    /// reached the wire. What is pinned is the consequence rather than the
87    /// unreachability: nothing pins the arm's reachability, which is exactly
88    /// why the consequence must not be an accusation.
89    ///
90    /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
91    /// [`ErrorCause::Facade`]: crate::dispatch::ErrorCause::Facade
92    #[error(
93        "a control message decoded for draft-11 did not narrow to draft-11: a defect in this build, and evidence about nothing the peer did"
94    )]
95    ControlMessageNarrowing,
96    /// A datagram whose Type announces extension headers arrived with an
97    /// Extension Headers Length of 0.
98    ///
99    /// draft-11 Section 9.2: "If an endpoint receives a datagram with Type 0x01
100    /// and Extension Headers Length is 0, it MUST close the session with
101    /// Protocol Violation." draft-11 Section 9.3: "If an endpoint receives a
102    /// datagram with Type 0x03 and Extension Headers Length is 0, it MUST close
103    /// the session with Protocol Violation."
104    ///
105    /// The codec's header decoder refuses such a datagram as a bare
106    /// `InvalidField`. [`Connection::recv_datagram`] names it here, and
107    /// [`Connection::close_for_data_stream`] closes the session over it.
108    #[error(
109        "the datagram for object {object_id} announces extension headers and has an Extension Headers Length of 0"
110    )]
111    EmptyDatagramExtensions {
112        /// The Object ID the datagram named.
113        object_id: u64,
114    },
115}
116
117/// [`ConnectionError::EmptyDatagramExtensions`] for a datagram the header
118/// decoder refused as `InvalidField`, when its empty extension block was the
119/// fault.
120///
121/// Types 0x01 and 0x03 are the two draft-11 Section 9.2 and draft-11 Section 9.3
122/// name, and both carry an Object ID and a Publisher Priority.
123fn empty_datagram_extensions(data: &[u8]) -> Option<ConnectionError> {
124    use crate::datagram_prefix::{empty_extension_block, Prefix};
125    empty_extension_block(data, |datagram_type, _| {
126        matches!(datagram_type, 0x01 | 0x03).then_some(Prefix { object_id: true, priority: true })
127    })
128    .map(|object_id| ConnectionError::EmptyDatagramExtensions { object_id })
129}
130
131impl From<crate::transport::DialError> for ConnectionError {
132    /// Maps a dial failure onto the variants this error already has, so a
133    /// caller matches `InvalidAddress` or `TlsConfig`.
134    ///
135    /// A `LocalSocket` failure maps to `InvalidAddress`, which the facade reads
136    /// as this side's failure
137    /// ([`ErrorCause::Facade`](crate::dispatch::ErrorCause::Facade)). The dial
138    /// phase stays on [`DialError::phase`](crate::transport::DialError::phase).
139    fn from(e: crate::transport::DialError) -> Self {
140        match e {
141            // Two variants, one arm, deliberately — see above.
142            crate::transport::DialError::InvalidAddress(s)
143            | crate::transport::DialError::LocalSocket(s) => ConnectionError::InvalidAddress(s),
144            crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
145            crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
146        }
147    }
148}
149
150/// Transport type for the connection.
151#[derive(Debug, Clone)]
152pub enum TransportType {
153    /// Raw QUIC via quinn. The `addr` field should be `host:port`.
154    Quic,
155    /// WebTransport via wtransport. The `url` field is the WebTransport URL.
156    WebTransport {
157        /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
158        url: String,
159    },
160}
161
162/// Configuration for a draft-11 MoQT client connection.
163pub struct ClientConfig {
164    /// Additional draft versions to offer in CLIENT_SETUP (draft-11 is always
165    /// offered first).
166    pub additional_versions: Vec<DraftVersion>,
167    /// The transport type (QUIC or WebTransport).
168    pub transport: TransportType,
169    /// Whether to skip TLS certificate verification (for testing).
170    pub skip_cert_verification: bool,
171    /// Custom CA certificates to trust (DER-encoded).
172    pub ca_certs: Vec<Vec<u8>>,
173    /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
174    pub setup_parameters: Vec<moqtap_codec::kvp::KeyValuePair>,
175}
176
177impl ClientConfig {
178    /// Returns the MoQT version varints for the CLIENT_SETUP message.
179    /// Draft-11 first, then any additional versions.
180    pub fn supported_versions(&self) -> Vec<VarInt> {
181        let mut versions = vec![DraftVersion::Draft11.version_varint()];
182        for v in &self.additional_versions {
183            let varint = v.version_varint();
184            if !versions.contains(&varint) {
185                versions.push(varint);
186            }
187        }
188        versions
189    }
190
191    /// Returns the ALPN protocol identifiers for the transport.
192    pub fn alpn(&self) -> Vec<Vec<u8>> {
193        match &self.transport {
194            TransportType::Quic => vec![DraftVersion::Draft11.quic_alpn().to_vec()],
195            TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
196        }
197    }
198}
199
200/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
201pub struct FramedSendStream {
202    inner: SendStream,
203    /// What the subgroup header opened on this stream settled, once one has
204    /// been written.
205    ///
206    /// `None` until a subgroup header has been written, which is what makes an
207    /// object sent before its header answerable rather than unframed bytes.
208    subgroup_objects: Option<SubgroupStreamState>,
209}
210
211/// What a subgroup header fixes for every object written after it.
212///
213/// Two facts, and neither is recoverable from an object on its own: the
214/// Object ID it has to advance past, and whether the stream's type puts an
215/// extension block on each object. The second has to be remembered rather than
216/// guessed: a header type whose Extensions Present column reads Yes puts an
217/// Extension Headers Length on every object of the subgroup, so assuming
218/// "absent" would write a stream a reader could not follow.
219#[derive(Debug, Clone, Copy)]
220struct SubgroupStreamState {
221    /// The last Object ID written, or `None` before the first object.
222    previous_object_id: Option<u64>,
223    /// Whether each object writes an Extension Headers Length field.
224    carries_extension_block: bool,
225}
226
227impl FramedSendStream {
228    /// Create a new framed send stream.
229    pub fn new(inner: SendStream) -> Self {
230        Self { inner, subgroup_objects: None }
231    }
232
233    /// Get the transport-level stream ID.
234    pub fn stream_id(&self) -> u64 {
235        self.inner.stream_id()
236    }
237
238    /// Write a control message to the stream with type+length framing.
239    /// Returns the raw bytes that were written (for event capture).
240    pub async fn write_control(
241        &mut self,
242        msg: &AnyControlMessage,
243    ) -> Result<Vec<u8>, ConnectionError> {
244        let mut buf = Vec::new();
245        msg.encode(&mut buf)?;
246        self.inner.write_all(&buf).await?;
247        Ok(buf)
248    }
249
250    /// Write a subgroup stream header. Also opens the Object ID bookkeeping
251    /// [`FramedSendStream::write_subgroup_object`] holds the stream to.
252    ///
253    /// The header is refused, and nothing is written, if its fields disagree
254    /// with its own stream type. That check has to happen here rather than at
255    /// the first object: the type is what every object after it is framed
256    /// against, so a header that went out saying the wrong thing cannot be
257    /// taken back.
258    pub async fn write_subgroup_header(
259        &mut self,
260        header: &AnySubgroupHeader,
261    ) -> Result<(), ConnectionError> {
262        let mut buf = Vec::new();
263        header.encode_stream_checked(&mut buf)?;
264        self.inner.write_all(&buf).await?;
265        // The framing is read off the header now, while the header is in hand.
266        // An object arriving later cannot say whether the stream carries an
267        // extension block, and writing one either way is not a recoverable
268        // mistake: the reader takes the next field along as the missing length
269        // and misframes every object after it.
270        self.subgroup_objects = Some(SubgroupStreamState {
271            previous_object_id: None,
272            carries_extension_block: header.carries_extension_block(),
273        });
274        Ok(())
275    }
276
277    /// Write a fetch response header.
278    pub async fn write_fetch_header(
279        &mut self,
280        header: &AnyFetchHeader,
281    ) -> Result<(), ConnectionError> {
282        let mut buf = Vec::new();
283        header.encode_stream(&mut buf);
284        self.inner.write_all(&buf).await?;
285        Ok(())
286    }
287
288    /// Append a draft-11 subgroup object (header + payload) to the stream.
289    ///
290    /// Section 9.4.2: "A publisher MUST NOT send an Object on a stream if its
291    /// Object ID is less than a previously sent Object ID within a given group
292    /// in that stream." A subgroup stream carries one group, so the Object IDs
293    /// written here are exactly the ones that sentence compares, and the
294    /// comparison needs the object before - which no per-header check can see.
295    /// The state advances only once the object has been written, so declining to
296    /// write an object leaves the next one measured against the last one kept.
297    ///
298    /// An equal Object ID is refused as well as a smaller one. The draft's own
299    /// sentence forbids only "less than", but an Object ID names an Object
300    /// within a Group: writing one twice on a stream describes the same Object
301    /// with two different payloads, and a reader has no way to choose. The
302    /// dispatch-level writer in the codec draws the line in the same place, and
303    /// two writers that disagreed about it would be worse than either answer.
304    ///
305    /// # Errors
306    ///
307    /// [`ConnectionError::DataStreamState`] if no subgroup header has been
308    /// written yet, or if `object` does not advance past the last one written.
309    pub async fn write_subgroup_object(
310        &mut self,
311        object: &SubgroupObject,
312    ) -> Result<(), ConnectionError> {
313        let state = self
314            .subgroup_objects
315            .as_mut()
316            .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
317        let object_id = object.header.object_id.into_inner();
318        if matches!(state.previous_object_id, Some(prev) if object_id <= prev) {
319            return Err(ConnectionError::DataStreamState(
320                "object id does not advance on the last one written to this stream",
321            ));
322        }
323        // The declared length comes from the payload rather than from the
324        // caller's field: a header that disagrees with the bytes beside it
325        // desynchronises every object after it on the stream, and nothing
326        // downstream can recover.
327        let mut header = object.header.clone();
328        header.payload_length = VarInt::from_usize(object.payload.len());
329        let mut buf = Vec::new();
330        header.encode_checked_with_extensions(state.carries_extension_block, &mut buf)?;
331        buf.extend_from_slice(&object.payload);
332        self.inner.write_all(&buf).await?;
333        state.previous_object_id = Some(object_id);
334        Ok(())
335    }
336
337    /// Append a draft-11 fetch object (header + payload) to the stream.
338    pub async fn write_fetch_object(
339        &mut self,
340        object: &FetchObject,
341    ) -> Result<(), ConnectionError> {
342        // The declared length comes from the payload rather than from the
343        // caller's field: a header that disagrees with the bytes beside it
344        // desynchronises every object after it on the stream, and nothing
345        // downstream can recover.
346        let mut header = object.header.clone();
347        header.payload_length = VarInt::from_usize(object.payload.len());
348        let mut buf = Vec::new();
349        header.encode_checked(&mut buf)?;
350        buf.extend_from_slice(&object.payload);
351        self.inner.write_all(&buf).await?;
352        Ok(())
353    }
354
355    /// Finish the stream (send FIN).
356    pub async fn finish(&mut self) -> Result<(), ConnectionError> {
357        self.inner.finish()?;
358        Ok(())
359    }
360}
361
362/// What an Object Status makes of an object, for Section 9.1.1.1's table.
363///
364/// `None` is Normal and not "no status": a datagram carrying a payload has no
365/// status field at all, and an object with a payload is an ordinary object.
366fn object_role(status: Option<u64>) -> ObjectRole {
367    match status {
368        None | Some(0x0) => ObjectRole::Produced,
369        // 0x4, end of Track. This draft folded drafts 08 through 10's second
370        // end-of-track status into this one and kept the looser of the two
371        // conditions, so the Group ID may equal the largest group seen.
372        Some(0x4) => ObjectRole::EndsTrack(Some(EndOfTrackForm::LastGroup)),
373        // Every other status is a statement about objects rather than one of
374        // them. Two of them name an Object ID one past the largest on purpose,
375        // so counting one as produced would refuse the end-of-track object the
376        // draft goes on to define.
377        _ => ObjectRole::Neither,
378    }
379}
380
381/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
382pub struct FramedRecvStream {
383    inner: RecvStream,
384    buf: BytesMut,
385    /// The record this stream's objects are measured against, and the Group ID
386    /// its header named.
387    ///
388    /// One group for the whole stream: a subgroup header names it once and no
389    /// object header repeats it. `None` on a stream that was never given one —
390    /// a stream for an alias no live binding names, and every stream built
391    /// outside [`Connection::accept_subgroup_stream`] — and on such a stream
392    /// the objects are read without being measured against the track at all.
393    tracking: Option<(TrackObjects, u64)>,
394    /// Whether the subgroup stream this reader is on carries an extension block
395    /// on every object.
396    ///
397    /// The stream's Type says so and nothing in an object header repeats it, so
398    /// a reader that does not remember the Type cannot parse the objects at all:
399    /// on an extensions-bearing stream it reads the Extension Headers Length as
400    /// the Object Payload Length and every field after it is nonsense.
401    ///
402    /// Set by [`FramedRecvStream::read_subgroup_header`] and read by
403    /// [`FramedRecvStream::read_subgroup_object`]. `false` until a subgroup
404    /// header has been read, which is the only order those two may be called
405    /// in.
406    subgroup_has_extensions: bool,
407    /// The Prior Group ID Gap values this subgroup stream's Objects carried.
408    /// Set by [`FramedRecvStream::read_subgroup_header`].
409    subgroup_gaps: Option<SubgroupGaps>,
410    /// The Prior gap conditions this fetch stream's Objects show. Reset by
411    /// [`FramedRecvStream::read_fetch_header`].
412    fetch_gaps: moqtap_codec::draft11::data_stream::FetchGaps,
413    /// The bytes of the last control frame a capturing read refused.
414    refused: Option<Vec<u8>>,
415}
416
417impl FramedRecvStream {
418    /// Create a new framed receive stream.
419    pub fn new(inner: RecvStream) -> Self {
420        Self {
421            inner,
422            buf: BytesMut::with_capacity(4096),
423            subgroup_has_extensions: false,
424            subgroup_gaps: None,
425            tracking: None,
426            fetch_gaps: moqtap_codec::draft11::data_stream::FetchGaps::new(),
427            refused: None,
428        }
429    }
430
431    /// Measure this stream's objects against `objects`, all of them in `group`.
432    ///
433    /// Called by [`Connection::accept_subgroup_stream`] once the header has been
434    /// read, which is the only point at which both the track and the group are
435    /// known.
436    fn measure_objects_against(&mut self, objects: TrackObjects, group: u64) {
437        self.tracking = Some((objects, group));
438    }
439
440    /// The bytes of the control frame the last capturing read refused.
441    pub(crate) fn take_refused(&mut self) -> Option<Vec<u8>> {
442        self.refused.take()
443    }
444
445    /// Keep the first `len` buffered bytes as a refused frame when the read is
446    /// capturing, and hand `err` back.
447    fn refuse(&mut self, capture_raw: bool, len: usize, err: ConnectionError) -> ConnectionError {
448        if capture_raw {
449            self.refused = Some(self.buf[..len].to_vec());
450        }
451        err
452    }
453
454    /// Record or judge one object this stream carried.
455    ///
456    /// The whole of Section 9.1.1.1's rule that a single header cannot settle: the
457    /// object's Group ID is the stream's, its Object ID is its own, and what
458    /// they are measured against is everything the track has carried on any
459    /// stream.
460    fn note_subgroup_object(&self, header: &ObjectHeader) -> Result<(), ConnectionError> {
461        let Some((objects, group)) = &self.tracking else { return Ok(()) };
462        let at = ObjectLocation { group: *group, object: header.object_id.into_inner() };
463        objects.note(at, object_role(Some(header.object_status as u64))).map_err(|placement| {
464            ConnectionError::Endpoint(EndpointError::EndOfTrackOutOfPlace {
465                alias: objects.alias(),
466                group: at.group,
467                object: at.object,
468                placement,
469            })
470        })
471    }
472
473    /// Get the transport-level stream ID.
474    pub fn stream_id(&self) -> u64 {
475        self.inner.stream_id()
476    }
477
478    /// Read more data from the stream into the internal buffer.
479    async fn fill(&mut self) -> Result<bool, ConnectionError> {
480        let mut tmp = [0u8; 4096];
481        match self.inner.read(&mut tmp).await {
482            Ok(Some(n)) => {
483                self.buf.extend_from_slice(&tmp[..n]);
484                Ok(true)
485            }
486            Ok(None) => Ok(false),
487            Err(e) => Err(ConnectionError::Transport(e)),
488        }
489    }
490
491    /// Ensure at least `n` bytes are available in the buffer.
492    async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
493        while self.buf.len() < n {
494            if !self.fill().await? {
495                return Err(ConnectionError::UnexpectedEnd);
496            }
497        }
498        Ok(())
499    }
500
501    /// Read a control message from the stream.
502    ///
503    /// When `capture_raw` is true, the returned tuple includes a clone of the
504    /// framed wire bytes (for observer emission). When false, the second
505    /// element is `None` and the payload clone is skipped.
506    pub async fn read_control(
507        &mut self,
508        capture_raw: bool,
509    ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
510        self.refused = None;
511        // Read type ID varint
512        self.ensure(1).await?;
513        let type_len = varint_len(self.buf[0]);
514        self.ensure(type_len).await?;
515
516        let mut cursor = &self.buf[..type_len];
517        if let Err(e) = VarInt::decode(&mut cursor) {
518            return Err(self.refuse(capture_raw, type_len, e.into()));
519        }
520
521        // Draft-11 uses a fixed 16-bit length after the type id.
522        let len_field_size = 2;
523        self.ensure(type_len + len_field_size).await?;
524        let payload_len = u16::from_be_bytes([self.buf[type_len], self.buf[type_len + 1]]) as usize;
525
526        // Read full payload
527        let total = type_len + len_field_size + payload_len;
528        self.ensure(total).await?;
529
530        // Capture raw bytes only if requested (observer attached).
531        let raw = capture_raw.then(|| self.buf[..total].to_vec());
532
533        // Now decode the whole message using the draft-11 dispatcher
534        let mut frame = &self.buf[..total];
535        let msg = match AnyControlMessage::decode(DraftVersion::Draft11, &mut frame) {
536            Ok(msg) => msg,
537            Err(e) => return Err(self.refuse(capture_raw, total, e.into())),
538        };
539        self.buf.advance(total);
540        Ok((msg, raw))
541    }
542
543    /// Read a subgroup stream header.
544    pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
545        self.ensure(1).await?;
546        loop {
547            let mut cursor = &self.buf[..];
548            match AnySubgroupHeader::decode_stream(DraftVersion::Draft11, &mut cursor) {
549                Ok(header) => {
550                    let consumed = self.buf.len() - cursor.remaining();
551                    self.buf.advance(consumed);
552                    // Clippy would rather see these two arms as an `if let`, and rustc rejects
553                    // that in a single-draft build, where the pattern is irrefutable. Only a
554                    // `match` satisfies both.
555                    #[allow(clippy::single_match)]
556                    match header {
557                        AnySubgroupHeader::Draft11(ref d) => {
558                            self.subgroup_has_extensions = d.stream_type.has_extensions();
559                            self.subgroup_gaps = Some(SubgroupGaps::new(d));
560                        }
561                        // Only this draft's header sets the flag. With draft 11 the only enabled
562                        // draft `AnySubgroupHeader` has a single variant, the arm above is
563                        // exhaustive and this one unreachable. Compiled in every configuration with
564                        // the lint allowed, rather than gated on a `cfg` naming every other
565                        // draft: such a list has to be edited in every draft module whenever a
566                        // draft is added, and a copy that omits one leaves this match
567                        // non-exhaustive.
568                        #[allow(unreachable_patterns)]
569                        _ => {}
570                    }
571                    return Ok(header);
572                }
573                Err(e) if e.is_incomplete() => {
574                    if !self.fill().await? {
575                        return Err(ConnectionError::UnexpectedEnd);
576                    }
577                }
578                Err(e) => return Err(ConnectionError::Codec(e)),
579            }
580        }
581    }
582
583    /// Read a fetch response header.
584    pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
585        self.ensure(1).await?;
586        loop {
587            let mut cursor = &self.buf[..];
588            match AnyFetchHeader::decode_stream(DraftVersion::Draft11, &mut cursor) {
589                Ok(header) => {
590                    let consumed = self.buf.len() - cursor.remaining();
591                    self.buf.advance(consumed);
592                    self.fetch_gaps = moqtap_codec::draft11::data_stream::FetchGaps::new();
593                    return Ok(header);
594                }
595                Err(e) if e.is_incomplete() => {
596                    if !self.fill().await? {
597                        return Err(ConnectionError::UnexpectedEnd);
598                    }
599                }
600                Err(e) => return Err(ConnectionError::Codec(e)),
601            }
602        }
603    }
604
605    /// Read the next draft-11 subgroup object (header + payload).
606    ///
607    /// Whether an object carries an extension block is a property of the
608    /// stream's Type, not of the object, so this reads it from the header
609    /// [`FramedRecvStream::read_subgroup_header`] recorded rather than assuming
610    /// either answer. Assuming "no extensions" is not a conservative default: on
611    /// a stream whose Type announces them the Extension Headers Length is read
612    /// as the Object Payload Length, and every object on the stream comes back
613    /// wrong instead of being refused.
614    ///
615    /// It is also what puts the rule binding extension headers to Object Status
616    /// within reach on a subgroup stream. A reader that never reads the block
617    /// cannot notice that a non-existent Object carried one.
618    ///
619    /// A Prior Group ID Gap larger than the stream's Group ID, or gaps that
620    /// leave no value common to every Object on the stream that carried one,
621    /// are returned as [`CodecError::MalformedTrack`] (draft-11 Section 10.1).
622    pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
623        loop {
624            let mut cursor = &self.buf[..];
625            match ObjectHeader::decode_with_extensions(self.subgroup_has_extensions, &mut cursor) {
626                Ok(header) => {
627                    let header_consumed = self.buf.len() - cursor.remaining();
628                    let payload_len = header.payload_length.into_inner() as usize;
629                    let total = header_consumed + payload_len;
630                    if self.buf.len() < total {
631                        if !self.fill().await? {
632                            return Err(ConnectionError::UnexpectedEnd);
633                        }
634                        continue;
635                    }
636                    if let Some(gaps) = &mut self.subgroup_gaps {
637                        gaps.check(&header).map_err(ConnectionError::Codec)?;
638                    }
639                    let payload = self.buf[header_consumed..total].to_vec();
640                    self.buf.advance(total);
641                    self.note_subgroup_object(&header)?;
642                    return Ok(SubgroupObject { header, payload });
643                }
644                Err(e) if e.is_incomplete() => {
645                    if !self.fill().await? {
646                        return Err(ConnectionError::UnexpectedEnd);
647                    }
648                }
649                Err(e) => return Err(ConnectionError::Codec(e)),
650            }
651        }
652    }
653
654    /// Read the next draft-11 fetch object (header + payload).
655    ///
656    /// A Prior Group ID Gap fault the stream shows (draft-11 Section 10.1) is
657    /// returned as [`CodecError::MalformedTrack`], and an extension block that
658    /// does not parse as [`CodecError::MalformedObjectProperties`].
659    pub async fn read_fetch_object(&mut self) -> Result<FetchObject, ConnectionError> {
660        loop {
661            let mut cursor = &self.buf[..];
662            match FetchObjectHeader::decode(&mut cursor) {
663                Ok(header) => {
664                    let header_consumed = self.buf.len() - cursor.remaining();
665                    let payload_len = header.payload_length.into_inner() as usize;
666                    let total = header_consumed + payload_len;
667                    if self.buf.len() < total {
668                        if !self.fill().await? {
669                            return Err(ConnectionError::UnexpectedEnd);
670                        }
671                        continue;
672                    }
673                    let payload = self.buf[header_consumed..total].to_vec();
674                    self.buf.advance(total);
675                    self.fetch_gaps
676                        .check(
677                            header.group_id.into_inner(),
678                            header.object_id.into_inner(),
679                            &header.extensions,
680                        )
681                        .map_err(ConnectionError::Codec)?;
682                    return Ok(FetchObject { header, payload });
683                }
684                Err(e) if e.is_incomplete() => {
685                    if !self.fill().await? {
686                        return Err(ConnectionError::UnexpectedEnd);
687                    }
688                }
689                Err(e) => return Err(ConnectionError::Codec(e)),
690            }
691        }
692    }
693}
694
695/// A live draft-11 MoQT connection over QUIC or WebTransport.
696pub struct Connection {
697    transport: Transport,
698    endpoint: Endpoint,
699    control_send: Option<FramedSendStream>,
700    control_recv: Option<FramedRecvStream>,
701    observer: Option<Box<dyn ConnectionObserver>>,
702    /// Setup events buffered during `connect()` and replayed when an
703    /// observer attaches via `set_observer` — without this, an observer
704    /// attached after `connect` returns would never see the handshake.
705    pending_events: Vec<ClientEvent>,
706    /// The server's half of the setup handshake, kept whole.
707    ///
708    /// The endpoint acts on the parameters it recognises and retains none of
709    /// them, and which parameters a server sends — in what order, with what
710    /// values — is the sharpest thing a session says about the implementation
711    /// behind it.
712    server_setup: AnyControlMessage,
713    /// The framed wire bytes of [`Self::server_setup`].
714    server_setup_raw: Option<Vec<u8>>,
715}
716
717impl Connection {
718    /// Connect to a draft-11 MoQT server as a client.
719    pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
720        // PATH is for native QUIC only, and the transport is known here and
721        // nowhere further in. Refusing before dialling means a session that
722        // the server would close on sight is never opened.
723        setup::validate_client_path_transport(
724            &config.setup_parameters,
725            matches!(config.transport, TransportType::WebTransport { .. }),
726        )
727        .map_err(EndpointError::from)?;
728
729        let transport = match &config.transport {
730            TransportType::Quic => Self::connect_quic(addr, &config).await?,
731            TransportType::WebTransport { url } => {
732                let url = url.clone();
733                Self::connect_webtransport(&url, &config).await?
734            }
735        };
736
737        Self::adopt(transport, config).await
738    }
739
740    /// Run the MoQT setup handshake over a transport somebody else established.
741    ///
742    /// For choosing the draft from what the server selected: dial once through
743    /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
744    /// connection to the module its answer names. [`Self::connect`] cannot do
745    /// this — it derives its single ALPN from the draft it was given.
746    ///
747    /// `config.draft` must match this module. The transport is adopted as
748    /// given; nothing here re-checks the ALPN it was negotiated with.
749    pub async fn adopt(
750        transport: Transport,
751        config: ClientConfig,
752    ) -> Result<Self, ConnectionError> {
753        Self::adopt_offering(transport, config, None).await
754    }
755
756    /// [`Self::adopt`], offering exactly `versions` in CLIENT_SETUP.
757    ///
758    /// `None` offers what `config` implies, which is what [`Self::adopt`]
759    /// passes. `Some` replaces the list outright, and takes raw varints rather
760    /// than [`DraftVersion`]s because the reason to reach for this is to offer
761    /// a version no draft assigns — which an enum of drafts cannot name.
762    ///
763    /// A server MUST answer with a version the client offered and MUST
764    /// otherwise close the session; from draft-11 the code for that is
765    /// `VERSION_NEGOTIATION_FAILED` (0x15). How a relay spells the refusal is
766    /// a conformance measurement, and offering a version deliberately outside
767    /// the negotiable set is the only way to ask for it.
768    pub async fn adopt_offering(
769        transport: Transport,
770        config: ClientConfig,
771        versions: Option<Vec<VarInt>>,
772    ) -> Result<Self, ConnectionError> {
773        Self::adopt_with(transport, config, versions, None).await
774    }
775
776    /// [`Self::adopt`] with `observer` attached before the handshake.
777    ///
778    /// The SETUP this side sends is reported as it is written, and a SETUP from
779    /// the peer that this side refuses as [`ClientEvent::ControlMessageRefused`].
780    /// [`Self::set_observer`] after `adopt` sees the same handshake events, but
781    /// only once the handshake has succeeded.
782    pub async fn adopt_observed(
783        transport: Transport,
784        config: ClientConfig,
785        observer: Box<dyn ConnectionObserver>,
786    ) -> Result<Self, ConnectionError> {
787        Self::adopt_with(transport, config, None, Some(observer)).await
788    }
789
790    /// [`Self::adopt_offering`] with `observer` attached before the handshake,
791    /// as [`Self::adopt_observed`].
792    pub async fn adopt_offering_observed(
793        transport: Transport,
794        config: ClientConfig,
795        versions: Option<Vec<VarInt>>,
796        observer: Box<dyn ConnectionObserver>,
797    ) -> Result<Self, ConnectionError> {
798        Self::adopt_with(transport, config, versions, Some(observer)).await
799    }
800
801    /// [`Self::adopt_offering`], reporting to `observer` from the first byte
802    /// written.
803    pub(crate) async fn adopt_with(
804        transport: Transport,
805        config: ClientConfig,
806        versions: Option<Vec<VarInt>>,
807        observer: Option<Box<dyn ConnectionObserver>>,
808    ) -> Result<Self, ConnectionError> {
809        // PATH is for native QUIC only, and the transport is known here and
810        // nowhere further in. Refusing before dialling means a session that
811        // the server would close on sight is never opened.
812        setup::validate_client_path_transport(
813            &config.setup_parameters,
814            matches!(config.transport, TransportType::WebTransport { .. }),
815        )
816        .map_err(EndpointError::from)?;
817
818        // Open bidirectional control stream
819        let (send, recv) = transport.open_bi().await?;
820        let mut control_send = FramedSendStream::new(send);
821        let mut control_recv = FramedRecvStream::new(recv);
822
823        // Perform setup handshake
824        let mut endpoint = Endpoint::new(Role::Client);
825        endpoint.connect()?;
826        let setup_msg = endpoint.send_client_setup(
827            versions.unwrap_or_else(|| config.supported_versions()),
828            config.setup_parameters.clone(),
829        )?;
830        let any_setup = AnyControlMessage::Draft11(setup_msg);
831        let raw_setup = control_send.write_control(&any_setup).await?;
832        // Reported as written, so a peer that never answers still leaves it
833        // observed.
834        if let Some(obs) = &observer {
835            obs.on_event_owned(ClientEvent::ControlMessage {
836                direction: Direction::Send,
837                message: any_setup.clone(),
838                raw: Some(raw_setup.clone()),
839            });
840        }
841
842        let (server_setup, raw_server_setup) = match control_recv.read_control(true).await {
843            Ok(read) => read,
844            Err(e) => {
845                if let Some(obs) = &observer {
846                    if let Some(event) = Self::refused_event(&e, control_recv.take_refused()) {
847                        obs.on_event_owned(event);
848                    }
849                }
850                return Err(e);
851            }
852        };
853        match &server_setup {
854            AnyControlMessage::Draft11(ControlMessage::ServerSetup(ref ss)) => {
855                if let Err(e) = endpoint.receive_server_setup(ss) {
856                    let err = ConnectionError::from(e);
857                    Self::refuse_setup(observer.as_deref(), &err, &server_setup, &raw_server_setup);
858                    return Err(err);
859                }
860            }
861            _ => {
862                return Err(ConnectionError::Endpoint(EndpointError::NotActive));
863            }
864        }
865
866        let mut pending_events = Vec::with_capacity(3);
867        pending_events.push(ClientEvent::ControlMessage {
868            direction: Direction::Send,
869            message: any_setup,
870            raw: Some(raw_setup),
871        });
872        pending_events.push(ClientEvent::ControlMessage {
873            direction: Direction::Receive,
874            message: server_setup.clone(),
875            raw: raw_server_setup.clone(),
876        });
877        if let Some(v) = endpoint.negotiated_version() {
878            pending_events.push(ClientEvent::SetupComplete { negotiated_version: v.into_inner() });
879        }
880
881        if let Some(obs) = &observer {
882            // The SETUP this side sent was reported as it was written.
883            for event in pending_events.drain(..).skip(1) {
884                obs.on_event_owned(event);
885            }
886        }
887
888        Ok(Self {
889            transport,
890            endpoint,
891            control_send: Some(control_send),
892            control_recv: Some(control_recv),
893            observer,
894            pending_events,
895            server_setup,
896            server_setup_raw: raw_server_setup,
897        })
898    }
899
900    /// Establish a raw QUIC connection.
901    ///
902    /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
903    /// TLS and endpoint setup.
904    async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
905        let (transport, _negotiated) = crate::transport::dial_quic(
906            addr,
907            &crate::transport::QuicDialOptions {
908                skip_cert_verification: config.skip_cert_verification,
909                ca_certs: config.ca_certs.clone(),
910                ..crate::transport::QuicDialOptions::new(config.alpn())
911            },
912        )
913        .await?;
914        Ok(transport)
915    }
916
917    /// Establish a WebTransport connection.
918    ///
919    /// [`crate::transport::dial_webtransport`] holds the TLS and endpoint
920    /// setup, exactly as `connect_quic` above defers its own. That is not
921    /// only deduplication: both dials hand the same `QuicDialOptions` to the
922    /// same config constructor in `transport::quic`, so the bundled roots and
923    /// `config.ca_certs` are what each of them trusts and one relay gets one
924    /// verdict whichever transport carries it. Settling trust at this call site
925    /// instead — from `wtransport`'s own builder settings, or from a second
926    /// config of this draft's own — puts the decision in two places, and a
927    /// caller's private CA then reaches only the dials whose call site
928    /// installed it.
929    #[cfg(feature = "webtransport")]
930    async fn connect_webtransport(
931        url: &str,
932        config: &ClientConfig,
933    ) -> Result<Transport, ConnectionError> {
934        Ok(crate::transport::dial_webtransport(
935            url,
936            &crate::transport::QuicDialOptions {
937                skip_cert_verification: config.skip_cert_verification,
938                ca_certs: config.ca_certs.clone(),
939                ..crate::transport::QuicDialOptions::new(config.alpn())
940            },
941        )
942        .await?)
943    }
944
945    /// Stub for when the webtransport feature is not enabled.
946    #[cfg(not(feature = "webtransport"))]
947    async fn connect_webtransport(
948        _url: &str,
949        _config: &ClientConfig,
950    ) -> Result<Transport, ConnectionError> {
951        Err(ConnectionError::Transport(TransportError::Connect(
952            "webtransport feature not enabled".into(),
953        )))
954    }
955
956    // ── Observer ───────────────────────────────────────────────
957
958    /// Attach an observer. Buffered handshake events from `connect()` are
959    /// flushed in arrival order before this returns.
960    pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
961        self.observer = Some(observer);
962        for event in self.pending_events.drain(..) {
963            if let Some(ref obs) = self.observer {
964                obs.on_event_owned(event);
965            }
966        }
967    }
968
969    /// Remove the observer.
970    pub fn clear_observer(&mut self) {
971        self.observer = None;
972    }
973
974    /// How the QUIC connection was closed, once it has been. See
975    /// [`TransportClose`](crate::transport::TransportClose).
976    pub fn transport_close(&self) -> Option<crate::transport::TransportClose> {
977        self.transport.transport_close()
978    }
979
980    /// Emit an event to the observer, if one is attached.
981    fn emit(&self, event: ClientEvent) {
982        if let Some(ref obs) = self.observer {
983            obs.on_event_owned(event);
984        }
985    }
986
987    /// Report a received frame this endpoint refused, when an observer is
988    /// attached and the refusal is the peer's: see [`Self::refused_event`].
989    fn emit_refused(&self, err: &ConnectionError, raw: Option<Vec<u8>>) {
990        if self.observer.is_some() {
991            if let Some(event) = Self::refused_event(err, raw) {
992                self.emit(event);
993            }
994        }
995    }
996
997    /// The [`ClientEvent::ControlMessageRefused`] for `raw`, when `err` refuses
998    /// what the peer sent: the decoder refused it, or it broke a rule
999    /// [`EndpointError::fault`] or [`Self::draft_specific_cause`] lays on the
1000    /// peer.
1001    fn refused_event(err: &ConnectionError, raw: Option<Vec<u8>>) -> Option<ClientEvent> {
1002        use crate::above_codec_rules::{DraftSpecificCause, EndpointFault};
1003        let peers = match err {
1004            ConnectionError::Codec(_) | ConnectionError::VarInt(_) => true,
1005            ConnectionError::Endpoint(e) => matches!(e.fault(), EndpointFault::Peer(_)),
1006            other => matches!(
1007                Self::draft_specific_cause(other),
1008                Some(DraftSpecificCause::PeerViolation { .. } | DraftSpecificCause::Codec { .. })
1009            ),
1010        };
1011        let raw = raw.filter(|_| peers)?;
1012        Some(ClientEvent::ControlMessageRefused { raw })
1013    }
1014
1015    /// Report a peer's SETUP the handshake refused to an observer attached
1016    /// before it: the message as received, then the refusal.
1017    fn refuse_setup(
1018        observer: Option<&dyn ConnectionObserver>,
1019        err: &ConnectionError,
1020        setup: &AnyControlMessage,
1021        raw: &Option<Vec<u8>>,
1022    ) {
1023        let Some(obs) = observer else { return };
1024        obs.on_event_owned(ClientEvent::ControlMessage {
1025            direction: Direction::Receive,
1026            message: setup.clone(),
1027            raw: raw.clone(),
1028        });
1029        if let Some(event) = Self::refused_event(err, raw.clone()) {
1030            obs.on_event_owned(event);
1031        }
1032    }
1033
1034    // ── Control message I/O ─────────────────────────────────
1035
1036    /// Send a control message on the control stream.
1037    pub async fn send_control(&mut self, msg: &ControlMessage) -> Result<(), ConnectionError> {
1038        let any = AnyControlMessage::Draft11(msg.clone());
1039        let send = self.control_send.as_mut().ok_or(ConnectionError::NoControlStream)?;
1040        let raw = send.write_control(&any).await?;
1041        self.emit(ClientEvent::ControlMessage {
1042            direction: Direction::Send,
1043            message: any,
1044            raw: Some(raw),
1045        });
1046        Ok(())
1047    }
1048
1049    /// Read the next control message from the control stream.
1050    pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
1051        self.recv_control_raw().await.map(|(msg, _)| msg)
1052    }
1053
1054    /// [`Self::recv_control`], keeping the frame's wire bytes when an observer
1055    /// is attached.
1056    async fn recv_control_raw(
1057        &mut self,
1058    ) -> Result<(ControlMessage, Option<Vec<u8>>), ConnectionError> {
1059        let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
1060        let capture_raw = self.observer.is_some();
1061        let (any, raw) = match recv.read_control(capture_raw).await {
1062            Ok(v) => v,
1063            Err(e) => {
1064                let raw = recv.take_refused();
1065                let err = self.close_for_codec(e);
1066                self.emit_refused(&err, raw);
1067                return Err(err);
1068            }
1069        };
1070        if capture_raw {
1071            self.emit(ClientEvent::ControlMessage {
1072                direction: Direction::Receive,
1073                message: any.clone(),
1074                raw: raw.clone(),
1075            });
1076        }
1077        match any {
1078            AnyControlMessage::Draft11(msg) => Ok((msg, raw)),
1079            // `AnyControlMessage` carries one variant per enabled draft feature. With draft 11 the
1080            // only one enabled the arm above is exhaustive and this rejection arm unreachable.
1081            // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
1082            // naming the other drafts: such a list has to be edited in every draft
1083            // module whenever a draft is added, and a copy that omits one leaves this match
1084            // non-exhaustive.
1085            #[allow(unreachable_patterns)]
1086            _ => Err(ConnectionError::ControlMessageNarrowing),
1087        }
1088    }
1089
1090    /// Read and dispatch the next incoming control message through the endpoint
1091    /// state machine. Returns the decoded message for inspection.
1092    pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
1093        let (msg, raw) = self.recv_control_raw().await?;
1094        if let Err(e) = self.endpoint.receive_message(msg.clone()) {
1095            let err = self.close_if_session_fatal(e);
1096            self.emit_refused(&err, raw);
1097            return Err(err);
1098        }
1099
1100        if let ControlMessage::GoAway(ref ga) = msg {
1101            self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
1102        }
1103
1104        Ok(msg)
1105    }
1106
1107    // ── Subscribe flow ──────────────────────────────────────
1108    /// The lowest Track Alias no live subscription on this connection holds.
1109    ///
1110    /// This draft makes the Track Alias the subscriber's to choose, so
1111    /// [`Self::subscribe`] takes one and every later-era caller has nothing to
1112    /// pass. See
1113    /// [`Endpoint::next_free_track_alias`](crate::draft11::endpoint::Endpoint::next_free_track_alias)
1114    /// for why the value is read off the endpoint rather than asked of the
1115    /// caller, and why picking one here stays correct alongside a caller that
1116    /// picks its own.
1117    pub fn next_free_track_alias(&self) -> VarInt {
1118        self.endpoint.next_free_track_alias()
1119    }
1120
1121    /// Send a SUBSCRIBE and return the allocated request ID.
1122    #[allow(clippy::too_many_arguments)]
1123    pub async fn subscribe(
1124        &mut self,
1125        track_alias: VarInt,
1126        track_namespace: TrackNamespace,
1127        track_name: Vec<u8>,
1128        subscriber_priority: u8,
1129        group_order: GroupOrder,
1130        filter_type: VarInt,
1131    ) -> Result<VarInt, ConnectionError> {
1132        let (req_id, msg) = self.endpoint.subscribe(
1133            track_alias,
1134            track_namespace,
1135            track_name,
1136            subscriber_priority,
1137            group_order,
1138            filter_type,
1139        )?;
1140        self.send_control(&msg).await?;
1141        Ok(req_id)
1142    }
1143
1144    /// Send a SUBSCRIBE for a range of the track and return the allocated ID.
1145    ///
1146    /// The Filter Type comes from the arguments, so the message cannot name a
1147    /// filter whose fields it does not carry.
1148    #[allow(clippy::too_many_arguments)]
1149    pub async fn subscribe_range(
1150        &mut self,
1151        track_alias: VarInt,
1152        track_namespace: TrackNamespace,
1153        track_name: Vec<u8>,
1154        subscriber_priority: u8,
1155        group_order: GroupOrder,
1156        start_location: Location,
1157        end_group: Option<VarInt>,
1158    ) -> Result<VarInt, ConnectionError> {
1159        let (req_id, msg) = self.endpoint.subscribe_range(
1160            track_alias,
1161            track_namespace,
1162            track_name,
1163            subscriber_priority,
1164            group_order,
1165            start_location,
1166            end_group,
1167        )?;
1168        self.send_control(&msg).await?;
1169        Ok(req_id)
1170    }
1171
1172    /// Send an UNSUBSCRIBE for the given request ID.
1173    pub async fn unsubscribe(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1174        let msg = self.endpoint.unsubscribe(request_id)?;
1175        self.send_control(&msg).await
1176    }
1177
1178    /// Accept a subscription the peer opened, sending SUBSCRIBE_OK.
1179    pub async fn subscribe_ok(
1180        &mut self,
1181        request_id: VarInt,
1182        expires: VarInt,
1183        group_order: GroupOrder,
1184        parameters: Vec<KeyValuePair>,
1185    ) -> Result<(), ConnectionError> {
1186        let msg = self.endpoint.send_subscribe_ok(request_id, expires, group_order, parameters)?;
1187        self.send_control(&msg).await
1188    }
1189
1190    /// Reject a subscription the peer opened, sending SUBSCRIBE_ERROR.
1191    ///
1192    /// The Track Alias travels back with the refusal: under the 'Retry Track
1193    /// Alias' code it is the alias the peer should try again with, and under
1194    /// any other code it is ignored.
1195    pub async fn subscribe_error(
1196        &mut self,
1197        request_id: VarInt,
1198        error_code: VarInt,
1199        reason_phrase: Vec<u8>,
1200        track_alias: VarInt,
1201    ) -> Result<(), ConnectionError> {
1202        let msg = self.endpoint.send_subscribe_error(
1203            request_id,
1204            error_code,
1205            reason_phrase,
1206            track_alias,
1207        )?;
1208        self.send_control(&msg).await
1209    }
1210
1211    /// End a subscription this endpoint accepted, sending SUBSCRIBE_DONE.
1212    pub async fn subscribe_done(
1213        &mut self,
1214        request_id: VarInt,
1215        status_code: VarInt,
1216        reason_phrase: Vec<u8>,
1217    ) -> Result<(), ConnectionError> {
1218        let msg = self.endpoint.send_subscribe_done(request_id, status_code, reason_phrase)?;
1219        self.send_control(&msg).await
1220    }
1221
1222    // ── Fetch flow ──────────────────────────────────────────
1223
1224    /// Send a FETCH and return the allocated request ID.
1225    #[allow(clippy::too_many_arguments)]
1226    pub async fn fetch(
1227        &mut self,
1228        track_namespace: TrackNamespace,
1229        track_name: Vec<u8>,
1230        subscriber_priority: u8,
1231        group_order: GroupOrder,
1232        start_group: VarInt,
1233        start_object: VarInt,
1234        end_group: VarInt,
1235        end_object: VarInt,
1236    ) -> Result<VarInt, ConnectionError> {
1237        let (req_id, msg) = self.endpoint.fetch(
1238            track_namespace,
1239            track_name,
1240            subscriber_priority,
1241            group_order,
1242            start_group,
1243            start_object,
1244            end_group,
1245            end_object,
1246        )?;
1247        self.send_control(&msg).await?;
1248        Ok(req_id)
1249    }
1250
1251    /// Send a Relative Joining Fetch and return the allocated request ID.
1252    ///
1253    /// `joining_start` counts groups back from the live edge of the
1254    /// subscription it names. Section 8.13.1 computes the range from "the
1255    /// Preceding Group Offset", which is this field under the name it carried
1256    /// one draft ago; the field list calls it Joining Start and says that for
1257    /// a Relative Joining Fetch "a value of 0 indicates the Fetch starts at
1258    /// the beginning of the Current Group". For the form that names the group
1259    /// outright, see
1260    /// [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1261    ///
1262    /// The subscription is named by `joining_subscribe_id`, which is the
1263    /// field's name in this draft even though the draft describes it as "The
1264    /// Request ID of the existing subscription to be joined".
1265    pub async fn joining_fetch(
1266        &mut self,
1267        subscriber_priority: u8,
1268        group_order: GroupOrder,
1269        joining_subscribe_id: VarInt,
1270        joining_start: VarInt,
1271    ) -> Result<VarInt, ConnectionError> {
1272        let (req_id, msg) = self.endpoint.joining_fetch(
1273            subscriber_priority,
1274            group_order,
1275            joining_subscribe_id,
1276            joining_start,
1277        )?;
1278        self.send_control(&msg).await?;
1279        Ok(req_id)
1280    }
1281
1282    /// Send an Absolute Joining Fetch and return the allocated request ID.
1283    ///
1284    /// `joining_start` is the group to begin at. Section 8.13 has an Absolute
1285    /// Joining Fetch "Identical to a Relative Joining Fetch except that the
1286    /// Start Group is determined by an absolute Group value rather than a
1287    /// relative offset to the subscription", so a subscriber that knows the
1288    /// group it wants can ask for it without first being told a Largest Group
1289    /// to count back from.
1290    ///
1291    /// Two calls rather than one taking a Fetch Type, because these are the
1292    /// only two the joining form admits. The endpoint is named the same way,
1293    /// so no call between an application and the wire has a Fetch Type in it
1294    /// to be given a wrong one.
1295    pub async fn absolute_joining_fetch(
1296        &mut self,
1297        subscriber_priority: u8,
1298        group_order: GroupOrder,
1299        joining_subscribe_id: VarInt,
1300        joining_start: VarInt,
1301    ) -> Result<VarInt, ConnectionError> {
1302        let (req_id, msg) = self.endpoint.absolute_joining_fetch(
1303            subscriber_priority,
1304            group_order,
1305            joining_subscribe_id,
1306            joining_start,
1307        )?;
1308        self.send_control(&msg).await?;
1309        Ok(req_id)
1310    }
1311
1312    /// Send a FETCH_CANCEL for the given request ID.
1313    pub async fn fetch_cancel(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1314        let msg = self.endpoint.fetch_cancel(request_id)?;
1315        self.send_control(&msg).await
1316    }
1317
1318    /// Accept a fetch the peer opened, sending FETCH_OK.
1319    ///
1320    /// The endpoint refuses a Joining Fetch naming a subscription this session
1321    /// cannot join and refuses a second answer to one FETCH, so nothing is
1322    /// written on the wire when it does either.
1323    pub async fn fetch_ok(
1324        &mut self,
1325        request_id: VarInt,
1326        group_order: GroupOrder,
1327        end_of_track: u8,
1328        end_location: Location,
1329        parameters: Vec<KeyValuePair>,
1330    ) -> Result<(), ConnectionError> {
1331        let msg = self.endpoint.send_fetch_ok(
1332            request_id,
1333            group_order,
1334            end_of_track,
1335            end_location,
1336            parameters,
1337        )?;
1338        self.send_control(&msg).await
1339    }
1340
1341    /// Refuse a fetch the peer opened, sending FETCH_ERROR.
1342    ///
1343    /// The endpoint refuses a second answer to one FETCH, and refuses a
1344    /// Joining Fetch's refusal under any code but the one the draft names for
1345    /// it, so nothing is written on the wire when it does either.
1346    pub async fn fetch_error(
1347        &mut self,
1348        request_id: VarInt,
1349        error_code: VarInt,
1350        reason_phrase: Vec<u8>,
1351    ) -> Result<(), ConnectionError> {
1352        let msg = self.endpoint.send_fetch_error(request_id, error_code, reason_phrase)?;
1353        self.send_control(&msg).await
1354    }
1355
1356    // ── Namespace flows ─────────────────────────────────────
1357
1358    /// Send a SUBSCRIBE_ANNOUNCES. Returns the allocated request ID.
1359    pub async fn subscribe_announces(
1360        &mut self,
1361        track_namespace_prefix: TrackNamespace,
1362    ) -> Result<VarInt, ConnectionError> {
1363        let (req_id, msg) = self.endpoint.subscribe_announces(track_namespace_prefix)?;
1364        self.send_control(&msg).await?;
1365        Ok(req_id)
1366    }
1367
1368    /// Accept a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_OK.
1369    ///
1370    /// The endpoint refuses a second answer to one SUBSCRIBE_ANNOUNCES, so nothing is
1371    /// written on the wire when it does.
1372    pub async fn subscribe_announces_ok(
1373        &mut self,
1374        request_id: VarInt,
1375    ) -> Result<(), ConnectionError> {
1376        let msg = self.endpoint.send_subscribe_announces_ok(request_id)?;
1377        self.send_control(&msg).await
1378    }
1379
1380    /// Refuse a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_ERROR.
1381    ///
1382    /// The other half of the same sentence: one answer, and this is the other
1383    /// one it can be.
1384    pub async fn subscribe_announces_error(
1385        &mut self,
1386        request_id: VarInt,
1387        error_code: VarInt,
1388        reason_phrase: Vec<u8>,
1389    ) -> Result<(), ConnectionError> {
1390        let msg =
1391            self.endpoint.send_subscribe_announces_error(request_id, error_code, reason_phrase)?;
1392        self.send_control(&msg).await
1393    }
1394
1395    /// Send an ANNOUNCE. Returns the allocated request ID.
1396    pub async fn announce(
1397        &mut self,
1398        track_namespace: TrackNamespace,
1399    ) -> Result<VarInt, ConnectionError> {
1400        let (req_id, msg) = self.endpoint.announce(track_namespace)?;
1401        self.send_control(&msg).await?;
1402        Ok(req_id)
1403    }
1404
1405    /// Send an UNANNOUNCE.
1406    pub async fn unannounce(
1407        &mut self,
1408        track_namespace: TrackNamespace,
1409    ) -> Result<(), ConnectionError> {
1410        let msg = self.endpoint.unannounce(track_namespace)?;
1411        self.send_control(&msg).await
1412    }
1413
1414    /// Accept an announcement the peer made, sending ANNOUNCE_OK.
1415    ///
1416    /// The endpoint refuses a second answer to one ANNOUNCE, so nothing is
1417    /// written on the wire when it does.
1418    pub async fn announce_ok(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1419        let msg = self.endpoint.send_announce_ok(request_id)?;
1420        self.send_control(&msg).await
1421    }
1422
1423    /// Refuse an announcement the peer made, sending ANNOUNCE_ERROR.
1424    ///
1425    /// The other half of the same sentence: one answer, and this is the other
1426    /// one it can be.
1427    pub async fn announce_error(
1428        &mut self,
1429        request_id: VarInt,
1430        error_code: VarInt,
1431        reason_phrase: Vec<u8>,
1432    ) -> Result<(), ConnectionError> {
1433        let msg = self.endpoint.send_announce_error(request_id, error_code, reason_phrase)?;
1434        self.send_control(&msg).await
1435    }
1436
1437    /// Revoke an acceptance, sending ANNOUNCE_CANCEL.
1438    ///
1439    /// The endpoint refuses one for an announcement it never accepted, so
1440    /// nothing is written on the wire when it does.
1441    pub async fn announce_cancel(
1442        &mut self,
1443        track_namespace: TrackNamespace,
1444        error_code: VarInt,
1445        reason_phrase: Vec<u8>,
1446    ) -> Result<(), ConnectionError> {
1447        let msg = self.endpoint.announce_cancel(track_namespace, error_code, reason_phrase)?;
1448        self.send_control(&msg).await
1449    }
1450    // ── Track Status flow ────────────────────────────────────
1451
1452    /// Send a TRACK_STATUS_REQUEST. Returns the allocated request ID.
1453    pub async fn track_status_request(
1454        &mut self,
1455        track_namespace: TrackNamespace,
1456        track_name: Vec<u8>,
1457    ) -> Result<VarInt, ConnectionError> {
1458        let (req_id, msg) = self.endpoint.track_status_request(track_namespace, track_name)?;
1459        self.send_control(&msg).await?;
1460        Ok(req_id)
1461    }
1462
1463    /// Answer a TRACK_STATUS_REQUEST the peer sent, sending TRACK_STATUS.
1464    ///
1465    /// The endpoint refuses a second answer to one request, so nothing is
1466    /// written on the wire when it does.
1467    pub async fn track_status(
1468        &mut self,
1469        request_id: VarInt,
1470        status_code: VarInt,
1471        largest_location: Location,
1472        parameters: Vec<KeyValuePair>,
1473    ) -> Result<(), ConnectionError> {
1474        let msg = self.endpoint.send_track_status(
1475            request_id,
1476            status_code,
1477            largest_location,
1478            parameters,
1479        )?;
1480        self.send_control(&msg).await
1481    }
1482
1483    // ── Data streams ────────────────────────────────────────
1484
1485    /// Open a new unidirectional stream for sending subgroup data.
1486    pub async fn open_subgroup_stream(
1487        &self,
1488        header: &AnySubgroupHeader,
1489    ) -> Result<FramedSendStream, ConnectionError> {
1490        // Before the stream is opened: the Original Publisher is who the rule
1491        // binds, so a header that would mix this track's framing is refused
1492        // here rather than written and answered by the peer.
1493        self.endpoint.note_object_forwarding_preference(
1494            header.track_alias(),
1495            ObjectForwardingPreference::Subgroup,
1496        )?;
1497        let send = self.transport.open_uni().await?;
1498        let mut framed = FramedSendStream::new(send);
1499        let sid = framed.stream_id();
1500        framed.write_subgroup_header(header).await?;
1501        self.emit(ClientEvent::StreamOpened {
1502            direction: Direction::Send,
1503            stream_kind: StreamKind::Subgroup,
1504            stream_id: sid,
1505        });
1506        self.emit(ClientEvent::DataStreamHeader {
1507            stream_id: sid,
1508            direction: Direction::Send,
1509            header: header.clone(),
1510        });
1511        Ok(framed)
1512    }
1513
1514    /// Open a new unidirectional stream for sending a FETCH's objects.
1515    ///
1516    /// The objects answering a FETCH do not go on the request's own stream:
1517    /// they go on a unidirectional stream of their own, which opens with a
1518    /// FETCH_HEADER naming the request they belong to. This writes that header
1519    /// and hands back the stream, the same way
1520    /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
1521    /// subgroup.
1522    ///
1523    /// The caller owns the stream that comes back. Nothing here remembers
1524    /// which request it belongs to, so an endpoint serving several fetches at
1525    /// once keeps its own map from Request ID to stream.
1526    pub async fn open_fetch_stream(
1527        &self,
1528        header: &AnyFetchHeader,
1529    ) -> Result<FramedSendStream, ConnectionError> {
1530        let send = self.transport.open_uni().await?;
1531        let mut framed = FramedSendStream::new(send);
1532        let sid = framed.stream_id();
1533        framed.write_fetch_header(header).await?;
1534        self.emit(ClientEvent::StreamOpened {
1535            direction: Direction::Send,
1536            stream_kind: StreamKind::Fetch,
1537            stream_id: sid,
1538        });
1539        Ok(framed)
1540    }
1541
1542    /// Accept the next unidirectional stream and read its fetch header.
1543    ///
1544    /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
1545    /// are separate because the header decides how every object after it is
1546    /// framed, so a caller has to know which it is expecting before the first
1547    /// byte is read.
1548    ///
1549    /// Objects come off the returned stream with
1550    /// [`FramedRecvStream::read_fetch_object`].
1551    pub async fn accept_fetch_stream(
1552        &self,
1553    ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
1554        let recv = self.transport.accept_uni().await?;
1555        let mut framed = FramedRecvStream::new(recv);
1556        let sid = framed.stream_id();
1557        let header = framed.read_fetch_header().await?;
1558        self.emit(ClientEvent::StreamOpened {
1559            direction: Direction::Receive,
1560            stream_kind: StreamKind::Fetch,
1561            stream_id: sid,
1562        });
1563        self.emit(ClientEvent::FetchStreamHeader {
1564            stream_id: sid,
1565            direction: Direction::Receive,
1566            header: header.clone(),
1567        });
1568        // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
1569        // carries an `AnySubgroupHeader` and cannot express one. What
1570        // `accept_subgroup_stream` does beyond this - the forwarding-preference
1571        // note, the object measurement - is about a subgroup and has no
1572        // counterpart on a fetch stream.
1573        Ok((header, framed))
1574    }
1575
1576    /// Accept an incoming unidirectional data stream and read its subgroup
1577    /// header.
1578    pub async fn accept_subgroup_stream(
1579        &self,
1580    ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
1581        let recv = self.transport.accept_uni().await?;
1582        let mut framed = FramedRecvStream::new(recv);
1583        let sid = framed.stream_id();
1584        let header = framed.read_subgroup_header().await?;
1585        self.emit(ClientEvent::StreamOpened {
1586            direction: Direction::Receive,
1587            stream_kind: StreamKind::Subgroup,
1588            stream_id: sid,
1589        });
1590        self.emit(ClientEvent::DataStreamHeader {
1591            stream_id: sid,
1592            direction: Direction::Receive,
1593            header: header.clone(),
1594        });
1595        // Every object on a subgroup stream has the Subgroup preference, so
1596        // the header settles the track's framing before a single object is
1597        // read.
1598        self.endpoint.note_object_forwarding_preference(
1599            header.track_alias(),
1600            ObjectForwardingPreference::Subgroup,
1601        )?;
1602        // The track is resolved here and not inside the stream: it takes the
1603        // endpoint's alias table, which a stream handle has no way back to.
1604        if let Some(objects) = self.endpoint.track_objects(header.track_alias()) {
1605            framed.measure_objects_against(objects, header.group_id());
1606        }
1607        Ok((header, framed))
1608    }
1609
1610    /// Send an object via datagram.
1611    ///
1612    /// The header goes through `AnyDatagramHeader::encode`, which refuses a
1613    /// header whose Object Status the framing it names cannot carry. Such a
1614    /// header errors here and nothing is sent, rather than going out as an
1615    /// ordinary payload datagram with the status quietly dropped.
1616    pub fn send_datagram(
1617        &self,
1618        header: &AnyDatagramHeader,
1619        payload: &[u8],
1620    ) -> Result<(), ConnectionError> {
1621        // Before anything is encoded, for the reason `open_subgroup_stream`
1622        // gives.
1623        self.endpoint.note_object_forwarding_preference(
1624            header.meta().track_alias,
1625            ObjectForwardingPreference::Datagram,
1626        )?;
1627        let mut buf = Vec::new();
1628        header.encode(&mut buf)?;
1629        buf.extend_from_slice(payload);
1630        self.emit(ClientEvent::DatagramReceived {
1631            direction: Direction::Send,
1632            header: header.clone(),
1633            payload_len: payload.len(),
1634        });
1635        self.transport.send_datagram(bytes::Bytes::from(buf))?;
1636        Ok(())
1637    }
1638
1639    /// Receive a datagram and decode it whole.
1640    ///
1641    /// An extension block announced with a length of 0 is returned as
1642    /// [`ConnectionError::EmptyDatagramExtensions`], which
1643    /// [`Connection::close_for_data_stream`] closes the session over.
1644    ///
1645    /// The codec's `Datagram::decode_object` then reads the whole datagram, and its
1646    /// refusals are returned as [`ConnectionError::Codec`]: an extension block that
1647    /// does not parse, a payload after a status, and a Prior Group ID Gap larger
1648    /// than the Group ID, which draft-11 Section 10.1 calls a malformed track. This
1649    /// draft has no Malformed Tracks section and names no answer, so nothing is
1650    /// withdrawn and the session stays open.
1651    pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
1652        let data = self.transport.recv_datagram().await?;
1653        let mut cursor = &data[..];
1654        let header = match AnyDatagramHeader::decode(DraftVersion::Draft11, &mut cursor) {
1655            Ok(header) => header,
1656            Err(CodecError::InvalidField) => {
1657                return Err(empty_datagram_extensions(&data)
1658                    .unwrap_or(ConnectionError::Codec(CodecError::InvalidField)));
1659            }
1660            Err(err) => return Err(err.into()),
1661        };
1662        let consumed = data.len() - cursor.len();
1663        let payload = data.slice(consumed..);
1664        self.emit(ClientEvent::DatagramReceived {
1665            direction: Direction::Receive,
1666            header: header.clone(),
1667            payload_len: payload.len(),
1668        });
1669        moqtap_codec::draft11::data_stream::Datagram::decode_object(&mut &data[..])?;
1670        // A datagram is the other framing, and it settles the track's just as a
1671        // subgroup header does.
1672        self.endpoint.note_object_forwarding_preference(
1673            header.meta().track_alias,
1674            ObjectForwardingPreference::Datagram,
1675        )?;
1676        // A datagram is a whole object, so the connection can measure it
1677        // without help from the caller.
1678        let meta = header.meta();
1679        self.endpoint.note_received_object(
1680            meta.track_alias,
1681            ObjectLocation { group: meta.group_id, object: meta.object_id },
1682            object_role(meta.status),
1683        )?;
1684        Ok((header, payload))
1685    }
1686
1687    // ── Accessors ───────────────────────────────────────────
1688
1689    /// Access the underlying endpoint state machine.
1690    pub fn endpoint(&self) -> &Endpoint {
1691        &self.endpoint
1692    }
1693
1694    /// Mutable access to the endpoint state machine.
1695    pub fn endpoint_mut(&mut self) -> &mut Endpoint {
1696        &mut self.endpoint
1697    }
1698
1699    /// The SETUP message the server answered the handshake with.
1700    ///
1701    /// `SERVER_SETUP` through draft-16, the server's half of the unified
1702    /// `SETUP` from draft-17. [`AnyControlMessage::fields`] renders it under
1703    /// this draft's own parameter names, in the order they arrived.
1704    pub fn server_setup(&self) -> &AnyControlMessage {
1705        &self.server_setup
1706    }
1707
1708    /// The framed wire bytes of [`Self::server_setup`], as they arrived.
1709    ///
1710    /// Kept beside the decoded form because the encoding is evidence the
1711    /// decoding discards: two relays sending the same parameter can still
1712    /// disagree on how wide a varint they wrote it in.
1713    pub fn server_setup_raw(&self) -> Option<&[u8]> {
1714        self.server_setup_raw.as_deref()
1715    }
1716
1717    /// Get the negotiated MoQT version.
1718    pub fn negotiated_version(&self) -> Option<VarInt> {
1719        self.endpoint.negotiated_version()
1720    }
1721
1722    /// Which of this draft's *own* `ConnectionError` variants this error is,
1723    /// and which kind of thing it says.
1724    ///
1725    /// The ten every draft carries answer `None` here: [`AnyConnectionError`]
1726    /// classifies those itself. Draft-11 adds two:
1727    /// [`ConnectionError::ControlMessageNarrowing`], a defect in this build, and
1728    /// [`ConnectionError::EmptyDatagramExtensions`], a decoder refusal the draft
1729    /// answers with a close.
1730    ///
1731    /// Matched exhaustively, with no wildcard arm and deliberately so: a
1732    /// variant added to this draft's error type has to arrive here as a compile
1733    /// error, beside the doc comment that quotes the sentence it enforces,
1734    /// rather than as a silent [`ErrorCause::Unclassified`] in the facade.
1735    ///
1736    /// [`AnyConnectionError`]: crate::dispatch::AnyConnectionError
1737    /// [`ErrorCause::Unclassified`]: crate::dispatch::ErrorCause::Unclassified
1738    pub fn draft_specific_cause(
1739        err: &ConnectionError,
1740    ) -> Option<crate::above_codec_rules::DraftSpecificCause> {
1741        match err {
1742            ConnectionError::Endpoint(_)
1743            | ConnectionError::Codec(_)
1744            | ConnectionError::Transport(_)
1745            | ConnectionError::VarInt(_)
1746            | ConnectionError::NoControlStream
1747            | ConnectionError::UnexpectedEnd
1748            | ConnectionError::StreamFinished
1749            | ConnectionError::InvalidAddress(_)
1750            | ConnectionError::TlsConfig(_)
1751            | ConnectionError::DataStreamState(_) => None,
1752            // This build decoding a message and then failing to narrow it to
1753            // its own draft. Nothing reached the wire and no peer is
1754            // implicated, which is the whole reason it is not
1755            // `ConnectionError::Codec`: under that name it would carry
1756            // `Some(PROTOCOL_VIOLATION)` out of `codec_session_error_code` and
1757            // publish a relay for this build's defect. See the variant's own
1758            // doc.
1759            ConnectionError::ControlMessageNarrowing => {
1760                Some(crate::above_codec_rules::DraftSpecificCause::LocalRefusal)
1761            }
1762            // The codec refuses the datagram as a bare `InvalidField`, and
1763            // `recv_datagram` names the rule, so `close_for_data_stream` reads
1764            // the close here. See the variant's own doc.
1765            ConnectionError::EmptyDatagramExtensions { .. } => {
1766                Some(crate::above_codec_rules::DraftSpecificCause::Codec {
1767                    error: CodecError::InvalidField,
1768                    close: Some(
1769                        moqtap_codec::draft11::error_codes::SessionErrorCode::ProtocolViolation
1770                            .as_u64(),
1771                    ),
1772                })
1773            }
1774        }
1775    }
1776
1777    /// The code to close the session with when a message could not be decoded
1778    /// because the peer broke a rule draft-11 answers with a close.
1779    ///
1780    /// Every variant listed here comes from a sentence in this draft that names
1781    /// the consequence, and the list is per draft: answering a bound this draft
1782    /// does not state would close a session over traffic a conforming peer may
1783    /// send.
1784    ///
1785    ///   - Reason Phrase, maximum 1024 bytes: "If an endpoint receives a length
1786    ///     exceeding the maximum, it MUST close the session with a Protocol
1787    ///     Violation."
1788    ///   - GOAWAY New Session URI, maximum 8,192 bytes, with the same sentence.
1789    ///     Drafts 11 through 19 state it; 07 through 10 state no maximum for
1790    ///     the field at all.
1791    ///   - Key-Value-Pair value, maximum 2^16-1 bytes, with the same sentence.
1792    ///   - Track Namespace tuple size: "If an endpoint receives a Track
1793    ///     Namespace tuple with an N of 0 or more than 32, it MUST close the
1794    ///     session with a Protocol Violation." Note the lower bound - an empty
1795    ///     tuple is refused here, where drafts 17 and later permit one.
1796    ///   - Full Track Name, maximum 4,096 bytes, "computed as the sum of the
1797    ///     lengths of each Track Namespace tuple field and the Track Name
1798    ///     length field". This draft bounds the pair and not the namespace
1799    ///     alone; draft-16 widened it.
1800    ///   - Duplicate parameters, a SHOULD rather than a MUST: "Receivers SHOULD
1801    ///     check that there are no unauthorized duplicate parameters and close
1802    ///     the session as a 'Protocol Violation' if found." The rule is
1803    ///     asymmetric here - "Receivers MUST allow duplicates of unknown
1804    ///     parameters", and one known type is granted repeats - and the codec
1805    ///     reports only the repeats this draft actually forbids.
1806    ///   - Unknown control message type: "An endpoint that receives an unknown
1807    ///     message type MUST close the session."
1808    ///   - Extension headers on an Object whose status is Object Does Not
1809    ///     Exist, Section 9.1.1.2. That one arrives on a data stream or a
1810    ///     datagram, so [`Connection::close_for_data_stream`] is what carries
1811    ///     it.
1812    ///
1813    /// **Not** the zero-length Track Namespace Field, the delta-encoded
1814    /// parameter type overflow, or the Object ID delta wrap. Those enter the
1815    /// specification at drafts 16 and 18, and this draft states none of them.
1816    /// **Not** the 2^16-1 control message length either. This draft states the
1817    /// limit - "the total length of a control message is limited to 2^16-1" -
1818    /// and states no consequence for exceeding it, so an oversized message is
1819    /// refused by the decoder and stops there.
1820    ///
1821    /// **Not** [`CodecError::UnexpectedEnd`], which reports no rule at all: the
1822    /// reader raises it whenever a message is still arriving, and
1823    /// `read_control` loops on it. Closing over it would end a session on an
1824    /// ordinary short read.
1825    ///
1826    /// **Not** the unknown Message Parameter rule. Drafts 16 through 19 require
1827    /// a close for a Message Parameter whose type the negotiated version does
1828    /// not define. This draft states the opposite and states it about the same
1829    /// parameters: "Receivers MUST allow duplicates of unknown parameters",
1830    /// which presumes an unknown parameter arrives and is carried. Refusing one
1831    /// here would close a session over an extension this draft leaves room for.
1832    ///
1833    /// `None` for everything else, including [`CodecError::InvalidField`]. That
1834    /// variant is shared by a dozen unrelated malformations, only some of which
1835    /// the draft answers with a close, so a session cannot be ended on it
1836    /// without ending sessions the draft does not ask to be ended. Splitting it
1837    /// is the way to bring the rest of those rules under this function;
1838    /// widening the match is not - the extension-header rule above is in this
1839    /// table because it has a variant of its own.
1840    pub fn codec_session_error_code(
1841        err: &CodecError,
1842    ) -> Option<moqtap_codec::draft11::error_codes::SessionErrorCode> {
1843        use moqtap_codec::draft11::error_codes::SessionErrorCode;
1844        use moqtap_codec::kvp::KvpError;
1845        match err {
1846            // The declared Length disagreeing with the fields, which every
1847            // draft answers with a close. Drafts 07 through 10 name no code for
1848            // it, so it takes the one their other unnamed rules take.
1849            CodecError::ControlMessageLengthMismatch { .. } => {
1850                Some(SessionErrorCode::ProtocolViolation)
1851            }
1852            CodecError::ReasonPhraseTooLong
1853            | CodecError::GoAwayUriTooLong
1854            | CodecError::InvalidNamespaceTupleSize(_)
1855            | CodecError::TrackNameTooLong
1856            | CodecError::DuplicateParameter(_)
1857            | CodecError::UnknownMessageType(_)
1858            | CodecError::ExtensionsOnNonExistentObject(_)
1859            | CodecError::Kvp(KvpError::ValueTooLong(_)) => {
1860                Some(SessionErrorCode::ProtocolViolation)
1861            }
1862            // An unknown data-plane type, Section 9: "An endpoint that
1863            // receives an unknown stream or datagram type MUST close the
1864            // session." One sentence covering two tables, which is why both
1865            // variants sit here.
1866            // A Content Exists field that is neither zero nor one,
1867            // Section 8.8: "Any other value is a protocol error and
1868            // MUST terminate the session with a Protocol Violation".
1869            CodecError::InvalidContentExists(_) => Some(SessionErrorCode::ProtocolViolation),
1870            // A Forward field that is neither zero nor one, Section 8.7 and
1871            // Section 8.10: "Any other value is a protocol error and MUST
1872            // terminate the session with a Protocol Violation". Draft-11 is
1873            // where the field arrives, and it carries only these two sites;
1874            // PUBLISH and PUBLISH_OK add two more from draft-12.
1875            CodecError::InvalidForward(_) => Some(SessionErrorCode::ProtocolViolation),
1876            CodecError::UnknownStreamType(_) | CodecError::UnknownDatagramType(_) => {
1877                Some(SessionErrorCode::ProtocolViolation)
1878            }
1879            // A key-value pair whose value is not the serialization its own
1880            // Type defines, Section 1.3.2: "If a receiver understands a Type,
1881            // and the following Value or Length/Value does not match the
1882            // serialization defined by that Type, the receiver MUST terminate the
1883            // session with error code 'Key-Value Formatting Error'."
1884            //
1885            // This draft states the general rule alone. The Authorization Token
1886            // structure it defines gets its own sentence only from draft-12 on,
1887            // and a token that cannot be decoded is answered here either way.
1888            //
1889            // The one rule in this table that names a code other than Protocol
1890            // Violation.
1891            CodecError::KeyValueFormatting { .. } => {
1892                Some(SessionErrorCode::KeyValueFormattingError)
1893            }
1894            // Everything this draft does not answer, named rather than swept up
1895            // by a wildcard. The arm is exhaustive deliberately: a new
1896            // `CodecError` variant will not compile until it has been placed on
1897            // one side or the other, on this draft, which is the decision a `_`
1898            // arm makes silently and invisibly in every draft module at once.
1899            //
1900            // Adding one variant to `CodecError` produces an `E0004` in every
1901            // draft module that matches it exhaustively, each naming the
1902            // variant that has nowhere to go. That is the whole mechanism.
1903            //
1904            // The nesting stops at `VarInt`, whose variants report how the bytes
1905            // ran out rather than a rule an endpoint states, so there is nothing
1906            // in it for a draft to answer. `Kvp` is spelled out because it does
1907            // carry one.
1908            // Not `ParameterValueOutOfRange`: no parameter this draft defines
1909            // restricts its value's range. Forwarding is a message field here
1910            // and is answered above.
1911            CodecError::ParameterValueOutOfRange { .. }
1912            | CodecError::UnexpectedEnd
1913            | CodecError::MessageTooLong(_)
1914            | CodecError::VarInt(_)
1915            | CodecError::InvalidField
1916            | CodecError::EmptyNamespaceField
1917            | CodecError::InvalidRange(..)
1918            | CodecError::ParameterLengthMismatch(_)
1919            | CodecError::EndOfTrackObjectId(_)
1920            | CodecError::KeyDeltaOverflow(..)
1921            // Not `TrackPropertyValueOutOfRange`: this draft has neither
1922            // namespace the variant is about. Draft-16 opens an extension header
1923            // registry with value rules of its own, and draft-17 renames it to
1924            // the Track Property registry. Before that, everything with a
1925            // restricted range is either a message field or a Message Parameter.
1926            | CodecError::TrackPropertyValueOutOfRange { .. }
1927            | CodecError::ParametersOutOfOrder(..)
1928            | CodecError::ObjectIdOverflow(..)
1929            | CodecError::InvalidRequiredRequestIdDelta(..)
1930            | CodecError::InvalidStreamTypeValue { .. }
1931            | CodecError::InvalidDatagramTypeValue { .. }
1932            | CodecError::UnknownMessageParameter(_)
1933            // Not `ParameterOutOfScope`: this draft states the scope rule and
1934            // answers it the other way. Section 8.2.1 Version Specific Parameters: "Each
1935            // version-specific parameter definition indicates the message types in which it can
1936            // appear. If it appears in some other type of message, it MUST be
1937            // ignored." The codec carries such a parameter on this draft and never
1938            // raises the variant, so this arm records a rule this draft has and
1939            // does not close over, not one it is missing. Draft-17 is where the
1940            // second sentence becomes a close.
1941            | CodecError::ParameterOutOfScope { .. }
1942            // A Filter Type outside the set this draft assigns. Section 8.7
1943            // states the rule and stops there: "A filter type other than the
1944            // above MUST be treated as error." No code, no close, and no
1945            // sentence elsewhere in the draft that turns an error into one — so
1946            // the message is refused and the session stays open.
1947            // Draft-14 Section 9.7 is where the same sentence gained "MUST be
1948            // close the session with PROTOCOL_VIOLATION", and it is answered
1949            // there.
1950            //
1951            // The assigned set is not the same on every draft either: 07 and 08
1952            // assign 0x1 as Latest Group, 09 and 10 withdraw it, and 11 and
1953            // later reinstate it as Next Group Start. The decoder holds each
1954            // draft to its own list; this arm only decides what a refusal does
1955            // to the session.
1956            //
1957            // The two rules below belong to the parameter form of the filter,
1958            // which arrives at draft-15. This draft carries the Filter Type as a
1959            // field of SUBSCRIBE, so there is no parameter for either to be
1960            // about.
1961            | CodecError::InvalidFilterType(_)
1962            | CodecError::SubscriptionFilterMalformed { .. }
1963            | CodecError::FilterEndGroupOverflow { .. }
1964            // A Fetch Type outside the set this draft assigns. Section 8.13
1965            // states the rule and stops there: "A Fetch Type other than 0x1,
1966            // 0x2 or 0x3 MUST be treated as an error", naming no code and no
1967            // close, exactly as this draft's Filter Type sentence does.
1968            // Draft-14 Section 9.16 is where both gained "MUST be close the
1969            // session with a PROTOCOL_VIOLATION", and it answers them there.
1970            | CodecError::InvalidFetchType(_)
1971            // The object payload rule, Section 9.1.1.1: "Any object with a status
1972            // code other than zero MUST have an empty payload." A MUST on the
1973            // sender with no receiver action named anywhere — the "SHOULD be
1974            // treated as a protocol error" in the same paragraph belongs to the
1975            // sentence before it, which is about a status value this draft does
1976            // not assign — so an object carrying a payload it may not is refused
1977            // and the session stays open.
1978            | CodecError::PayloadNotPermitted { .. }
1979            | CodecError::MalformedObjectProperties { .. }
1980            // draft-11 Section 10.1 calls a track with a Prior Group ID Gap
1981            // fault malformed and names no close, so the session stays open.
1982            | CodecError::MalformedTrack { .. }
1983            // Raised only by the draft-23 codec.
1984            | CodecError::ControlMessageExceedsLimit { .. }
1985            | CodecError::MissingErrorDetail { .. }
1986            | CodecError::MalformedErrorDetail { .. }
1987            | CodecError::TokenAliasInSetup { .. }
1988            | CodecError::EmptyTrackName
1989            | CodecError::MalformedAuthToken { .. }
1990            | CodecError::TrackPropertiesNotPermitted
1991            | CodecError::DeltaDecodingOverflow { .. }
1992            | CodecError::MalformedObject { .. }
1993            | CodecError::UnknownObjectStatus(_)
1994            | CodecError::UnsupportedDraft(_)
1995            | CodecError::Kvp(
1996                KvpError::MissingLength | KvpError::UnexpectedEnd | KvpError::VarInt(_),
1997            ) => None,
1998        }
1999    }
2000
2001    /// Close the session on the wire when a decode failure is one draft-11
2002    /// answers with a close, and hand the error back unchanged.
2003    /// Without it every bound the decoder enforces would stop at *this endpoint
2004    /// refused the frame* while the peer, which is the one that broke the rule,
2005    /// saw a session that was still open and went on sending. "MUST close the
2006    /// session" is a statement about the wire.
2007    fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
2008        if let ConnectionError::Codec(inner) = &err {
2009            if let Some(code) = Self::codec_session_error_code(inner) {
2010                // QUIC application error codes are 62-bit; every code in this
2011                // registry is far below `u32::MAX`, and saturating rather than
2012                // truncating means a future code that is not could never be
2013                // reported as a different, assigned one.
2014                let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2015                self.close(wire_code, inner.to_string().as_bytes());
2016            }
2017        }
2018        err
2019    }
2020
2021    /// Close the session on the wire when the endpoint says a violation is
2022    /// fatal to it, and hand the error back unchanged.
2023    ///
2024    /// [`EndpointError::session_error_code`] answers `Some` for exactly the
2025    /// errors this draft ends the session over, and the endpoint has already
2026    /// moved its own state machine to Closed by the time this runs. Without
2027    /// this step that move is purely internal: the local endpoint refuses to
2028    /// start anything new while the peer, which is the one that broke the
2029    /// rule, sees a session that is still open and goes on sending. A rule
2030    /// that names a session termination code is a statement about the wire,
2031    /// so it takes a CONNECTION_CLOSE to satisfy it.
2032    ///
2033    /// The reason phrase is the error's own `Display` text, which names the
2034    /// rule rather than repeating the numeric code the close already carries.
2035    ///
2036    /// Errors that answer `None` are recoverable and nothing is sent.
2037    fn close_for(&self, err: &EndpointError) {
2038        if let Some(code) = err.session_error_code() {
2039            // QUIC application error codes are 62-bit; every code in this
2040            // registry is far below `u32::MAX`, and saturating rather than
2041            // truncating means a future code that is not could never be
2042            // reported as a different, assigned one.
2043            let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2044            self.close(wire_code, err.to_string().as_bytes());
2045        }
2046    }
2047
2048    /// [`close_for`](Self::close_for), then the error unchanged, for the
2049    /// common case where the endpoint's error is also what the caller returns.
2050    fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
2051        self.close_for(&err);
2052        ConnectionError::Endpoint(err)
2053    }
2054
2055    /// Close the session over a rule broken on a data stream, reporting whether
2056    /// it did.
2057    ///
2058    /// A data stream cannot close for itself the way `recv_control` does:
2059    /// [`Connection::accept_subgroup_stream`] hands the caller a
2060    /// [`FramedRecvStream`] holding no connection, so the reader that finds the
2061    /// violation is not the object that can act on it. Keeping it a separate
2062    /// call is deliberate as well - a permissive caller, one reproducing a
2063    /// capture, can read a violating stream and report it without tearing the
2064    /// session down.
2065    ///
2066    /// The rule this draft answers here is extension headers on an Object whose
2067    /// status is Object Does Not Exist, which reaches subgroup streams, fetch
2068    /// streams and status datagrams alike. It shares `codec_session_error_code`
2069    /// with the control path, so a rule is answered with one code whichever
2070    /// stream carried it.
2071    ///
2072    /// Not every rule that reaches here is the decoder's. A track whose objects
2073    /// mix forwarding preferences is the endpoint's to notice — it takes the
2074    /// alias table to know which track an object belongs to — and it arrives on
2075    /// exactly these streams. Both kinds are asked for a code the same way, and
2076    /// a rule with no code is declined rather than guessed at.
2077    pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
2078        match err {
2079            ConnectionError::Codec(inner) => {
2080                let Some(code) = Self::codec_session_error_code(inner) else { return false };
2081                let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2082                self.close(wire_code, inner.to_string().as_bytes());
2083                true
2084            }
2085            // A rule the endpoint raises rather than the decoder. The two
2086            // reach their codes through different tables and mean the same
2087            // thing here: `Some` is a rule this draft ends the session over.
2088            ConnectionError::Endpoint(inner) => {
2089                let Some(code) = inner.session_error_code() else { return false };
2090                let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
2091                self.close(wire_code, inner.to_string().as_bytes());
2092                true
2093            }
2094            // Not a `Codec` failure: the codec refuses the datagram as a bare
2095            // `InvalidField`, which `recv_datagram` names, so the code comes
2096            // from `draft_specific_cause`.
2097            ConnectionError::EmptyDatagramExtensions { .. } => {
2098                let Some(crate::above_codec_rules::DraftSpecificCause::Codec {
2099                    close: Some(code),
2100                    ..
2101                }) = Self::draft_specific_cause(err)
2102                else {
2103                    return false;
2104                };
2105                self.close(u32::try_from(code).unwrap_or(u32::MAX), err.to_string().as_bytes());
2106                true
2107            }
2108            _ => false,
2109        }
2110    }
2111
2112    /// Close the connection.
2113    pub fn close(&self, code: u32, reason: &[u8]) {
2114        self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
2115        self.transport.close(code, reason);
2116    }
2117}
2118
2119/// Determine the encoded length of a varint from its first byte.
2120fn varint_len(first_byte: u8) -> usize {
2121    1 << (first_byte >> 6)
2122}
2123
2124#[cfg(test)]
2125mod tests {
2126    use super::*;
2127
2128    /// Only a refusal of what the peer sent is reported as a refused frame,
2129    /// and only with the bytes it was about.
2130    #[test]
2131    fn only_a_refusal_of_the_peers_frame_is_reported() {
2132        let raw = Some(vec![0x01]);
2133        let decoder = ConnectionError::Codec(CodecError::UnexpectedEnd);
2134        assert!(Connection::refused_event(&decoder, raw.clone()).is_some());
2135        assert!(Connection::refused_event(&decoder, None).is_none());
2136        let local = ConnectionError::Endpoint(EndpointError::NotActive);
2137        assert!(Connection::refused_event(&local, raw).is_none());
2138    }
2139
2140    /// This build failing to narrow a message it decoded is never a finding
2141    /// about the peer.
2142    ///
2143    /// The arm that raises `ControlMessageNarrowing` is unreachable — this
2144    /// draft's decoder can only hand back this draft's variant — and nothing
2145    /// pins that. What is pinned here is the half that matters.
2146    /// `CodecError::UnknownMessageType(0)` is what the arm must not raise:
2147    /// `codec_session_error_code` answers it `Some(PROTOCOL_VIOLATION)` on
2148    /// every draft in range, so the day the narrowing failed a conformance
2149    /// probe would publish a relay for sending a control message type this
2150    /// draft does not assign — with `0x00` attached as the codepoint that
2151    /// proved it, which is an accusation better evidenced than any real one
2152    /// this build makes. The section stating that rule is numbered differently
2153    /// on every draft, and the point does not turn on the number.
2154    #[test]
2155    fn a_message_this_build_could_not_narrow_names_nobody() {
2156        use crate::dispatch::{AnyConnectionError, ErrorCause};
2157
2158        let err: AnyConnectionError = ConnectionError::ControlMessageNarrowing.into();
2159        assert!(err.is_local(), "a narrowing this build could not do is this build's");
2160        assert_eq!(
2161            err.cause(),
2162            &ErrorCause::Facade,
2163            "nothing reached the wire, so there is no rule and no close code to read"
2164        );
2165    }
2166
2167    #[test]
2168    fn client_config_supported_versions_default() {
2169        let config = ClientConfig {
2170            additional_versions: Vec::new(),
2171            transport: TransportType::Quic,
2172            skip_cert_verification: false,
2173            ca_certs: Vec::new(),
2174            setup_parameters: Vec::new(),
2175        };
2176        let versions = config.supported_versions();
2177        assert_eq!(versions.len(), 1);
2178        assert_eq!(versions[0].into_inner(), 0xff000000 + 11);
2179    }
2180
2181    #[test]
2182    fn client_config_alpn_quic() {
2183        let config = ClientConfig {
2184            additional_versions: Vec::new(),
2185            transport: TransportType::Quic,
2186            skip_cert_verification: false,
2187            ca_certs: Vec::new(),
2188            setup_parameters: Vec::new(),
2189        };
2190        assert_eq!(config.alpn(), vec![DraftVersion::Draft11.quic_alpn().to_vec()]);
2191    }
2192
2193    #[test]
2194    fn moqt_alpn_value() {
2195        assert_eq!(MOQT_ALPN, b"moq-00");
2196    }
2197}