Skip to main content

moqtap_client/draft18/
connection.rs

1use std::collections::VecDeque;
2use std::sync::Mutex;
3
4use bytes::{Buf, Bytes, BytesMut};
5
6use crate::draft18::endpoint::{Endpoint, EndpointError};
7use crate::draft18::event::{ClientEvent, Direction, StreamKind};
8use crate::draft18::observer::ConnectionObserver;
9use crate::draft18::session::request_id::Role;
10use crate::draft18::session::setup;
11use crate::malformed_tracks::MalformedTrackCondition;
12use crate::track_locations::{ObjectLocation, ObjectRole, TrackObjects};
13use crate::transport::{RecvStream, SendStream, Transport, TransportError};
14use moqtap_codec::dispatch::{
15    AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
16};
17use moqtap_codec::draft18::data_stream::{
18    FetchHeader, FetchObject, FetchObjectHeader, FetchObjectReader, GroupOrder, SubgroupObject,
19    SubgroupObjectReader,
20};
21use moqtap_codec::draft18::error_codes::StreamResetErrorCode;
22use moqtap_codec::draft18::message::{
23    ControlMessage, FetchOk, MessageType, Namespace, NamespaceDone, PublishBlocked, RequestError,
24    RequestOk, SubscribeOk,
25};
26use moqtap_codec::error::CodecError;
27use moqtap_codec::kvp::KeyValuePair;
28use moqtap_codec::types::*;
29use moqtap_codec::varint::VarInt;
30use moqtap_codec::version::DraftVersion;
31
32/// The ALPN identifier draft-18 uses on raw QUIC, `moqt-18`.
33///
34/// Drafts 07 to 14 share one ALPN, `moq-00`, and a peer that offers it has
35/// said nothing about which of the eight it speaks. Draft-15 ended that:
36/// from there each draft has an ALPN of its own, so the version is settled
37/// by the TLS handshake before a byte of MoQT is written.
38///
39/// This is [`DraftVersion::Draft18`]'s own
40/// [`quic_alpn`](DraftVersion::quic_alpn), which is what
41/// [`ClientConfig::alpn`] offers; the test below holds the two together.
42pub const MOQT_ALPN: &[u8] = b"moqt-18";
43
44/// The unidirectional stream type that marks one direction of the control
45/// plane, and the SETUP message type. On draft-18 they are one number,
46/// 0x2F00: Section 3.4 (Unidirectional Stream Types) lists it as the type of
47/// a SETUP stream, and Section 10.3 gives it as the SETUP message's own type
48/// field.
49///
50/// Because they are the same number a control stream carries no separate
51/// stream header. The varint a reader uses to recognise the stream is the
52/// first field of the SETUP message it then decodes, and a writer that
53/// encodes a SETUP onto a fresh unidirectional stream has already written the
54/// stream type by writing the message.
55///
56/// Encoded with MoQT's variable-length integer, whose width is the number of
57/// leading 1 bits in the first byte, 0x2F00 is the two bytes `AF 00` — four
58/// under RFC 9000's encoding, which this draft does not use. Read it through
59/// [`DraftVersion::decode_varint`] rather than assuming a width.
60pub const CONTROL_STREAM_TYPE: u64 = 0x2F00;
61
62/// The application error code a request stream is reset with when the
63/// requester abandons it: `CANCELLED`, 0x1.
64///
65/// Draft-17 removed UNSUBSCRIBE and FETCH_CANCEL and draft-18 keeps them
66/// gone. Cancelling a request is resetting the bidirectional stream it was
67/// made on, and `CANCELLED` is the code draft-18 assigns for "the stream was
68/// cancelled by either endpoint" — see [`StreamResetErrorCode::Cancelled`].
69/// Taken from the codec's own registry rather than written as a literal so a
70/// renumbering in a later draft cannot be missed here.
71///
72/// Draft-18 renamed the registry: draft-17 Section 14.5.4 called it "Data
73/// Stream Reset Error Codes" and draft-18 calls it "Stream Reset Error Codes"
74/// (Section 15.10), widening it to cover request streams explicitly. The value
75/// is unchanged.
76///
77/// This is what [`RequestStream::cancel`] uses when no code is chosen for it,
78/// and what `RequestStream`'s [`Drop`] sends.
79pub const REQUEST_CANCELLED: u64 = StreamResetErrorCode::Cancelled as u64;
80
81/// The application error code a request stream **the peer opened** is reset
82/// with when this endpoint abandons it: `INTERNAL_ERROR`, 0x0.
83///
84/// Dropping an inbound request is not the act [`REQUEST_CANCELLED`] describes.
85/// Draft-18 Section 3.3.2 grants a responder a cancel — "Receivers cancel
86/// requests if they are unable to or choose not to respond" — but a handle
87/// that fell out of scope chose nothing; it failed to serve, which is what
88/// [`StreamResetErrorCode::InternalError`], "an implementation specific error"
89/// in Section 3.3.3, names. The two codes are on the wire, so a peer counting
90/// refusals can tell a deliberate rejection from a dropped request only if they
91/// differ.
92///
93/// It is also what a responder that already answered is reset with when it is
94/// dropped without finishing. A FIN there would claim the request completed,
95/// and for a subscription it has not: PUBLISH_DONE is still owed.
96pub const REQUEST_UNANSWERED: u64 = StreamResetErrorCode::InternalError as u64;
97
98/// Errors from the connection layer.
99#[derive(Debug, thiserror::Error)]
100pub enum ConnectionError {
101    /// Endpoint state machine error.
102    #[error("endpoint error: {0}")]
103    Endpoint(#[from] EndpointError),
104    /// Wire codec error.
105    #[error("codec error: {0}")]
106    Codec(#[from] CodecError),
107    /// Transport-level error.
108    #[error("transport error: {0}")]
109    Transport(#[from] TransportError),
110    /// Variable-length integer decoding error.
111    #[error("varint error: {0}")]
112    VarInt(#[from] moqtap_codec::varint::VarIntError),
113    /// Control stream was not opened.
114    #[error("control stream not open")]
115    NoControlStream,
116    /// Stream ended before a complete message was read.
117    #[error("unexpected end of stream")]
118    UnexpectedEnd,
119    /// Stream was finished by the peer.
120    #[error("stream finished")]
121    StreamFinished,
122    /// Invalid server address string.
123    #[error("invalid server address: {0}")]
124    InvalidAddress(String),
125    /// TLS configuration error.
126    #[error("TLS config error: {0}")]
127    TlsConfig(String),
128    /// Data stream used out of order (e.g. object before header).
129    #[error("data stream state error: {0}")]
130    DataStreamState(&'static str),
131    /// A control message this build decoded for draft-18 and then could not
132    /// narrow to draft-18's own message type.
133    ///
134    /// Unreachable, and that is not the same as harmless. `read_control`
135    /// decodes with this connection's own draft, so the `AnyControlMessage` it
136    /// hands back can only carry this draft's variant — but the narrowing arm
137    /// is compiled in every configuration anyway, under
138    /// `#[allow(unreachable_patterns)]` rather than a `cfg` naming the other
139    /// drafts, because such a list has to be edited in every per-draft
140    /// module whenever a draft is added and a copy that omits one leaves the
141    /// match non-exhaustive.
142    ///
143    /// Spelled as `CodecError::UnknownMessageType(0)` it would not stay inert:
144    /// every draft's
145    /// [`codec_session_error_code`](Connection::codec_session_error_code)
146    /// answers that variant `Some(PROTOCOL_VIOLATION)`. So the day the
147    /// narrowing did fail, this build's own defect would reach a caller as *the
148    /// peer sent a control message type this draft does not assign, and the
149    /// session must be closed with a Protocol Violation* — carrying `0x00` as
150    /// the codepoint that proved it. A conformance report reading that
151    /// publishes a named, well-evidenced accusation against a relay for
152    /// something no relay did.
153    ///
154    /// A variant of its own is what stops that.
155    /// [`draft_specific_cause`](Connection::draft_specific_cause) answers it
156    /// [`LocalRefusal`], the facade turns that into [`ErrorCause::Facade`], and
157    /// nothing downstream can read a rule out of a cause that says nothing
158    /// reached the wire. What is pinned is the consequence rather than the
159    /// unreachability: nothing pins the arm's reachability, which is exactly
160    /// why the consequence must not be an accusation.
161    ///
162    /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
163    /// [`ErrorCause::Facade`]: crate::dispatch::ErrorCause::Facade
164    #[error(
165        "a control message decoded for draft-18 did not narrow to draft-18: a defect in this          build, and evidence about nothing the peer did"
166    )]
167    ControlMessageNarrowing,
168    /// An Object arrived carrying properties on a status that is not Normal.
169    ///
170    /// Draft-18 Section 11.2.1.2: "Any Object with status Normal can have
171    /// properties (Section 2.5). If an endpoint receives properties on an
172    /// Object with status that is not Normal, it MUST close the session with a
173    /// PROTOCOL_VIOLATION."
174    ///
175    /// The codec decodes such an Object rather than refusing it — the frame is
176    /// well formed, and a tool that reports non-conforming traffic has to be
177    /// able to read it. Being an endpoint rather than an observer is what turns
178    /// it into an error, so it is raised here, on the receive path, and not in
179    /// the decoder.
180    #[error(
181        "object {object_id} carries {properties_len} bytes of properties on status {status:?}, which is not Normal"
182    )]
183    PropertiesOnNonNormalStatus {
184        /// The Object ID the properties arrived on.
185        object_id: u64,
186        /// Length in bytes of the properties block.
187        properties_len: usize,
188        /// The Object's status, resolved through the encoding's elision rule.
189        ///
190        /// Spelled out in full because the glob import of `moqtap_codec::types`
191        /// brings a different `ObjectStatus` into this module.
192        status: moqtap_codec::draft18::types::ObjectStatus,
193    },
194    /// A message that begins a request stream was handed to
195    /// [`Connection::send_control`]. Nothing was written.
196    #[error(
197        "{0:?} begins a request stream of its own and must not be written on the control stream"
198    )]
199    RequestOnControlStream(MessageType),
200    /// A message draft-18 places on a request stream was handed to
201    /// [`Connection::send_control`]. Nothing was written.
202    ///
203    /// Distinct from [`ConnectionError::RequestOnControlStream`], which is about
204    /// a message that would *begin* a request stream of its own. This one is
205    /// about a message that belongs on a request stream already open, and so has
206    /// no meaning without one around it.
207    #[error("{0:?} belongs on a request stream, not on the control stream")]
208    RequestStreamMessageOnControlStream(MessageType),
209    /// A datagram carrying an Object Status arrived with bytes after its
210    /// header.
211    ///
212    /// Draft-18 Section 11.2.1.1: "Any object with a status code other than
213    /// zero MUST have an empty payload." Section 11.3.1 says the same thing
214    /// about the framing: a datagram with the STATUS bit set "is present and
215    /// there is no Object Payload."
216    ///
217    /// The codec cannot see this. `DatagramHeader::decode` stops at the end of
218    /// the header and never owns the datagram's tail, so the only layer that
219    /// holds both the status and the bytes after it is this one. Without the
220    /// check the application is handed, say, an End-of-Group object carrying
221    /// four bytes of payload — a combination the draft forbids outright.
222    ///
223    /// Recoverable: the drafts state the rule as a property of a conforming
224    /// object, not as one of the "MUST close the session" cases, so the datagram
225    /// is refused and the session left running.
226    #[error(
227        "datagram for object {object_id} carries {payload_len} bytes after a header \
228         whose status is {status:?}, which permits no payload"
229    )]
230    PayloadOnStatusDatagram {
231        /// Object ID from the datagram header.
232        object_id: u64,
233        /// How many bytes followed the header.
234        payload_len: usize,
235        /// The status the header declared.
236        ///
237        /// Spelled out in full because the glob import of `moqtap_codec::types`
238        /// brings a different `ObjectStatus` into this module.
239        status: Option<moqtap_codec::draft18::types::ObjectStatus>,
240    },
241    /// A bidirectional stream the peer opened began with a message type that
242    /// does not open a request stream.
243    ///
244    /// Draft-18 Section 3.3: "Bidirectional streams MUST NOT begin with any
245    /// other message type unless negotiated. If they do, the peer MUST close
246    /// the Session with a PROTOCOL_VIOLATION." The session has already been
247    /// closed on the wire by the time this is returned, and the offending
248    /// stream reset.
249    #[error(
250        "a bidirectional stream the peer opened began with {0:?}, which does not begin a request stream; the session was closed"
251    )]
252    NonRequestOnRequestStream(MessageType),
253    /// A `respond_*` helper was handed a request stream this endpoint opened.
254    /// Nothing was written and no state moved.
255    #[error(
256        "this endpoint opened request {0}; only the endpoint a request stream was opened toward may answer it"
257    )]
258    RespondedToOwnRequest(u64),
259}
260
261impl From<crate::transport::DialError> for ConnectionError {
262    /// Maps a dial failure onto the variants this error already has, so a
263    /// caller matches `InvalidAddress` or `TlsConfig`.
264    ///
265    /// # `LocalSocket` joins `InvalidAddress`, and that is the answer being kept
266    ///
267    /// A socket this machine would not open has a variant of its own on
268    /// [`DialError`](crate::transport::DialError), and it still arrives here.
269    /// Not laziness about the churn — `InvalidAddress` is one of the
270    /// variants the facade reads as
271    /// [`ErrorCause::Facade`](crate::dispatch::ErrorCause::Facade), which
272    /// `is_local` answers **true** for, and a failed bind is this side's by
273    /// definition. Routing it to `Transport` would read better in prose and
274    /// would publish this machine's missing IPv6 stack as the relay's doing.
275    ///
276    /// The phase is not lost, only unread on this path. A caller measuring
277    /// which stage of a dial died reads
278    /// [`DialError::phase`](crate::transport::DialError::phase) off the dial
279    /// itself; a caller who arrived at this type named a `host:port` and asked
280    /// for a connection, not for a measurement, and a public variant here for
281    /// a distinction nothing on this path reads is churn with no reader, which
282    /// is why this impl stays flat.
283    fn from(e: crate::transport::DialError) -> Self {
284        match e {
285            // Two variants, one arm, deliberately — see above.
286            crate::transport::DialError::InvalidAddress(s)
287            | crate::transport::DialError::LocalSocket(s) => ConnectionError::InvalidAddress(s),
288            crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
289            crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
290        }
291    }
292}
293
294/// Transport type for the connection.
295#[derive(Debug, Clone)]
296pub enum TransportType {
297    /// Raw QUIC via quinn. The `addr` field should be `host:port`.
298    Quic,
299    /// WebTransport via wtransport. The `url` field is the WebTransport URL.
300    WebTransport {
301        /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
302        url: String,
303    },
304}
305
306/// Configuration for a MoQT client connection.
307///
308/// Both `draft` and `transport` are required -- there is no `Default` impl.
309pub struct ClientConfig {
310    /// The MoQT draft version to use (primary, determines codec/framing).
311    pub draft: DraftVersion,
312    /// The transport type (QUIC or WebTransport).
313    pub transport: TransportType,
314    /// Whether to skip TLS certificate verification (for testing).
315    pub skip_cert_verification: bool,
316    /// Custom CA certificates to trust (DER-encoded).
317    pub ca_certs: Vec<Vec<u8>>,
318    /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
319    pub setup_parameters: Vec<KeyValuePair>,
320}
321
322impl ClientConfig {
323    /// Returns the ALPN protocol identifiers for the transport.
324    pub fn alpn(&self) -> Vec<Vec<u8>> {
325        match &self.transport {
326            TransportType::Quic => vec![self.draft.quic_alpn().to_vec()],
327            TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
328        }
329    }
330}
331
332/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
333pub struct FramedSendStream {
334    inner: SendStream,
335    draft: DraftVersion,
336    /// Stateful subgroup object writer.
337    subgroup_io: Option<SubgroupObjectReader>,
338}
339
340impl FramedSendStream {
341    /// Create a new framed send stream for the given draft version.
342    pub fn new(inner: SendStream, draft: DraftVersion) -> Self {
343        Self { inner, draft, subgroup_io: None }
344    }
345
346    /// Get the transport-level stream ID.
347    pub fn stream_id(&self) -> u64 {
348        self.inner.stream_id()
349    }
350
351    /// Write a control message to the stream with type+length framing.
352    /// Returns the raw bytes that were written (for event capture).
353    pub async fn write_control(
354        &mut self,
355        msg: &AnyControlMessage,
356    ) -> Result<Vec<u8>, ConnectionError> {
357        let mut buf = Vec::new();
358        msg.encode(&mut buf)?;
359        self.inner.write_all(&buf).await?;
360        Ok(buf)
361    }
362
363    /// Write a subgroup stream header. Also initializes the internal
364    /// delta-encoding state used by
365    /// [`FramedSendStream::write_subgroup_object`].
366    ///
367    /// The header is refused, and nothing is written, if its fields disagree
368    /// with its own stream type. That check has to happen here rather than at
369    /// the first object: the type is what every object after it is framed
370    /// against, so a header that went out saying the wrong thing cannot be
371    /// taken back.
372    pub async fn write_subgroup_header(
373        &mut self,
374        header: &AnySubgroupHeader,
375    ) -> Result<(), ConnectionError> {
376        let mut buf = Vec::new();
377        header.encode_stream_checked(&mut buf)?;
378        self.inner.write_all(&buf).await?;
379        // Clippy would rather see these two arms as an `if let`, and rustc rejects
380        // that in a single-draft build, where the pattern is irrefutable. Only a
381        // `match` satisfies both.
382        #[allow(clippy::single_match)]
383        match header {
384            AnySubgroupHeader::Draft18(ref header) => {
385                self.subgroup_io = Some(SubgroupObjectReader::new(header));
386            }
387            // Only this draft's header seeds the object reader. With draft 18 the only enabled
388            // draft `AnySubgroupHeader` has a single variant, the arm above is exhaustive and this
389            // one unreachable. Compiled in every configuration with the lint allowed, rather than
390            // gated on a `cfg` naming the other drafts: such a list has to be edited in
391            // every draft module whenever a draft is added, and a copy that omits one leaves this
392            // match non-exhaustive.
393            #[allow(unreachable_patterns)]
394            _ => {}
395        }
396        Ok(())
397    }
398
399    /// Write a fetch response header.
400    pub async fn write_fetch_header(
401        &mut self,
402        header: &AnyFetchHeader,
403    ) -> Result<(), ConnectionError> {
404        let mut buf = Vec::new();
405        header.encode_stream(&mut buf);
406        self.inner.write_all(&buf).await?;
407        Ok(())
408    }
409
410    /// Append a draft-18 subgroup object to the stream using the
411    /// stateful writer seeded from
412    /// [`FramedSendStream::write_subgroup_header`].
413    pub async fn write_subgroup_object(
414        &mut self,
415        object: &SubgroupObject,
416    ) -> Result<(), ConnectionError> {
417        let writer = self
418            .subgroup_io
419            .as_mut()
420            .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
421        let mut buf = Vec::new();
422        writer.write_object(object, &mut buf)?;
423        self.inner.write_all(&buf).await?;
424        Ok(())
425    }
426
427    /// Append a fetch object to the stream.
428    ///
429    /// The fetch stream had a header writer and no object writer, so a caller
430    /// could open one and put nothing on it through this type. The subgroup
431    /// stream has had both since the writer was introduced.
432    ///
433    /// The declared length comes from the payload rather than from the caller's
434    /// field: a header that disagrees with the bytes beside it desynchronises
435    /// every object after it on the stream, and nothing downstream can recover.
436    ///
437    /// # Errors
438    ///
439    /// [`ConnectionError::Codec`] if the header's fields disagree with the
440    /// Serialization Flags that announce them, which the encoder refuses rather
441    /// than writing a frame its own reader cannot take apart.
442    pub async fn write_fetch_object(
443        &mut self,
444        header: &FetchObjectHeader,
445        payload: &[u8],
446    ) -> Result<(), ConnectionError> {
447        let mut header = header.clone();
448        header.payload_length = VarInt::from_usize(payload.len());
449        let mut buf = Vec::new();
450        header.encode(&mut buf)?;
451        buf.extend_from_slice(payload);
452        self.inner.write_all(&buf).await?;
453        Ok(())
454    }
455
456    /// Finish the stream (send FIN).
457    pub async fn finish(&mut self) -> Result<(), ConnectionError> {
458        self.inner.finish()?;
459        Ok(())
460    }
461
462    /// Reset the stream with `code`, telling the peer transmission was
463    /// abandoned rather than completed.
464    ///
465    /// Dropping a send stream sends a FIN, which claims the stream ended
466    /// cleanly; this is the only way to say the opposite. See
467    /// [`SendStream::reset`].
468    pub fn reset(&mut self, code: u64) -> Result<(), ConnectionError> {
469        self.inner.reset(code)?;
470        Ok(())
471    }
472
473    /// Returns the draft version this stream is framed for.
474    pub fn draft(&self) -> DraftVersion {
475        self.draft
476    }
477}
478
479/// What an Object Status makes of an object here.
480///
481/// Two answers where drafts 08 through 13 have three, and the missing one is
482/// the point: the end-of-track status settles where the track ended and is
483/// judged against nothing, because the rule about where one may be placed is
484/// not in this draft. `a_track_may_end_where_it_has_already_been.rs` asserts
485/// that acceptance.
486///
487/// Every other status is a statement about objects rather than one of them.
488fn object_role(status: Option<u64>) -> ObjectRole {
489    match status {
490        None | Some(0x0) => ObjectRole::Produced,
491        Some(0x4) => ObjectRole::EndsTrack(None),
492        _ => ObjectRole::Neither,
493    }
494}
495
496/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
497pub struct FramedRecvStream {
498    inner: RecvStream,
499    buf: BytesMut,
500    draft: DraftVersion,
501    /// Stateful subgroup object reader.
502    subgroup_io: Option<SubgroupObjectReader>,
503    /// Stateful fetch object reader, holding the prior Object's Location and the
504    /// Group Order its deltas count in. Started by
505    /// [`FramedRecvStream::begin_fetch_objects`] rather than by the fetch
506    /// header, which does not carry the order.
507    fetch_io: Option<FetchObjectReader>,
508    /// The record this stream's objects are measured against, and the Group ID
509    /// its header named.
510    ///
511    /// One group for the whole stream: a subgroup header names it once and no
512    /// object header repeats it. `None` on a stream that was never given one -
513    /// a stream for an alias no live binding names, and every stream built
514    /// outside [`Connection::accept_subgroup_stream`].
515    tracking: Option<(TrackObjects, u64)>,
516}
517
518impl FramedRecvStream {
519    /// Create a new framed receive stream for the given draft version.
520    pub fn new(inner: RecvStream, draft: DraftVersion) -> Self {
521        Self {
522            inner,
523            buf: BytesMut::with_capacity(4096),
524            draft,
525            subgroup_io: None,
526            fetch_io: None,
527            tracking: None,
528        }
529    }
530
531    /// Get the transport-level stream ID.
532    pub fn stream_id(&self) -> u64 {
533        self.inner.stream_id()
534    }
535
536    /// Measure this stream's objects against `objects`, all of them in `group`.
537    fn measure_objects_against(&mut self, objects: TrackObjects, group: u64) {
538        self.tracking = Some((objects, group));
539    }
540
541    /// Judge one object this stream carried against where its track ended.
542    fn note_subgroup_object(
543        &self,
544        object: u64,
545        status: Option<u64>,
546    ) -> Result<(), ConnectionError> {
547        let Some((objects, group)) = &self.tracking else { return Ok(()) };
548        let at = ObjectLocation { group: *group, object };
549        objects.note_past_final(at, object_role(status)).map_err(|end| {
550            ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject {
551                alias: objects.alias(),
552                group: at.group,
553                object: at.object,
554                final_group: end.group,
555                final_object: end.object,
556            })
557        })
558    }
559
560    /// Read more data from the stream into the internal buffer.
561    async fn fill(&mut self) -> Result<bool, ConnectionError> {
562        let mut tmp = [0u8; 4096];
563        match self.inner.read(&mut tmp).await {
564            Ok(Some(n)) => {
565                self.buf.extend_from_slice(&tmp[..n]);
566                Ok(true)
567            }
568            Ok(None) => Ok(false),
569            Err(e) => Err(ConnectionError::Transport(e)),
570        }
571    }
572
573    /// Ensure at least `n` bytes are available in the buffer.
574    async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
575        while self.buf.len() < n {
576            if !self.fill().await? {
577                return Err(ConnectionError::UnexpectedEnd);
578            }
579        }
580        Ok(())
581    }
582
583    /// Read this stream's leading variable-length integer **without
584    /// consuming it**, and return its value.
585    ///
586    /// Every unidirectional MoQT stream on draft-18 opens with a varint
587    /// naming what it is (Section 3.4): 0x05 for FETCH_HEADER, 0x10-0x1D for
588    /// SUBGROUP_HEADER, and [`CONTROL_STREAM_TYPE`] for SETUP. Telling the
589    /// peer's control stream apart from a data stream means reading that
590    /// varint, and taking it off the transport would destroy it: the control
591    /// stream's type varint *is* the SETUP message's type field, so a stream
592    /// whose type had been stripped would no longer decode as a SETUP.
593    ///
594    /// Nothing is stripped. The bytes land in this reader's own buffer, and
595    /// every other method here — [`read_control`](Self::read_control),
596    /// [`read_subgroup_header`](Self::read_subgroup_header),
597    /// [`read_fetch_header`](Self::read_fetch_header) — decodes out of that
598    /// buffer and advances it only on a successful decode. A stream this was
599    /// called on is indistinguishable from one it was not, which is what
600    /// makes it safe to peek a stream and then hand it to whichever reader
601    /// the type turned out to call for.
602    ///
603    /// It reads, so it can block: a peer that opens a stream and then writes
604    /// nothing leaves this pending until a byte arrives or the stream ends.
605    ///
606    /// # Errors
607    ///
608    /// - [`ConnectionError::UnexpectedEnd`] if the stream ends before a whole
609    ///   varint has arrived.
610    /// - [`ConnectionError::Transport`] if the peer reset the stream.
611    /// - [`ConnectionError::VarInt`] if the bytes are not a valid varint.
612    ///
613    /// Whatever did arrive stays in the buffer in every case.
614    pub async fn peek_stream_type(&mut self) -> Result<u64, ConnectionError> {
615        self.ensure(1).await?;
616        let type_len = self.draft.varint_len(self.buf[0]);
617        self.ensure(type_len).await?;
618        let mut cursor = &self.buf[..type_len];
619        Ok(self.draft.decode_varint(&mut cursor)?.into_inner())
620    }
621
622    /// Read a control message from the stream.
623    ///
624    /// When `capture_raw` is true, the returned tuple includes a clone of the
625    /// framed wire bytes (for observer emission). When false, the second
626    /// element is `None` and the payload clone is skipped.
627    pub async fn read_control(
628        &mut self,
629        capture_raw: bool,
630    ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
631        // Read type ID varint
632        self.ensure(1).await?;
633        let type_len = self.draft.varint_len(self.buf[0]);
634        self.ensure(type_len).await?;
635
636        let mut cursor = &self.buf[..type_len];
637        let _type_id = self.draft.decode_varint(&mut cursor)?;
638
639        // Draft-18: 16-bit BE payload length
640        let (payload_len, len_field_size) = if self.draft.uses_fixed_length_framing() {
641            self.ensure(type_len + 2).await?;
642            let hi = self.buf[type_len] as usize;
643            let lo = self.buf[type_len + 1] as usize;
644            ((hi << 8) | lo, 2)
645        } else {
646            self.ensure(type_len + 1).await?;
647            let payload_len_start = type_len;
648            let payload_len_varint_len = self.draft.varint_len(self.buf[payload_len_start]);
649            self.ensure(type_len + payload_len_varint_len).await?;
650            let mut cursor = &self.buf[payload_len_start..type_len + payload_len_varint_len];
651            let payload_len = self.draft.decode_varint(&mut cursor)?.into_inner() as usize;
652            (payload_len, payload_len_varint_len)
653        };
654
655        // Read full payload
656        let total = type_len + len_field_size + payload_len;
657        self.ensure(total).await?;
658
659        // Capture raw bytes only if requested (observer attached).
660        let raw = capture_raw.then(|| self.buf[..total].to_vec());
661
662        // Now decode the whole message
663        let mut frame = &self.buf[..total];
664        let msg = AnyControlMessage::decode(self.draft, &mut frame)?;
665        self.buf.advance(total);
666        Ok((msg, raw))
667    }
668
669    /// Read a subgroup stream header. Also initializes the internal
670    /// delta-decoding state.
671    pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
672        self.ensure(1).await?;
673        loop {
674            let mut cursor = &self.buf[..];
675            match AnySubgroupHeader::decode(self.draft, &mut cursor) {
676                Ok(header) => {
677                    let consumed = self.buf.len() - cursor.remaining();
678                    self.buf.advance(consumed);
679                    // Clippy would rather see these two arms as an `if let`, and rustc rejects
680                    // that in a single-draft build, where the pattern is irrefutable. Only a
681                    // `match` satisfies both.
682                    #[allow(clippy::single_match)]
683                    match header {
684                        AnySubgroupHeader::Draft18(ref header) => {
685                            self.subgroup_io = Some(SubgroupObjectReader::new(header));
686                        }
687                        // Only this draft's header seeds the object reader. With draft 18 the only
688                        // enabled draft `AnySubgroupHeader` has a single variant, the arm above is
689                        // exhaustive and this one unreachable. Compiled in every configuration with
690                        // the lint allowed, rather than gated on a `cfg` naming the other thirteen
691                        // drafts: such a list has to be edited in every draft module whenever a
692                        // draft is added, and a copy that omits one leaves this match
693                        // non-exhaustive.
694                        #[allow(unreachable_patterns)]
695                        _ => {}
696                    }
697                    return Ok(header);
698                }
699                Err(e) if e.is_incomplete() => {
700                    if !self.fill().await? {
701                        return Err(ConnectionError::UnexpectedEnd);
702                    }
703                }
704                Err(e) => return Err(ConnectionError::Codec(e)),
705            }
706        }
707    }
708
709    /// Read a fetch response header.
710    pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
711        self.ensure(1).await?;
712        loop {
713            let mut cursor = &self.buf[..];
714            match AnyFetchHeader::decode(self.draft, &mut cursor) {
715                Ok(header) => {
716                    let consumed = self.buf.len() - cursor.remaining();
717                    self.buf.advance(consumed);
718                    return Ok(header);
719                }
720                Err(e) if e.is_incomplete() => {
721                    if !self.fill().await? {
722                        return Err(ConnectionError::UnexpectedEnd);
723                    }
724                }
725                Err(e) => return Err(ConnectionError::Codec(e)),
726            }
727        }
728    }
729
730    /// Read the next draft-18 subgroup object from this stream using
731    /// the stateful reader seeded by
732    /// [`FramedRecvStream::read_subgroup_header`].
733    ///
734    /// Errors with [`ConnectionError::PropertiesOnNonNormalStatus`] on an
735    /// Object that carries properties on a status other than Normal, which
736    /// draft-18 Section 11.2.1.2 answers with a session close. The Object is
737    /// consumed from the stream before the check, so the reader stays in step
738    /// with the wire and a caller that reports the violation and reads on sees
739    /// the following Object rather than a re-parse of this one.
740    pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
741        if self.subgroup_io.is_none() {
742            return Err(ConnectionError::DataStreamState("subgroup header not read yet"));
743        }
744        loop {
745            let reader = self.subgroup_io.as_mut().unwrap();
746            let mut probe = reader.clone();
747            let mut cursor = &self.buf[..];
748            match probe.read_object(&mut cursor) {
749                Ok(obj) => {
750                    let consumed = self.buf.len() - cursor.remaining();
751                    self.buf.advance(consumed);
752                    *reader = probe;
753                    if !obj.properties_permitted() {
754                        return Err(ConnectionError::PropertiesOnNonNormalStatus {
755                            object_id: obj.object_id.into_inner(),
756                            properties_len: obj.extension_headers.len(),
757                            status: obj.status(),
758                        });
759                    }
760                    self.note_subgroup_object(
761                        obj.object_id.into_inner(),
762                        obj.object_status.map(|s| s as u64),
763                    )?;
764                    return Ok(obj);
765                }
766                Err(e) if e.is_incomplete() => {
767                    if !self.fill().await? {
768                        return Err(ConnectionError::UnexpectedEnd);
769                    }
770                }
771                Err(e) => return Err(ConnectionError::Codec(e)),
772            }
773        }
774    }
775
776    /// Read the next draft-18 fetch header from this stream.
777    pub async fn read_fetch_stream_header(&mut self) -> Result<FetchHeader, ConnectionError> {
778        loop {
779            let mut cursor = &self.buf[..];
780            match FetchHeader::decode(&mut cursor) {
781                Ok(hdr) => {
782                    let consumed = self.buf.len() - cursor.remaining();
783                    self.buf.advance(consumed);
784                    return Ok(hdr);
785                }
786                Err(e) if e.is_incomplete() => {
787                    if !self.fill().await? {
788                        return Err(ConnectionError::UnexpectedEnd);
789                    }
790                }
791                Err(e) => return Err(ConnectionError::Codec(e)),
792            }
793        }
794    }
795
796    /// Stop accepting data on this stream with `code` as the `STOP_SENDING`
797    /// application error code, discarding anything unread.
798    ///
799    /// Dropping a receive stream also stops it, but with a hard-coded 0. See
800    /// [`RecvStream::stop`].
801    pub fn stop(&mut self, code: u64) -> Result<(), ConnectionError> {
802        self.inner.stop(code)?;
803        Ok(())
804    }
805
806    /// Wait for the peer to reset this stream, consuming nothing.
807    ///
808    /// See [`RecvStream::received_reset`] for what `Ok(None)` means and why a
809    /// caller must not re-poll after it.
810    pub async fn received_reset(&mut self) -> Result<Option<u64>, ConnectionError> {
811        Ok(self.inner.received_reset().await?)
812    }
813
814    /// Start reading the objects of a fetch stream whose Groups arrive in
815    /// `group_order`.
816    ///
817    /// Section 11.4.4.1 makes a Group ID Delta count downward under Descending
818    /// and upward under Ascending, so the same bytes are two different
819    /// Locations and nothing on the data stream says which. The order is the
820    /// one the **request** asked for — Section 10.12.3: "The publisher
821    /// responding to a FETCH is responsible for delivering all available
822    /// Objects in the requested range in the requested order" — carried by the
823    /// GROUP_ORDER parameter on the FETCH, or by its absence, which Section
824    /// 10.2.8 reads as Ascending. Either way it is a control message this
825    /// stream never sees. Draft-19 encodes both IDs the same way and needs the
826    /// same call; drafts 15, 16 and 17 resolve their fetch objects without an
827    /// order, because none of those drafts encodes an ID as a difference.
828    ///
829    /// Call it after [`FramedRecvStream::read_fetch_header`] and before the
830    /// first [`FramedRecvStream::read_fetch_object`]; calling it again restarts
831    /// the running state, which is what a second fetch stream on the same
832    /// connection would want and what the middle of one would not.
833    pub fn begin_fetch_objects(&mut self, group_order: GroupOrder) {
834        self.fetch_io = Some(FetchObjectReader::new(group_order));
835    }
836
837    /// Read the next draft-18 fetch object and its payload.
838    ///
839    /// The mirror of [`FramedSendStream::write_fetch_object`]. What comes back
840    /// is a [`FetchObject`] rather than a header: draft-18's reader resolves the
841    /// Location as it reads, and the resolved Group ID, Subgroup ID, Object ID
842    /// and Priority are the fields a subscriber acts on. The header it decoded
843    /// from is inside it.
844    ///
845    /// The payload comes back with it because `payload_length` says how many
846    /// bytes follow, and a reader that takes the wrong number of them
847    /// desynchronises every later object on the stream.
848    ///
849    /// # Errors
850    ///
851    /// [`ConnectionError::DataStreamState`] when
852    /// [`FramedRecvStream::begin_fetch_objects`] has not been called,
853    /// [`ConnectionError::UnexpectedEnd`] when the stream ends inside the header
854    /// or inside the payload it declared, and [`ConnectionError::Codec`] on
855    /// every rule Section 11.4.4.1 states — a first Object that inherits, a
856    /// delta that runs off either end of the space, or an Object ID above
857    /// 2^64-1.
858    pub async fn read_fetch_object(&mut self) -> Result<(FetchObject, Vec<u8>), ConnectionError> {
859        if self.fetch_io.is_none() {
860            return Err(ConnectionError::DataStreamState("fetch object reader not started"));
861        }
862        let object = loop {
863            let reader = self.fetch_io.as_mut().unwrap();
864            // Advanced on a probe and committed only once the whole header was
865            // there to read: a reader advanced by a short read would resolve
866            // the next Object against a half-read one.
867            let mut probe = reader.clone();
868            let mut cursor = &self.buf[..];
869            match probe.read_object_header(&mut cursor) {
870                Ok(object) => {
871                    let consumed = self.buf.len() - cursor.remaining();
872                    self.buf.advance(consumed);
873                    *reader = probe;
874                    break object;
875                }
876                Err(e) if e.is_incomplete() => {
877                    if !self.fill().await? {
878                        return Err(ConnectionError::UnexpectedEnd);
879                    }
880                }
881                Err(e) => return Err(ConnectionError::Codec(e)),
882            }
883        };
884        let payload = self.read_object_payload(&object.header.payload_length).await?;
885        Ok((object, payload))
886    }
887
888    /// Take the `length` payload bytes that follow a fetch object's header.
889    ///
890    /// Separate from the header read because the header is decoded from a probe
891    /// cursor that may have to be retried after a fill, and the payload is a
892    /// flat byte count that never is.
893    async fn read_object_payload(&mut self, length: &VarInt) -> Result<Vec<u8>, ConnectionError> {
894        let length = length.into_inner() as usize;
895        self.ensure(length).await?;
896        let payload = self.buf[..length].to_vec();
897        self.buf.advance(length);
898        Ok(payload)
899    }
900
901    /// Returns the draft version this stream is framed for.
902    pub fn draft(&self) -> DraftVersion {
903        self.draft
904    }
905}
906
907/// Which of the seven message types draft-18 Section 3.3 lets a bidirectional
908/// stream begin with opened a request stream.
909///
910/// Draft-18 Section 3.3: "A request stream begins with one of these seven
911/// message types: TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
912/// SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS. Bidirectional streams MUST NOT
913/// begin with any other message type unless negotiated."
914///
915/// The set is per draft and is not stable across drafts. Draft-17 named six:
916/// draft-18 added SUBSCRIBE_TRACKS and renumbered SUBSCRIBE_NAMESPACE from
917/// 0x11 to 0x50. A per-draft enum is what makes it impossible to name
918/// draft-18's seventh kind in draft-17 code, or to forget it here.
919#[derive(Debug, Clone, Copy, PartialEq, Eq)]
920pub enum RequestKind {
921    /// TRACK_STATUS, 0x0D.
922    TrackStatus,
923    /// SUBSCRIBE, 0x03.
924    Subscribe,
925    /// PUBLISH, 0x1D.
926    Publish,
927    /// FETCH, 0x16 — standalone or joining.
928    Fetch,
929    /// PUBLISH_NAMESPACE, 0x06.
930    PublishNamespace,
931    /// SUBSCRIBE_NAMESPACE, 0x50 on this draft — 0x11 on draft-17.
932    SubscribeNamespace,
933    /// SUBSCRIBE_TRACKS, 0x51, new in draft-18.
934    SubscribeTracks,
935}
936
937impl RequestKind {
938    /// The message type a request stream of this kind begins with.
939    pub const fn message_type(self) -> MessageType {
940        match self {
941            RequestKind::TrackStatus => MessageType::TrackStatus,
942            RequestKind::Subscribe => MessageType::Subscribe,
943            RequestKind::Publish => MessageType::Publish,
944            RequestKind::Fetch => MessageType::Fetch,
945            RequestKind::PublishNamespace => MessageType::PublishNamespace,
946            RequestKind::SubscribeNamespace => MessageType::SubscribeNamespace,
947            RequestKind::SubscribeTracks => MessageType::SubscribeTracks,
948        }
949    }
950
951    /// The kind of request stream `ty` opens, or `None` when it opens none.
952    ///
953    /// The inverse of [`message_type`](Self::message_type) and the classifier
954    /// the accept path runs on the first message of a bidirectional stream the
955    /// peer opened. `None` is the PROTOCOL_VIOLATION case of draft-18
956    /// Section 3.3.
957    ///
958    /// Like [`starts_a_request_stream`] the match is exhaustive over
959    /// [`MessageType`] with **no wildcard arm**, so a message type added in a
960    /// later draft stops this compiling until someone classifies it; the unit
961    /// test below holds the two functions to the same answer for every
962    /// assigned type, so neither can drift from the other.
963    pub const fn from_message_type(ty: MessageType) -> Option<RequestKind> {
964        match ty {
965            MessageType::TrackStatus => Some(RequestKind::TrackStatus),
966            MessageType::Subscribe => Some(RequestKind::Subscribe),
967            MessageType::Publish => Some(RequestKind::Publish),
968            MessageType::Fetch => Some(RequestKind::Fetch),
969            MessageType::PublishNamespace => Some(RequestKind::PublishNamespace),
970            MessageType::SubscribeNamespace => Some(RequestKind::SubscribeNamespace),
971            MessageType::SubscribeTracks => Some(RequestKind::SubscribeTracks),
972            MessageType::Setup
973            | MessageType::GoAway
974            | MessageType::Namespace
975            | MessageType::NamespaceDone
976            | MessageType::PublishBlocked
977            | MessageType::RequestUpdate
978            | MessageType::SubscribeOk
979            | MessageType::RequestOk
980            | MessageType::RequestError
981            | MessageType::FetchOk
982            | MessageType::PublishDone => None,
983        }
984    }
985}
986
987/// Which side opened the bidirectional stream a request travels on.
988///
989/// Draft-18 Section 3.3 gives every request a bidirectional stream, and either
990/// endpoint may open one. The two directions are not symmetric — one side owes
991/// a response and the other is waiting for it — so a [`RequestStream`] carries
992/// this to say which side of that it is on.
993#[derive(Debug, Clone, Copy, PartialEq, Eq)]
994pub enum RequestOrigin {
995    /// This endpoint opened the stream and wrote the request on it. What comes
996    /// back is a response, and dropping the handle cancels the request.
997    Local,
998    /// The peer opened the stream; this endpoint owes it a response. What
999    /// comes back is a follow-up to the peer's request, never a response, and
1000    /// dropping the handle abandons a request that was asked of us.
1001    Peer,
1002}
1003/// Whether draft-18 Table 5 places this message on a request stream that is
1004/// already open.
1005///
1006/// All four of the messages Table 5 marks "Request" without their beginning one:
1007/// REQUEST_UPDATE modifies the request its stream carries (Section 10.9),
1008/// NAMESPACE and NAMESPACE_DONE report namespaces on the SUBSCRIBE_NAMESPACE
1009/// stream that asked for them (Sections 10.16 and 10.17), and PUBLISH_BLOCKED
1010/// names a track on the SUBSCRIBE_TRACKS stream that asked for it (Section
1011/// 10.20).
1012///
1013/// `Endpoint::receive_message` already closes the session over all four when
1014/// they arrive on the control stream. Without this the client would write on the
1015/// control stream exactly what its own peer half refuses to read there.
1016fn belongs_on_a_request_stream(ty: MessageType) -> bool {
1017    matches!(
1018        ty,
1019        MessageType::RequestUpdate
1020            | MessageType::Namespace
1021            | MessageType::NamespaceDone
1022            | MessageType::PublishBlocked
1023    )
1024}
1025
1026/// Whether `ty` is one of the seven message types draft-18 Section 3.3 lets a
1027/// bidirectional stream begin with.
1028///
1029/// The match is exhaustive over [`MessageType`] and deliberately has **no
1030/// wildcard arm**. That is the drift guard: `MessageType` is not
1031/// `#[non_exhaustive]`, so the day a draft gains a message type this stops
1032/// compiling until someone says here whether the new type opens a request
1033/// stream. A wildcard would silently answer "no" for it — which is exactly
1034/// what would have happened to SUBSCRIBE_TRACKS, the type draft-18 added.
1035///
1036/// Classification is over the typed `MessageType`, never over a raw `u64`,
1037/// because the number alone does not say which registry it came from: on
1038/// draft-18, 0x50 is SUBSCRIBE_NAMESPACE as a control message type and also
1039/// the start of a SUBGROUP_HEADER range as a unidirectional stream type.
1040pub const fn starts_a_request_stream(ty: MessageType) -> bool {
1041    match ty {
1042        // The seven that begin a request stream.
1043        MessageType::TrackStatus
1044        | MessageType::Subscribe
1045        | MessageType::Publish
1046        | MessageType::Fetch
1047        | MessageType::PublishNamespace
1048        | MessageType::SubscribeNamespace
1049        | MessageType::SubscribeTracks => true,
1050        // These travel on a request stream too — draft-18's Table 5 marks
1051        // NAMESPACE, NAMESPACE_DONE, PUBLISH_BLOCKED and REQUEST_UPDATE
1052        // "Request", and GOAWAY "Control, Request" — but none of them may
1053        // *begin* one, which is the only question asked here. Table 5 marks
1054        // just the seven above "Request, First". SETUP is the control stream's
1055        // own type varint and belongs to no bidirectional stream at all.
1056        MessageType::Setup
1057        | MessageType::GoAway
1058        | MessageType::Namespace
1059        | MessageType::NamespaceDone
1060        | MessageType::PublishBlocked
1061        | MessageType::RequestUpdate => false,
1062        // Responses. They cannot begin a stream: they arrive on the request
1063        // stream their request opened, which is why they carry no request id
1064        // of their own on this draft. Draft-18 has one fewer than draft-17
1065        // because PUBLISH_OK became an alias of REQUEST_OK, 0x07.
1066        MessageType::SubscribeOk
1067        | MessageType::RequestOk
1068        | MessageType::RequestError
1069        | MessageType::FetchOk
1070        | MessageType::PublishDone => false,
1071    }
1072}
1073
1074/// One request and its answer, on a bidirectional stream of their own.
1075///
1076/// Draft-18 Section 3.3 carries forward draft-17's move of requests off the
1077/// control plane: each request is the first message on a bidirectional stream
1078/// it opens, and the response comes back on that same stream. Responses carry
1079/// no request id on this draft — **the stream is the correlation**, which is
1080/// why this handle exists and why a bare request id is no longer enough to
1081/// find an answer.
1082///
1083/// # Reading and writing go through the connection
1084///
1085/// This handle owns both halves of the stream but not the session, so the
1086/// endpoint state machine and the observer stay where they were. Read a
1087/// response with [`Connection::recv_on_request_stream`], write a follow-up with
1088/// [`Connection::send_on_request_stream`], and cancel with
1089/// [`Connection::cancel_request_stream`].
1090///
1091/// [`cancel`](Self::cancel) and [`peer_cancelled`](Self::peer_cancelled) are on
1092/// the handle because they touch the stream and nothing else, and [`Drop`]
1093/// needs the first of them. Neither moves the endpoint's record of the request,
1094/// which is why the connection carries a pair of its own.
1095///
1096/// # Dropping this cancels the request
1097///
1098/// A dropped handle resets the send half and sends `STOP_SENDING` on the
1099/// receive half, unless the stream was already cancelled or finished. Letting
1100/// the default drop stand would send a FIN instead, telling the peer the
1101/// request ended *cleanly* when it was abandoned. Which code goes on the wire
1102/// depends on who opened the stream — see [`Drop`].
1103///
1104/// The consequence is sharp and worth stating: a live subscription's request
1105/// stream must be **held for the subscription's life**, because PUBLISH_DONE
1106/// arrives on it. Keeping only [`request_id`](Self::request_id) and letting
1107/// the handle fall out of scope cancels the subscription.
1108///
1109/// What a drop cannot do is say so at the endpoint. [`Drop`] holds the stream
1110/// and not the session, so the request stays where it was in the endpoint's
1111/// record while the stream it travelled on is gone. Call
1112/// [`Connection::cancel_request_stream`] wherever that record matters.
1113///
1114/// # Which side opened it changes what this handle does
1115///
1116/// [`origin`](Self::origin) says whether this endpoint opened the stream or
1117/// accepted it, and three behaviours turn on it: reads dispatch as responses
1118/// or as follow-ups to the peer's request, the `respond_*` helpers refuse a
1119/// stream this endpoint opened, and [`Drop`] resets with
1120/// [`REQUEST_UNANSWERED`] rather than [`REQUEST_CANCELLED`]. Everything else —
1121/// [`cancel`](Self::cancel), [`peer_cancelled`](Self::peer_cancelled),
1122/// [`finish`](Self::finish),
1123/// [`Connection::send_on_request_stream`] — is the same in both directions.
1124/// Draft-18 Section 3.3.2 is explicit that a cancel is available to both:
1125/// "Senders cancel requests if the response is no longer of interest;
1126/// Receivers cancel requests if they are unable to or choose not to respond."
1127///
1128/// All fields are private so the shape can grow without breaking callers.
1129#[must_use = "dropping a request stream cancels the request; hold it until the response arrives"]
1130pub struct RequestStream {
1131    send: FramedSendStream,
1132    recv: FramedRecvStream,
1133    request_id: VarInt,
1134    kind: RequestKind,
1135    /// The unidirectional stream this request's objects are being served on.
1136    ///
1137    /// Only a FETCH has one, and only once the caller has opened it through
1138    /// [`Connection::open_fetch_stream_on`]. Held here rather than in a table
1139    /// on the connection because the request stream is already the thing that
1140    /// knows what this request still owes, and because a handle kept beside
1141    /// the request cannot outlive it.
1142    fetch_data: Option<FramedSendStream>,
1143    draft: DraftVersion,
1144    stream_id: u64,
1145    cancelled: bool,
1146    finished: bool,
1147    origin: RequestOrigin,
1148    /// Whether a `respond_*` helper has written a response on this stream.
1149    /// True on a [`RequestOrigin::Peer`] stream from the response written on
1150    /// it, and on a [`RequestOrigin::Local`] one from the answer to an update
1151    /// the peer sent, which is the only response this endpoint writes on a
1152    /// stream of its own.
1153    responded: bool,
1154}
1155
1156impl RequestStream {
1157    /// The request id the endpoint allocated for this request.
1158    ///
1159    /// Useful for logging and for endpoint calls that still take one. It is
1160    /// not enough to find the response: draft-18 responses carry no request
1161    /// id, so only this stream identifies them.
1162    pub fn request_id(&self) -> VarInt {
1163        self.request_id
1164    }
1165
1166    /// Which of the seven request types opened this stream.
1167    pub fn kind(&self) -> RequestKind {
1168        self.kind
1169    }
1170
1171    /// The transport-level stream identifier, the same one
1172    /// [`ClientEvent::StreamOpened`] reports for data streams.
1173    pub fn stream_id(&self) -> u64 {
1174        self.stream_id
1175    }
1176
1177    /// The draft version this stream is framed for.
1178    pub fn draft(&self) -> DraftVersion {
1179        self.draft
1180    }
1181
1182    /// Which side opened this stream.
1183    ///
1184    /// [`RequestOrigin::Peer`] means this endpoint owes a response and the
1185    /// `respond_*` helpers apply; [`RequestOrigin::Local`] means it is waiting
1186    /// for one.
1187    pub fn origin(&self) -> RequestOrigin {
1188        self.origin
1189    }
1190
1191    /// Whether a response has been written on this stream by one of the
1192    /// `respond_*` helpers.
1193    ///
1194    /// On a [`RequestOrigin::Local`] stream this says an update the peer sent
1195    /// was answered here, not that the request itself was: that one is
1196    /// answered by the peer.
1197    pub fn responded(&self) -> bool {
1198        self.responded
1199    }
1200
1201    /// The stream this request's objects are being served on, if one is open.
1202    ///
1203    /// Only a FETCH answered through
1204    /// [`Connection::open_fetch_stream_on`](Connection::open_fetch_stream_on)
1205    /// has one. Writing objects goes through this rather than through a handle
1206    /// the caller keeps, so that the connection can still reach the stream
1207    /// when a rule says to reset it.
1208    pub fn fetch_data(&mut self) -> Option<&mut FramedSendStream> {
1209        self.fetch_data.as_mut()
1210    }
1211
1212    /// Reset the fetch data stream, if one was opened, and forget it.
1213    ///
1214    /// The sentence that requires this names no error code, so the code comes
1215    /// from the registry rather than from here: CANCELLED is "the stream was
1216    /// cancelled by either endpoint", which is what a publisher abandoning the
1217    /// objects it was serving has done.
1218    fn reset_fetch_data(&mut self) {
1219        if let Some(mut framed) = self.fetch_data.take() {
1220            let _ = framed.reset(StreamResetErrorCode::Cancelled as u64);
1221        }
1222    }
1223
1224    /// Whether [`cancel`](Self::cancel) has already run on this handle.
1225    ///
1226    /// Says nothing about the peer: a peer reset is learned from
1227    /// [`peer_cancelled`](Self::peer_cancelled) or from the next read.
1228    pub fn is_cancelled(&self) -> bool {
1229        self.cancelled
1230    }
1231
1232    /// Cancel the request by resetting the stream, handing the peer `code`.
1233    ///
1234    /// Draft-18 has no UNSUBSCRIBE and no FETCH_CANCEL: resetting the request
1235    /// stream is how a request is withdrawn. Both halves are shut — a QUIC
1236    /// bidirectional stream has two independent halves, so resetting only the
1237    /// send half would leave the peer free to keep writing a response nobody
1238    /// will read. The send half is reset with `code` and the receive half is
1239    /// stopped with the same value.
1240    /// [`REQUEST_CANCELLED`] is the ordinary choice. The parameter is a plain
1241    /// `u64` rather than a draft enum because the registry is named differently
1242    /// across these drafts — draft-17 Section 14.5.4's "Data Stream Reset Error
1243    /// Codes" against draft-18's "Stream Reset Error Codes" — and a caller who
1244    /// wants a typed value has [`StreamResetErrorCode::as_u64`].
1245    ///
1246    /// **This is the stream and nothing else.** The endpoint's record of the
1247    /// request does not move, so a response already in flight is still accepted
1248    /// after this returns. [`Connection::cancel_request_stream`] does both and
1249    /// is what a caller holding a connection should reach for; this stays
1250    /// because [`Drop`] has no connection to reach.
1251    ///
1252    /// Idempotent, and it retires the [`Drop`] behaviour: a cancelled handle
1253    /// does nothing further when it goes out of scope. Errors from a stream
1254    /// that was already reset or stopped are swallowed for the same reason —
1255    /// the request is cancelled either way.
1256    ///
1257    /// # Errors
1258    ///
1259    /// [`ConnectionError::Transport`] carrying [`TransportError::Write`] if
1260    /// `code` is outside the QUIC varint range (`0..2^62`). Nothing is sent
1261    /// in that case, and the handle is *not* marked cancelled, so a caller
1262    /// can retry with a representable code.
1263    pub fn cancel(&mut self, code: u64) -> Result<(), ConnectionError> {
1264        if self.cancelled {
1265            return Ok(());
1266        }
1267        // Reject an unrepresentable code before either half is touched, so a
1268        // failed call leaves the stream exactly as it was.
1269        if code > MAX_QUIC_VARINT {
1270            return Err(ConnectionError::Transport(TransportError::Write(format!(
1271                "error code {code} exceeds the varint range"
1272            ))));
1273        }
1274        self.cancelled = true;
1275        // Already-finished or already-reset halves report StreamClosed; the
1276        // request ends up cancelled regardless, so neither is worth raising.
1277        let _ = self.send.reset(code);
1278        let _ = self.recv.stop(code);
1279        Ok(())
1280    }
1281
1282    /// Wait for the peer to cancel this request, consuming nothing.
1283    ///
1284    /// A caller applying backpressure is deliberately not calling
1285    /// [`Connection::recv_on_request_stream`], which is the only other place a
1286    /// peer reset surfaces — so without this the abandonment goes unobserved
1287    /// for as long as the backpressure lasts. This grants no flow-control
1288    /// credit and is cancel-safe.
1289    ///
1290    /// Returns `Ok(Some(code))` with the peer's application error code, or
1291    /// `Ok(None)` meaning **no reset is observable, now or ever — stop
1292    /// asking**. A caller that re-polls after `Ok(None)` spins.
1293    ///
1294    /// Like [`cancel`](Self::cancel), this records nothing at the endpoint.
1295    /// [`Connection::peer_cancelled_on_request_stream`] is the same wait with
1296    /// the record attached.
1297    ///
1298    /// On WebTransport this always answers `Ok(None)`: `wtransport` exposes no
1299    /// reset-only observable, so a WebTransport caller learns of a peer cancel
1300    /// on its next read and not before.
1301    pub async fn peer_cancelled(&mut self) -> Result<Option<u64>, ConnectionError> {
1302        self.recv.received_reset().await
1303    }
1304
1305    /// Finish the send half cleanly, leaving the receive half open.
1306    ///
1307    /// Whether a requester may FIN before its response arrives is not settled
1308    /// by anything this implementation can check, so no request helper calls
1309    /// this and the default is to leave the send half open for the request's
1310    /// life. It is offered for a caller that knows its peer.
1311    ///
1312    /// A finished handle, like a cancelled one, does nothing further on
1313    /// [`Drop`].
1314    pub async fn finish(&mut self) -> Result<(), ConnectionError> {
1315        if self.finished || self.cancelled {
1316            return Ok(());
1317        }
1318        self.finished = true;
1319        self.send.finish().await
1320    }
1321}
1322
1323impl Drop for RequestStream {
1324    /// Reset the request unless it was already cancelled or finished.
1325    ///
1326    /// See the type-level note: the default drop would FIN the send half,
1327    /// which claims a clean end for a request the caller walked away from.
1328    ///
1329    /// The code says which walking away it was. A stream this endpoint opened
1330    /// is cancelled — [`REQUEST_CANCELLED`] — which is the requester act
1331    /// draft-18 Section 3.3.2 describes. A stream the peer opened is reset
1332    /// with [`REQUEST_UNANSWERED`] whether or not a response was already
1333    /// written: before one, the request was never served; after one, the
1334    /// obligations that follow it are still outstanding.
1335    fn drop(&mut self) {
1336        if self.cancelled || self.finished {
1337            return;
1338        }
1339        let code = match self.origin {
1340            RequestOrigin::Local => REQUEST_CANCELLED,
1341            RequestOrigin::Peer => REQUEST_UNANSWERED,
1342        };
1343        let _ = self.send.reset(code);
1344        let _ = self.recv.stop(code);
1345    }
1346}
1347
1348/// Holds a peer-opened stream pair while its first message is being read, and
1349/// puts it back on the connection's queue if that read is abandoned.
1350///
1351/// [`Connection::accept_request_stream`] awaits a whole control message, and a
1352/// caller may drop that future — a `select!` against a shutdown signal is the
1353/// ordinary reason. Without this the stream, and every byte already read off
1354/// it into the reader's buffer, would go with the future: the peer would see a
1355/// request stream reset for no reason it could act on.
1356///
1357/// [`Drop`] is the only place this can run, because a cancelled future is
1358/// never polled again. Every path that finishes — success or error — takes the
1359/// pair out first, so a pair still present when this drops was cancelled.
1360struct PendingInbound<'a> {
1361    pair: Option<(FramedSendStream, FramedRecvStream)>,
1362    queue: &'a Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1363}
1364
1365impl Drop for PendingInbound<'_> {
1366    fn drop(&mut self) {
1367        if let Some(pair) = self.pair.take() {
1368            // Front, not back: this stream arrived before anything still
1369            // queued behind it, and a partially read message must not be
1370            // handed out after a stream that arrived later.
1371            self.queue.lock().unwrap_or_else(|p| p.into_inner()).push_front(pair);
1372        }
1373    }
1374}
1375
1376/// The largest value a QUIC application error code can carry, `2^62 - 1`.
1377///
1378/// Checked by [`RequestStream::cancel`] before either half of the stream is
1379/// touched, so an unrepresentable code cannot half-cancel a request.
1380const MAX_QUIC_VARINT: u64 = (1u64 << 62) - 1;
1381
1382/// A live MoQT connection over QUIC or WebTransport, combining the endpoint
1383/// state machine with actual network I/O.
1384pub struct Connection {
1385    transport: Transport,
1386    endpoint: Endpoint,
1387    draft: DraftVersion,
1388    control_send: Option<FramedSendStream>,
1389    control_recv: Option<FramedRecvStream>,
1390    observer: Option<Box<dyn ConnectionObserver>>,
1391    /// Setup events buffered during `connect()` and replayed when an
1392    /// observer attaches via `set_observer` — without this, an observer
1393    /// attached after `connect` returns would never see the handshake.
1394    pending_events: Vec<ClientEvent>,
1395    /// The server's half of the setup handshake, kept whole.
1396    ///
1397    /// The endpoint acts on the parameters it recognises and retains none of
1398    /// them, and which parameters a server sends — in what order, with what
1399    /// values — is the sharpest thing a session says about the implementation
1400    /// behind it.
1401    server_setup: AnyControlMessage,
1402    /// The framed wire bytes of [`Self::server_setup`].
1403    server_setup_raw: Option<Vec<u8>>,
1404    /// Unidirectional streams accepted while `connect` was looking for the
1405    /// peer's control stream, in arrival order.
1406    ///
1407    /// Data streams are allowed to arrive before the control streams on this
1408    /// draft, so the search cannot assume the first unidirectional stream is
1409    /// the control one — and dropping the ones that are not would silently
1410    /// lose objects the peer already sent.
1411    /// [`accept_subgroup_stream`](Connection::accept_subgroup_stream) empties
1412    /// this before it accepts anything new.
1413    ///
1414    /// Behind a mutex because that method takes `&self`. The lock is only
1415    /// ever held for a `pop_front`, never across an await.
1416    deferred_uni: Mutex<VecDeque<FramedRecvStream>>,
1417    /// Bidirectional streams the peer opened that
1418    /// [`accept_request_stream`](Connection::accept_request_stream) took off
1419    /// the transport but did not finish reading a first message from, because
1420    /// its future was dropped. In arrival order.
1421    ///
1422    /// Without this a caller could not put `accept_request_stream` in a
1423    /// `select!` at all: losing the race would lose a stream the peer had
1424    /// already opened and, with it, whatever of the request had arrived.
1425    /// [`accept_request_stream`](Connection::accept_request_stream) empties
1426    /// this before it accepts anything new.
1427    ///
1428    /// Behind a mutex for the same reason `deferred_uni` is: the lock is only
1429    /// ever held for a push or a pop, never across an await.
1430    pending_inbound: Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
1431}
1432
1433impl Connection {
1434    /// Connect to a MoQT server as a client.
1435    ///
1436    /// Establishes a QUIC or WebTransport connection (based on
1437    /// `config.transport`), brings up the control plane, performs the SETUP
1438    /// handshake, and returns a ready-to-use connection.
1439    ///
1440    /// # The control plane is a pair of unidirectional streams
1441    ///
1442    /// Draft-18 Section 3.3: "MOQT uses a pair of unidirectional streams for
1443    /// creating the session and exchanging control messages. Each peer opens
1444    /// one control stream beginning with a SETUP message." So each direction
1445    /// is a separate stream opened by the peer that writes on it. This opens
1446    /// one with `open_uni` and writes SETUP on it, then finds the peer's by
1447    /// accepting unidirectional streams until one leads with
1448    /// [`CONTROL_STREAM_TYPE`].
1449    ///
1450    /// Nothing is written ahead of the SETUP: 0x2F00 is both the SETUP
1451    /// message type and the unidirectional stream type for a control stream,
1452    /// so the message's own first field is the stream header. See
1453    /// [`CONTROL_STREAM_TYPE`].
1454    ///
1455    /// A bidirectional stream is *not* the control stream here — the same
1456    /// section makes it a request stream, one that begins with TRACK_STATUS,
1457    /// SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE or
1458    /// SUBSCRIBE_TRACKS: "Bidirectional streams MUST NOT begin with any other
1459    /// message type unless negotiated. If they do, the peer MUST close the
1460    /// Session with a PROTOCOL_VIOLATION." A SETUP written on a bidirectional
1461    /// stream is exactly that case, so a peer that enforces the topology
1462    /// answers it by closing the session.
1463    ///
1464    /// # Unidirectional streams that arrive before the peer's control stream
1465    ///
1466    /// They are kept, not dropped. Section 3.3 expects them: "Unidirectional
1467    /// streams containing Objects or bidirectional stream(s) beginning with a
1468    /// request message could arrive prior to the control streams, in which
1469    /// case the data SHOULD be buffered until both control streams arrive and
1470    /// setup is complete." Each such stream is set aside and handed to
1471    /// [`accept_subgroup_stream`](Self::accept_subgroup_stream) in arrival
1472    /// order, ahead of any newly accepted stream. Only the leading type
1473    /// varint is read from them here; the rest stays on the transport, unread
1474    /// and still flow-controlled, so nothing is buffered in this process
1475    /// beyond those few bytes.
1476    ///
1477    /// One limit worth knowing: the search waits for each stream's type
1478    /// varint in turn, so a peer that opens a unidirectional stream and then
1479    /// writes nothing on it stalls the handshake behind that stream.
1480    pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
1481        // PATH is for native QUIC only, and the transport is known here and
1482        // nowhere further in. Refusing before dialling means a session that
1483        // the server would close on sight is never opened.
1484        setup::validate_client_path_transport(
1485            &config.setup_parameters,
1486            matches!(config.transport, TransportType::WebTransport { .. }),
1487        )
1488        .map_err(EndpointError::from)?;
1489
1490        let transport = match &config.transport {
1491            TransportType::Quic => Self::connect_quic(addr, &config).await?,
1492            TransportType::WebTransport { url } => {
1493                let url = url.clone();
1494                Self::connect_webtransport(&url, &config).await?
1495            }
1496        };
1497
1498        Self::adopt(transport, config).await
1499    }
1500
1501    /// Run the MoQT setup handshake over a transport somebody else established.
1502    ///
1503    /// For choosing the draft from what the server selected: dial once through
1504    /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
1505    /// connection to the module its answer names. [`Self::connect`] cannot do
1506    /// this — it derives its single ALPN from the draft it was given.
1507    ///
1508    /// `config.draft` must match this module. The transport is adopted as
1509    /// given; nothing here re-checks the ALPN it was negotiated with.
1510    pub async fn adopt(
1511        transport: Transport,
1512        config: ClientConfig,
1513    ) -> Result<Self, ConnectionError> {
1514        let draft = config.draft;
1515        // PATH is for native QUIC only, and the transport is known here and
1516        // nowhere further in. Refusing before dialling means a session that
1517        // the server would close on sight is never opened.
1518        setup::validate_client_path_transport(
1519            &config.setup_parameters,
1520            matches!(config.transport, TransportType::WebTransport { .. }),
1521        )
1522        .map_err(EndpointError::from)?;
1523
1524        // Send half of the control plane: one unidirectional stream whose
1525        // first message is SETUP, which is also its stream header.
1526        let send = transport.open_uni().await?;
1527        let mut control_send = FramedSendStream::new(send, draft);
1528
1529        // Perform setup handshake (draft-18: no versions)
1530        let mut endpoint = Endpoint::new(Role::Client);
1531        endpoint.connect()?;
1532        let setup_msg = endpoint.send_setup(config.setup_parameters.clone())?;
1533        let any_setup = AnyControlMessage::Draft18(setup_msg);
1534        let raw_setup = control_send.write_control(&any_setup).await?;
1535
1536        // Receive half: the peer's control stream is whichever unidirectional
1537        // stream leads with CONTROL_STREAM_TYPE.
1538        let mut deferred_uni: VecDeque<FramedRecvStream> = VecDeque::new();
1539        let mut control_recv = loop {
1540            let recv = transport.accept_uni().await?;
1541            let mut framed = FramedRecvStream::new(recv, draft);
1542            match framed.peek_stream_type().await {
1543                Ok(CONTROL_STREAM_TYPE) => break framed,
1544                // Every other type is a data stream — and so is a stream that
1545                // ended or failed before its type arrived, not because it is
1546                // one but because there is nothing left to decide with. The
1547                // data path sees the same end one read later and reports it
1548                // the way it reports every other. Treating it as the control
1549                // stream would hand the session's control plane to a stream
1550                // that carried nothing.
1551                _ => deferred_uni.push_back(framed),
1552            }
1553        };
1554
1555        let (server_setup, raw_server_setup) = control_recv.read_control(true).await?;
1556        // Unified SETUP in draft-18: server responds with the same message type.
1557        match &server_setup {
1558            AnyControlMessage::Draft18(ControlMessage::Setup(ref s)) => {
1559                endpoint.receive_setup(s)?;
1560            }
1561            _ => {
1562                return Err(ConnectionError::Endpoint(EndpointError::NotActive));
1563            }
1564        }
1565
1566        let pending_events = vec![
1567            ClientEvent::ControlMessage {
1568                direction: Direction::Send,
1569                message: any_setup,
1570                stream_id: None,
1571                raw: Some(raw_setup),
1572            },
1573            ClientEvent::ControlMessage {
1574                direction: Direction::Receive,
1575                message: server_setup.clone(),
1576                stream_id: None,
1577                raw: raw_server_setup.clone(),
1578            },
1579            ClientEvent::SetupComplete { negotiated_version: 0xff000000 + 18 },
1580        ];
1581
1582        Ok(Self {
1583            transport,
1584            endpoint,
1585            draft,
1586            control_send: Some(control_send),
1587            control_recv: Some(control_recv),
1588            observer: None,
1589            pending_events,
1590            server_setup,
1591            server_setup_raw: raw_server_setup,
1592            deferred_uni: Mutex::new(deferred_uni),
1593            pending_inbound: Mutex::new(VecDeque::new()),
1594        })
1595    }
1596
1597    /// Establish a raw QUIC connection.
1598    ///
1599    /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
1600    /// TLS and endpoint setup.
1601    async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
1602        let (transport, _negotiated) = crate::transport::dial_quic(
1603            addr,
1604            &crate::transport::QuicDialOptions {
1605                skip_cert_verification: config.skip_cert_verification,
1606                ca_certs: config.ca_certs.clone(),
1607                ..crate::transport::QuicDialOptions::new(config.alpn())
1608            },
1609        )
1610        .await?;
1611        Ok(transport)
1612    }
1613
1614    /// Establish a WebTransport connection.
1615    ///
1616    /// [`crate::transport::dial_webtransport`] holds the TLS and endpoint
1617    /// setup, exactly as `connect_quic` above defers its own. That is not
1618    /// only deduplication: both dials must trust the same roots. Settling trust
1619    /// at this call site instead — from `wtransport`'s own builder settings, or
1620    /// from a second config of this draft's own — puts the decision in two
1621    /// places, where it can stop matching what the QUIC dial trusts, so one
1622    /// relay would pass on one transport and fail on the other and a caller's
1623    /// private CA would reach only the dials whose call site installed it.
1624    /// Both ask the same function what to trust.
1625    #[cfg(feature = "webtransport")]
1626    async fn connect_webtransport(
1627        url: &str,
1628        config: &ClientConfig,
1629    ) -> Result<Transport, ConnectionError> {
1630        Ok(crate::transport::dial_webtransport(
1631            url,
1632            &crate::transport::QuicDialOptions {
1633                skip_cert_verification: config.skip_cert_verification,
1634                ca_certs: config.ca_certs.clone(),
1635                // The draft's own protocol identifier, which this draft
1636                // negotiates in `WT-Available-Protocols` rather than in
1637                // CLIENT_SETUP: "The client includes MOQT protocol identifiers
1638                // in the WT-Available-Protocols header". `config.alpn()` above
1639                // is `h3`, which is the HTTP/3 name and settles no version.
1640                wt_protocols: vec![config.draft.quic_alpn().to_vec()],
1641                ..crate::transport::QuicDialOptions::new(config.alpn())
1642            },
1643        )
1644        .await?)
1645    }
1646
1647    /// Stub for when the webtransport feature is not enabled.
1648    #[cfg(not(feature = "webtransport"))]
1649    async fn connect_webtransport(
1650        _url: &str,
1651        _config: &ClientConfig,
1652    ) -> Result<Transport, ConnectionError> {
1653        Err(ConnectionError::Transport(TransportError::Connect(
1654            "webtransport feature not enabled".into(),
1655        )))
1656    }
1657
1658    // -- Observer ---------------------------------------------------
1659
1660    /// Attach an observer. Buffered handshake events from `connect()` are
1661    /// flushed in arrival order before this returns.
1662    pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
1663        self.observer = Some(observer);
1664        for event in self.pending_events.drain(..) {
1665            if let Some(ref obs) = self.observer {
1666                obs.on_event_owned(event);
1667            }
1668        }
1669    }
1670
1671    /// Remove the observer.
1672    pub fn clear_observer(&mut self) {
1673        self.observer = None;
1674    }
1675
1676    /// Emit an event to the observer, if one is attached.
1677    fn emit(&self, event: ClientEvent) {
1678        if let Some(ref obs) = self.observer {
1679            obs.on_event_owned(event);
1680        }
1681    }
1682
1683    // -- Control message I/O ----------------------------------------
1684
1685    /// Send a control message on the control stream.
1686    ///
1687    /// Wraps the draft-18 message in `AnyControlMessage::Draft18` for
1688    /// framing. This is the route for the messages that belong to the session
1689    /// rather than to one request: GOAWAY, NAMESPACE, NAMESPACE_DONE,
1690    /// PUBLISH_BLOCKED and REQUEST_UPDATE, the last of which carries its own
1691    /// request id and is handled on the control stream by the peer's endpoint.
1692    /// SETUP is written by [`connect`](Self::connect) and is the control
1693    /// stream's own type varint.
1694    ///
1695    /// # Requests are refused here
1696    ///
1697    /// Draft-18 Section 3.3 keeps requests off the control plane: "In addition
1698    /// to the control streams, this specification uses bidirectional streams
1699    /// to carry requests. A request stream begins with one of these seven
1700    /// message types: TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH,
1701    /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS." The
1702    /// response comes back on that same bidirectional stream, and resetting it
1703    /// cancels the request (Section 3.3.1).
1704    ///
1705    /// Handing one of those seven to this method returns
1706    /// [`ConnectionError::RequestOnControlStream`] and writes **nothing** —
1707    /// an enforcing peer sees no bytes at all, not a misplaced request. Use
1708    /// the typed helpers, which open a bidirectional stream each:
1709    /// [`subscribe`](Self::subscribe), [`fetch`](Self::fetch),
1710    /// [`joining_fetch`](Self::joining_fetch), [`publish`](Self::publish),
1711    /// [`track_status`](Self::track_status),
1712    /// [`publish_namespace`](Self::publish_namespace),
1713    /// [`subscribe_namespace`](Self::subscribe_namespace) and
1714    /// [`subscribe_tracks`](Self::subscribe_tracks).
1715    ///
1716    /// Response types are still permitted, and should not be used: a response
1717    /// written here will be refused by a conforming peer, whose endpoint
1718    /// answers a response on the control stream with an error. Answer a peer's
1719    /// request on the stream it opened, with the helpers
1720    /// [`accept_request_stream`](Self::accept_request_stream) hands a handle
1721    /// for — [`respond_ok`](Self::respond_ok),
1722    /// [`respond_subscribe_ok`](Self::respond_subscribe_ok),
1723    /// [`respond_fetch_ok`](Self::respond_fetch_ok) and
1724    /// [`respond_error`](Self::respond_error).
1725    /// [`publish_done`](Self::publish_done) does not come through here either:
1726    /// it takes the request stream its PUBLISH opened.
1727    ///
1728    /// NAMESPACE, NAMESPACE_DONE and PUBLISH_BLOCKED are permitted here too and
1729    /// likewise should not be: draft-18's Table 5 marks all three "Request",
1730    /// and Sections 10.16, 10.17 and 10.20 put each on the stream of the
1731    /// request that asked for it. [`namespace_on`](Self::namespace_on),
1732    /// [`namespace_done_on`](Self::namespace_done_on) and
1733    /// [`publish_blocked_on`](Self::publish_blocked_on) are the routes that
1734    /// place them where the table says.
1735    pub async fn send_control(&mut self, msg: &ControlMessage) -> Result<(), ConnectionError> {
1736        let ty = msg.message_type();
1737        if starts_a_request_stream(ty) {
1738            return Err(ConnectionError::RequestOnControlStream(ty));
1739        }
1740        // What this endpoint refuses to receive on the control stream it must
1741        // not write there either, or the client emits frames its own peer half
1742        // would close the session over.
1743        if belongs_on_a_request_stream(ty) {
1744            return Err(ConnectionError::RequestStreamMessageOnControlStream(ty));
1745        }
1746        let any = AnyControlMessage::Draft18(msg.clone());
1747        let send = self.control_send.as_mut().ok_or(ConnectionError::NoControlStream)?;
1748        let raw = send.write_control(&any).await?;
1749        self.emit(ClientEvent::ControlMessage {
1750            direction: Direction::Send,
1751            message: any,
1752            stream_id: None,
1753            raw: Some(raw),
1754        });
1755        Ok(())
1756    }
1757
1758    /// Read the next control message from the control stream.
1759    ///
1760    /// Returns the `AnyControlMessage` and also extracts the draft-18
1761    /// `ControlMessage` for internal endpoint dispatch.
1762    pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
1763        let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
1764        let capture_raw = self.observer.is_some();
1765        let read = recv.read_control(capture_raw).await;
1766        let (any, raw) = match read {
1767            Ok(v) => v,
1768            Err(e) => return Err(self.close_for_codec(e)),
1769        };
1770        if capture_raw {
1771            self.emit(ClientEvent::ControlMessage {
1772                direction: Direction::Receive,
1773                message: any.clone(),
1774                stream_id: None,
1775                raw,
1776            });
1777        }
1778        // Unwrap to draft-18 for the endpoint
1779        match any {
1780            AnyControlMessage::Draft18(msg) => Ok(msg),
1781            // `AnyControlMessage` carries one variant per enabled draft feature. With draft 18 the
1782            // only one enabled the arm above is exhaustive and this rejection arm unreachable.
1783            // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
1784            // naming the other drafts: such a list has to be edited in every draft module
1785            // whenever a draft is added, and a copy that omits one leaves this match
1786            // non-exhaustive.
1787            #[allow(unreachable_patterns)]
1788            _ => Err(ConnectionError::ControlMessageNarrowing),
1789        }
1790    }
1791
1792    /// Read and dispatch the next incoming control message through the
1793    /// endpoint state machine. Returns the decoded message for inspection.
1794    ///
1795    /// Responses never arrive here. Draft-18 responses carry no request id
1796    /// and belong on the request stream that asked for them, so the endpoint
1797    /// refuses a response that turns up on the control stream. Read them with
1798    /// [`recv_on_request_stream`](Self::recv_on_request_stream).
1799    pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
1800        let msg = self.recv_control().await?;
1801        self.endpoint.receive_message(msg.clone()).map_err(|e| self.close_if_session_fatal(e))?;
1802
1803        // Emit draining event if this was a GoAway
1804        if let ControlMessage::GoAway(ref ga) = msg {
1805            self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
1806        }
1807
1808        Ok(msg)
1809    }
1810
1811    // -- Request streams --------------------------------------------
1812
1813    /// Open the bidirectional stream a request will be carried on.
1814    ///
1815    /// Opened *before* the endpoint allocates a request id, so a transport
1816    /// that refuses a new stream — the peer's `initial_max_streams_bidi` is
1817    /// exhausted, the connection is gone — costs nothing. The endpoint has no
1818    /// way to abandon a request it has already allocated, so every failure
1819    /// that can be moved ahead of the allocation is.
1820    ///
1821    /// Nothing is written here. A request stream carries no stream-type
1822    /// header: its first field is the leading message's own type field, which
1823    /// is what [`begin_request`](Self::begin_request) writes.
1824    async fn open_request_bi(
1825        &self,
1826    ) -> Result<(FramedSendStream, FramedRecvStream), ConnectionError> {
1827        let (send, recv) = self.transport.open_bi().await?;
1828        Ok((FramedSendStream::new(send, self.draft), FramedRecvStream::new(recv, self.draft)))
1829    }
1830
1831    /// Reset a request stream that was opened but whose request could not be
1832    /// built, and pass the endpoint's error through.
1833    ///
1834    /// Without this, an endpoint refusal — the session is draining, the
1835    /// request-id range is exhausted — would leave a bidirectional stream
1836    /// open that never carries a first message, and dropping it would FIN it,
1837    /// telling the peer an empty stream ended cleanly.
1838    fn or_abandon<T>(
1839        halves: &mut (FramedSendStream, FramedRecvStream),
1840        built: Result<T, EndpointError>,
1841    ) -> Result<T, ConnectionError> {
1842        match built {
1843            Ok(value) => Ok(value),
1844            Err(e) => {
1845                let _ = halves.0.reset(REQUEST_CANCELLED);
1846                let _ = halves.1.stop(REQUEST_CANCELLED);
1847                Err(ConnectionError::Endpoint(e))
1848            }
1849        }
1850    }
1851
1852    /// Write `msg` as the first message on an opened bidirectional stream and
1853    /// hand back the [`RequestStream`] that owns both halves.
1854    ///
1855    /// This is the one place a request reaches the wire. Every request helper
1856    /// funnels through it, so the ordering — open, allocate, write, emit — is
1857    /// stated once.
1858    ///
1859    /// A failed write resets both halves rather than leaving a half-written
1860    /// request stream behind. What it cannot undo is the endpoint's
1861    /// allocation: the request id and its state machine already exist, and
1862    /// there is no way to retract them, so a write that fails here leaves one
1863    /// pending request the endpoint will never see answered.
1864    async fn begin_request(
1865        &mut self,
1866        halves: (FramedSendStream, FramedRecvStream),
1867        kind: RequestKind,
1868        request_id: VarInt,
1869        msg: &ControlMessage,
1870    ) -> Result<RequestStream, ConnectionError> {
1871        debug_assert_eq!(
1872            msg.message_type(),
1873            kind.message_type(),
1874            "a request stream's first message must be the one its kind names"
1875        );
1876        let (mut send, mut recv) = halves;
1877        let stream_id = send.stream_id();
1878        self.emit(ClientEvent::StreamOpened {
1879            direction: Direction::Send,
1880            stream_kind: StreamKind::Request,
1881            stream_id,
1882        });
1883        let any = AnyControlMessage::Draft18(msg.clone());
1884        let raw = match send.write_control(&any).await {
1885            Ok(raw) => raw,
1886            Err(e) => {
1887                let _ = send.reset(REQUEST_CANCELLED);
1888                let _ = recv.stop(REQUEST_CANCELLED);
1889                return Err(e);
1890            }
1891        };
1892        self.emit(ClientEvent::ControlMessage {
1893            direction: Direction::Send,
1894            message: any,
1895            stream_id: Some(stream_id),
1896            raw: Some(raw),
1897        });
1898        Ok(RequestStream {
1899            send,
1900            recv,
1901            request_id,
1902            kind,
1903            draft: self.draft,
1904            stream_id,
1905            cancelled: false,
1906            finished: false,
1907            origin: RequestOrigin::Local,
1908            responded: false,
1909            fetch_data: None,
1910        })
1911    }
1912
1913    /// Read the next message off a request stream and dispatch it through the
1914    /// endpoint with that stream's own request id.
1915    ///
1916    /// On draft-18 a response carries no request id; the stream is the
1917    /// correlation, so the id comes from the handle and not from the wire.
1918    ///
1919    /// This blocks until a whole message has arrived. Backpressure is per
1920    /// request: a stream nobody reads stays unread, and the peer stays flow
1921    /// controlled on it alone. A peer that reset the stream surfaces as
1922    /// [`ConnectionError::Transport`] carrying
1923    /// [`TransportError::StreamReset`] with the peer's code; a caller that is
1924    /// deliberately not reading should watch
1925    /// [`RequestStream::peer_cancelled`] instead.
1926    ///
1927    /// # Errors
1928    ///
1929    /// [`ConnectionError::Endpoint`] if the message is not one of this
1930    /// draft's response types, or if it does not fit the request's state.
1931    /// The message has already been emitted to the observer by then — what
1932    /// arrived is reported whether or not the endpoint accepts it.
1933    pub async fn recv_on_request_stream(
1934        &mut self,
1935        stream: &mut RequestStream,
1936    ) -> Result<ControlMessage, ConnectionError> {
1937        let capture_raw = self.observer.is_some();
1938        let (any, raw) = match stream.recv.read_control(capture_raw).await {
1939            Ok(read) => read,
1940            Err(e) => {
1941                // A peer that reset this stream cancelled the request on it,
1942                // and this is where a caller reading normally learns of it. The
1943                // record is made and its verdict dropped: the read's own error
1944                // is what the caller has to act on, and returning a state error
1945                // in its place would hide a reset behind it.
1946                if matches!(e, ConnectionError::Transport(TransportError::StreamReset(_))) {
1947                    let _ = self.endpoint.cancel_request(stream.request_id);
1948                }
1949                return Err(e);
1950            }
1951        };
1952        if capture_raw {
1953            self.emit(ClientEvent::ControlMessage {
1954                direction: Direction::Receive,
1955                message: any.clone(),
1956                stream_id: Some(stream.stream_id()),
1957                raw,
1958            });
1959        }
1960        let msg = match any {
1961            AnyControlMessage::Draft18(msg) => Ok::<_, ConnectionError>(msg),
1962            // `AnyControlMessage` carries one variant per enabled draft feature. With draft 18 the
1963            // only one enabled the arm above is exhaustive and this rejection arm unreachable.
1964            // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
1965            // naming the other drafts: such a list has to be edited in every draft module
1966            // whenever a draft is added, and a copy that omits one leaves this match
1967            // non-exhaustive.
1968            #[allow(unreachable_patterns)]
1969            _ => Err(ConnectionError::ControlMessageNarrowing),
1970        }?;
1971        // Which dispatcher this belongs to is decided by who opened the
1972        // stream, not by the message. On a stream this endpoint opened the
1973        // next message is the answer to our request; on one the peer opened it
1974        // cannot be, because we are the one who owes an answer. Feeding a
1975        // peer's REQUEST_UPDATE to the response dispatcher would look up a
1976        // request we never made.
1977        let dispatched = match stream.origin {
1978            RequestOrigin::Local => {
1979                self.endpoint.receive_response_on_stream(stream.request_id, msg.clone())
1980            }
1981            RequestOrigin::Peer => {
1982                self.endpoint.receive_on_peer_request_stream(stream.request_id, msg.clone())
1983            }
1984        };
1985        dispatched.map_err(|e| self.close_if_session_fatal(e))?;
1986        Ok(msg)
1987    }
1988
1989    /// Write a follow-up message on an already-open request stream.
1990    ///
1991    /// The request itself was written when the stream was opened; this is for
1992    /// what comes after it on the same stream, PUBLISH_DONE among them — see
1993    /// [`publish_done`](Self::publish_done), which uses this.
1994    ///
1995    /// It does not refuse any message type. Which messages may follow a
1996    /// request on its own stream is not something this implementation can
1997    /// settle, so the choice is left to the caller rather than guessed at.
1998    pub async fn send_on_request_stream(
1999        &mut self,
2000        stream: &mut RequestStream,
2001        msg: &ControlMessage,
2002    ) -> Result<(), ConnectionError> {
2003        let any = AnyControlMessage::Draft18(msg.clone());
2004        let raw = stream.send.write_control(&any).await?;
2005        self.emit(ClientEvent::ControlMessage {
2006            direction: Direction::Send,
2007            message: any,
2008            stream_id: Some(stream.stream_id()),
2009            raw: Some(raw),
2010        });
2011        Ok(())
2012    }
2013
2014    /// Cancel a request: record it at the endpoint, then terminate its stream.
2015    ///
2016    /// Section 3.3.2 puts the cancel at the stream — "Implementations SHOULD
2017    /// cancel requests by abruptly terminating any directions of a stream that
2018    /// are still open" — while the request's own state lives in the endpoint,
2019    /// so the two have to move together. This is the only place that moves
2020    /// both.
2021    ///
2022    /// The endpoint goes first and the stream is terminated only if it agrees,
2023    /// which is the order every request path here uses: a caller acts on a
2024    /// stream after the endpoint has accepted the step, never before. A refused
2025    /// cancel therefore leaves the stream exactly as it was, and
2026    /// [`RequestStream::cancel`] is still there for a caller that wants the
2027    /// stream reset regardless.
2028    ///
2029    /// Idempotent from both ends: a request that has already ended accepts the
2030    /// cancel and stays where it is, and a handle that has already been
2031    /// cancelled resets nothing a second time.
2032    ///
2033    /// # Errors
2034    ///
2035    /// [`ConnectionError::Endpoint`] if no request carries this stream's id or
2036    /// the request has not been written, and [`ConnectionError::Transport`] if
2037    /// `code` is outside the QUIC varint range — see
2038    /// [`RequestStream::cancel`], which is what sends it.
2039    pub fn cancel_request_stream(
2040        &mut self,
2041        stream: &mut RequestStream,
2042        code: u64,
2043    ) -> Result<(), ConnectionError> {
2044        let recorded = self.endpoint.cancel_request(stream.request_id);
2045        recorded.map_err(|e| self.close_if_session_fatal(e))?;
2046        stream.cancel(code)
2047    }
2048
2049    /// Wait for the peer to cancel this request, and record it if it does.
2050    ///
2051    /// [`RequestStream::peer_cancelled`] with the endpoint's record attached. A
2052    /// caller applying backpressure is deliberately not calling
2053    /// [`recv_on_request_stream`](Self::recv_on_request_stream), which is the
2054    /// other place a peer reset surfaces, so without this the request would end
2055    /// on the wire and stay open in the endpoint's record for as long as the
2056    /// backpressure lasts.
2057    ///
2058    /// Returns what the handle's own method returns; see it for the `Ok(None)`
2059    /// case and for what WebTransport can and cannot observe. Cancel-safe, and
2060    /// it grants no flow-control credit.
2061    pub async fn peer_cancelled_on_request_stream(
2062        &mut self,
2063        stream: &mut RequestStream,
2064    ) -> Result<Option<u64>, ConnectionError> {
2065        let code = stream.peer_cancelled().await?;
2066        if code.is_some() {
2067            // Discarded for the reason the read path discards it: the peer has
2068            // ended the request whatever the record said, and a state error
2069            // here would replace the answer the caller asked for.
2070            let _ = self.endpoint.cancel_request(stream.request_id);
2071        }
2072        Ok(code)
2073    }
2074
2075    // -- Accepting the peer's request streams -----------------------
2076
2077    /// Accept the next bidirectional stream the peer opened, read the request
2078    /// it begins with, and hand back that request and a handle to answer it
2079    /// on.
2080    ///
2081    /// This is the mirror of the request helpers. Where
2082    /// [`subscribe`](Self::subscribe) and its siblings open a stream and write
2083    /// a request, this takes one the peer opened and reads one. Draft-18
2084    /// Section 3.3 puts requests in both directions on bidirectional streams,
2085    /// so a client that only ever calls the helpers can never be published to
2086    /// or subscribed from.
2087    ///
2088    /// The returned [`RequestStream`] carries [`RequestOrigin::Peer`]. Answer
2089    /// it with [`respond_subscribe_ok`](Self::respond_subscribe_ok),
2090    /// [`respond_fetch_ok`](Self::respond_fetch_ok),
2091    /// [`respond_ok`](Self::respond_ok) or
2092    /// [`respond_error`](Self::respond_error), and **hold it for as long as
2093    /// the request lasts** — a subscription's PUBLISH_DONE is written on it,
2094    /// and dropping it resets the stream.
2095    ///
2096    /// # Two refusals, two codes
2097    ///
2098    /// Draft-18 Section 3.3, on a stream that begins with the wrong type:
2099    /// "Bidirectional streams MUST NOT begin with any other message type
2100    /// unless negotiated. If they do, the peer MUST close the Session with a
2101    /// PROTOCOL_VIOLATION." Section 10.1, on the Request ID: "If an endpoint
2102    /// receives a Request ID where the least significant bit is incorrect for
2103    /// the sender, or a duplicate Request ID, it MUST close the session with
2104    /// INVALID_REQUEST_ID." Both are closes of the session on the wire, with
2105    /// different codes, and both happen before this returns — the error handed
2106    /// back reports a session that is already gone, not one the caller must
2107    /// remember to close.
2108    ///
2109    /// # Cancelling this future loses nothing
2110    ///
2111    /// A stream taken off the transport but not yet read is put back on an
2112    /// internal queue, and the next call takes it before accepting anything
2113    /// new — including whatever bytes of the request had already arrived,
2114    /// which live in the stream's own reader. So this is safe to `select!`
2115    /// against a shutdown signal or a timer. See
2116    /// [`pending_inbound_count`](Self::pending_inbound_count).
2117    ///
2118    /// What it is **not** safe to do is run concurrently with another method
2119    /// on the same connection: this takes `&mut self` because registering the
2120    /// peer's request moves endpoint state, and no signature avoids that while
2121    /// the connection owns the endpoint. A caller blocked in
2122    /// [`recv_on_request_stream`](Self::recv_on_request_stream) waiting for
2123    /// its own response is not accepting, and the peer's request streams queue
2124    /// up in the transport behind it. One loop that never blocks indefinitely
2125    /// on a single read is the shape this supports.
2126    ///
2127    /// # Ordering
2128    ///
2129    /// The endpoint is told about the request last, after every step that can
2130    /// fail or be cancelled, and building the handle afterwards cannot fail.
2131    /// This is the inverse of the outbound path's reasoning — it opens the
2132    /// stream before allocating a Request ID for the same reason — and rests
2133    /// on the same fact: the endpoint has no way to abandon a request it has
2134    /// already registered. Registering earlier would let a cancelled accept
2135    /// leave a state machine keyed to a stream nobody holds, and the peer's
2136    /// next use of that Request ID would then be reported as a duplicate — a
2137    /// session close, over an id the peer used exactly once.
2138    ///
2139    /// # Errors
2140    ///
2141    /// - [`ConnectionError::NonRequestOnRequestStream`] — the session has been
2142    ///   closed with PROTOCOL_VIOLATION and the stream reset.
2143    /// - [`ConnectionError::Endpoint`] carrying `RequestId` or
2144    ///   `DuplicateRequestId` — the session has been closed with
2145    ///   INVALID_REQUEST_ID and the stream reset.
2146    /// - [`ConnectionError::Endpoint`] carrying `NotActive` or `Draining` —
2147    ///   the stream is reset, the session is left alone.
2148    /// - [`ConnectionError::Transport`] or [`ConnectionError::Codec`] — the
2149    ///   stream is reset, the session is left alone.
2150    pub async fn accept_request_stream(
2151        &mut self,
2152    ) -> Result<(ControlMessage, RequestStream), ConnectionError> {
2153        let pair = match self.take_pending_inbound() {
2154            Some(pair) => pair,
2155            None => {
2156                let (send, recv) = self.transport.accept_bi().await?;
2157                (FramedSendStream::new(send, self.draft), FramedRecvStream::new(recv, self.draft))
2158            }
2159        };
2160        let capture_raw = self.observer.is_some();
2161
2162        let (any, raw, mut send, mut recv) = {
2163            let mut pending = PendingInbound { pair: Some(pair), queue: &self.pending_inbound };
2164            let read = {
2165                let (_, recv) = pending.pair.as_mut().expect("set on construction");
2166                recv.read_control(capture_raw).await
2167            };
2168            // Taken out before anything can return, so the guard's Drop puts
2169            // the pair back for exactly one reason: this future was cancelled.
2170            let (mut send, mut recv) = pending.pair.take().expect("set on construction");
2171            match read {
2172                Ok((any, raw)) => (any, raw, send, recv),
2173                Err(e) => {
2174                    // A stream whose first message could not be read is not
2175                    // worth queueing: the next accept would fail on it the
2176                    // same way. Reset rather than FIN — nothing was served.
2177                    let _ = send.reset(REQUEST_UNANSWERED);
2178                    let _ = recv.stop(REQUEST_UNANSWERED);
2179                    return Err(e);
2180                }
2181            }
2182        };
2183
2184        // Reported once the request has actually arrived rather than when the
2185        // stream came off the transport, so a cancelled accept that is retried
2186        // does not report the same stream twice.
2187        let stream_id = send.stream_id();
2188        self.emit(ClientEvent::StreamOpened {
2189            direction: Direction::Receive,
2190            stream_kind: StreamKind::Request,
2191            stream_id,
2192        });
2193        if capture_raw {
2194            self.emit(ClientEvent::ControlMessage {
2195                direction: Direction::Receive,
2196                message: any.clone(),
2197                stream_id: Some(stream_id),
2198                raw,
2199            });
2200        }
2201
2202        let msg = match any {
2203            AnyControlMessage::Draft18(msg) => msg,
2204            // `AnyControlMessage` carries one variant per enabled draft feature. With draft 18 the
2205            // only one enabled the arm above is exhaustive and this rejection arm unreachable.
2206            // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
2207            // naming the other drafts: such a list has to be edited in every draft module
2208            // whenever a draft is added, and a copy that omits one leaves this match
2209            // non-exhaustive.
2210            #[allow(unreachable_patterns)]
2211            _ => {
2212                let _ = send.reset(REQUEST_UNANSWERED);
2213                let _ = recv.stop(REQUEST_UNANSWERED);
2214                return Err(ConnectionError::ControlMessageNarrowing);
2215            }
2216        };
2217
2218        let ty = msg.message_type();
2219        let Some(kind) = RequestKind::from_message_type(ty) else {
2220            let err = self.endpoint.refuse_non_request(ty);
2221            self.close_for(&err);
2222            let _ = send.reset(REQUEST_UNANSWERED);
2223            let _ = recv.stop(REQUEST_UNANSWERED);
2224            return Err(ConnectionError::NonRequestOnRequestStream(ty));
2225        };
2226
2227        let request_id = match self.endpoint.receive_request_on_stream(&msg) {
2228            Ok(request_id) => request_id,
2229            Err(e) => {
2230                let _ = send.reset(REQUEST_UNANSWERED);
2231                let _ = recv.stop(REQUEST_UNANSWERED);
2232                return Err(self.close_if_session_fatal(e));
2233            }
2234        };
2235
2236        Ok((
2237            msg,
2238            RequestStream {
2239                send,
2240                recv,
2241                request_id,
2242                kind,
2243                draft: self.draft,
2244                stream_id,
2245                cancelled: false,
2246                finished: false,
2247                origin: RequestOrigin::Peer,
2248                responded: false,
2249                fetch_data: None,
2250            },
2251        ))
2252    }
2253
2254    /// Take the oldest stream pair a cancelled
2255    /// [`accept_request_stream`](Self::accept_request_stream) put back, if any.
2256    ///
2257    /// Synchronous on purpose, like
2258    /// [`take_deferred_uni`](Self::take_deferred_uni): the guard is dropped
2259    /// before the caller awaits, so the lock is never held across a suspension
2260    /// point.
2261    fn take_pending_inbound(&self) -> Option<(FramedSendStream, FramedRecvStream)> {
2262        self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
2263    }
2264
2265    /// How many peer-opened request streams a cancelled
2266    /// [`accept_request_stream`](Self::accept_request_stream) put back and a
2267    /// later call has not yet taken.
2268    ///
2269    /// Zero unless an accept future was dropped mid-read.
2270    pub fn pending_inbound_count(&self) -> usize {
2271        self.pending_inbound.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
2272    }
2273
2274    // -- Answering the peer's requests ------------------------------
2275
2276    /// Write `msg` on the request stream `stream` carries, driving the endpoint
2277    /// first and the wire second.
2278    ///
2279    /// Everything a responder writes goes on the request's own bidirectional
2280    /// stream and never on the control stream: draft-18 responses carry no
2281    /// Request ID, so the stream is the only thing that says what is being
2282    /// answered. Taking the id off the handle rather than from the caller makes
2283    /// that correlation unforgeable.
2284    async fn drive_and_send(
2285        &mut self,
2286        stream: &mut RequestStream,
2287        msg: &ControlMessage,
2288    ) -> Result<(), ConnectionError> {
2289        // A request this endpoint made is answered by the peer, with one
2290        // exception the draft states outright: "A subscriber can also send
2291        // REQUEST_UPDATE to modify parameters of a subscription established
2292        // with PUBLISH", and the receiver of one "MUST respond with exactly one
2293        // REQUEST_OK or REQUEST_ERROR message indicating if the update was
2294        // successful". On a PUBLISH this endpoint sent, that receiver is this
2295        // endpoint, so the one response it may write on a stream of its own is
2296        // the answer to an update waiting there.
2297        let answers_an_update =
2298            matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_))
2299                && self.endpoint.has_unanswered_update(stream.request_id);
2300        if stream.origin != RequestOrigin::Peer && !answers_an_update {
2301            return Err(ConnectionError::RespondedToOwnRequest(stream.request_id.into_inner()));
2302        }
2303        // The endpoint first, so a message that does not fit the request's
2304        // state is refused before any of it reaches the wire. What this cannot
2305        // undo is a write that fails afterwards, which leaves the state
2306        // machine one step ahead of the peer — the same asymmetry
2307        // `begin_request` carries on the outbound side.
2308        self.endpoint.send_response_on_stream(stream.request_id, msg)?;
2309        self.send_on_request_stream(stream, msg).await
2310    }
2311
2312    /// [`drive_and_send`](Self::drive_and_send) for a message that *answers*
2313    /// the request, marking the handle as responded and optionally finishing
2314    /// the send half.
2315    ///
2316    /// `fin` is true only for REQUEST_ERROR. See
2317    /// [`respond_error`](Self::respond_error).
2318    async fn respond(
2319        &mut self,
2320        stream: &mut RequestStream,
2321        msg: ControlMessage,
2322        fin: bool,
2323    ) -> Result<(), ConnectionError> {
2324        self.drive_and_send(stream, &msg).await?;
2325        stream.responded = true;
2326        // `fin` says the message ends the exchange; owing a termination says
2327        // it does not, whatever the message looks like. A REQUEST_ERROR
2328        // answering an update is the case where the two disagree, and the
2329        // draft asks for a PUBLISH_DONE after it that a finished send half
2330        // could not carry.
2331        // Section 10.9.1: "When a REQUEST_UPDATE fails for a FETCH, the
2332        // publisher MUST reset the FETCH data stream." A REQUEST_ERROR on a
2333        // fetch that has already been answered can only be answering an
2334        // update, because the request's own answer was the FETCH_OK; one
2335        // before that refuses the fetch itself, and there is no data stream
2336        // open to reset.
2337        if fin && stream.kind == RequestKind::Fetch && stream.responded {
2338            stream.reset_fetch_data();
2339        }
2340        if fin && !self.endpoint.owes_update_failure(stream.request_id) {
2341            stream.finish().await?;
2342        }
2343        Ok(())
2344    }
2345
2346    /// Answer a peer's PUBLISH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE,
2347    /// SUBSCRIBE_TRACKS or TRACK_STATUS with REQUEST_OK.
2348    ///
2349    /// Draft-18 Section 10.5 folded PUBLISH_OK into REQUEST_OK, so this is the
2350    /// route for a PUBLISH the peer offered as well — draft-17 had a message of
2351    /// its own for that case and a helper to match.
2352    ///
2353    /// The send half is left open. A SUBSCRIBE_NAMESPACE responder still owes
2354    /// the peer the namespaces it accepted, and a PUBLISH responder is now the
2355    /// subscriber of a live subscription, so finishing here would end the
2356    /// request before it had been served; a TRACK_STATUS responder owes nothing
2357    /// further and may call [`RequestStream::finish`] straight after.
2358    ///
2359    /// # Errors
2360    ///
2361    /// [`ConnectionError::RespondedToOwnRequest`] if `stream` is one this
2362    /// endpoint opened, and [`ConnectionError::Endpoint`] if no request of a
2363    /// kind REQUEST_OK answers is pending on it. Nothing is written either
2364    /// way.
2365    pub async fn respond_ok(
2366        &mut self,
2367        stream: &mut RequestStream,
2368        response: RequestOk,
2369    ) -> Result<(), ConnectionError> {
2370        self.respond(stream, ControlMessage::RequestOk(response), false).await
2371    }
2372
2373    /// Answer a peer's SUBSCRIBE with SUBSCRIBE_OK.
2374    ///
2375    /// The send half is left open, and it must be: this endpoint is now the
2376    /// publisher of an established subscription and owes it a PUBLISH_DONE,
2377    /// which travels on this same stream —
2378    /// [`publish_done_on`](Self::publish_done_on).
2379    pub async fn respond_subscribe_ok(
2380        &mut self,
2381        stream: &mut RequestStream,
2382        response: SubscribeOk,
2383    ) -> Result<(), ConnectionError> {
2384        self.respond(stream, ControlMessage::SubscribeOk(response), false).await
2385    }
2386
2387    /// Answer a peer's FETCH with FETCH_OK.
2388    ///
2389    /// The send half is left open. The fetched objects travel on separate
2390    /// unidirectional streams, so a fetch responder may call
2391    /// [`RequestStream::finish`] as soon as this returns; it is not done here
2392    /// because nothing about FETCH_OK says the responder has no more to write.
2393    pub async fn respond_fetch_ok(
2394        &mut self,
2395        stream: &mut RequestStream,
2396        response: FetchOk,
2397    ) -> Result<(), ConnectionError> {
2398        self.respond(stream, ControlMessage::FetchOk(response), false).await
2399    }
2400
2401    /// Reject a peer's request with REQUEST_ERROR, and finish the send half.
2402    ///
2403    /// The FIN is part of the act, not a convenience: draft-18 Section 3.3.2
2404    /// says "When an endpoint rejects a request without performing any
2405    /// application processing, it SHOULD send a REQUEST_ERROR and FIN the
2406    /// stream." It is also the one response that can be finished immediately,
2407    /// because a rejected request leaves nothing further to send — every
2408    /// success path owes the peer something more.
2409    ///
2410    /// A finished handle does nothing further on [`Drop`], so the rejected
2411    /// stream is not then reset.
2412    pub async fn respond_error(
2413        &mut self,
2414        stream: &mut RequestStream,
2415        response: RequestError,
2416    ) -> Result<(), ConnectionError> {
2417        self.respond(stream, ControlMessage::RequestError(response), true).await
2418    }
2419
2420    /// End a subscription this endpoint accepted, on the stream the peer's
2421    /// SUBSCRIBE opened.
2422    ///
2423    /// The mirror of [`publish_done`](Self::publish_done), which ends a
2424    /// publication this endpoint offered with PUBLISH. Both write PUBLISH_DONE
2425    /// on a request stream and take the Request ID off the handle; they differ
2426    /// in which state machine moves, and therefore in which one refuses.
2427    pub async fn publish_done_on(
2428        &mut self,
2429        stream: &mut RequestStream,
2430        status_code: VarInt,
2431        stream_count: VarInt,
2432        reason_phrase: Vec<u8>,
2433    ) -> Result<(), ConnectionError> {
2434        let msg = ControlMessage::PublishDone(moqtap_codec::draft18::message::PublishDone {
2435            status_code,
2436            stream_count,
2437            reason_phrase,
2438        });
2439        self.respond(stream, msg, false).await
2440    }
2441
2442    /// Report a namespace on the stream a peer's SUBSCRIBE_NAMESPACE opened.
2443    ///
2444    /// Draft-18 Table 5 marks NAMESPACE (0x8) "Request", and Section 10.16 says
2445    /// why: it "is sent on the response stream of a SUBSCRIBE_NAMESPACE
2446    /// request", carrying only the suffix left after the prefix that request
2447    /// named. So it goes here and not through
2448    /// [`send_control`](Self::send_control).
2449    ///
2450    /// This is not the response — [`respond_ok`](Self::respond_ok) is, and it
2451    /// must come first, since a namespace subscription that has not been
2452    /// accepted has nothing to report on. The handle is not marked as
2453    /// responded and the send half stays open: more namespaces may follow.
2454    pub async fn namespace_on(
2455        &mut self,
2456        stream: &mut RequestStream,
2457        message: Namespace,
2458    ) -> Result<(), ConnectionError> {
2459        self.drive_and_send(stream, &ControlMessage::Namespace(message)).await
2460    }
2461
2462    /// Report that a namespace is finished, on the stream a peer's
2463    /// SUBSCRIBE_NAMESPACE opened.
2464    ///
2465    /// Table 5 marks NAMESPACE_DONE (0xE) "Request" for the same reason
2466    /// [`namespace_on`](Self::namespace_on) gives. Section 10.17: it says the
2467    /// publisher will stop serving new subscriptions for that one namespace,
2468    /// which leaves the namespace subscription itself running, so the send half
2469    /// stays open here too.
2470    pub async fn namespace_done_on(
2471        &mut self,
2472        stream: &mut RequestStream,
2473        message: NamespaceDone,
2474    ) -> Result<(), ConnectionError> {
2475        self.drive_and_send(stream, &ControlMessage::NamespaceDone(message)).await
2476    }
2477
2478    /// Report a track that cannot be published, on the stream a peer's
2479    /// SUBSCRIBE_TRACKS opened.
2480    ///
2481    /// Table 5 marks PUBLISH_BLOCKED (0xF) "Request", and Section 10.20 says
2482    /// "All PUBLISH_BLOCKED messages are in response to a SUBSCRIBE_TRACKS" —
2483    /// so this needs the SUBSCRIBE_TRACKS stream, which is what distinguishes
2484    /// it from [`namespace_on`](Self::namespace_on) and its sibling. The rest
2485    /// of the subscription is unaffected, so the send half stays open.
2486    pub async fn publish_blocked_on(
2487        &mut self,
2488        stream: &mut RequestStream,
2489        message: PublishBlocked,
2490    ) -> Result<(), ConnectionError> {
2491        self.drive_and_send(stream, &ControlMessage::PublishBlocked(message)).await
2492    }
2493
2494    // -- Subscribe flow ---------------------------------------------
2495
2496    /// Send a SUBSCRIBE on a bidirectional stream of its own.
2497    ///
2498    /// The returned [`RequestStream`] is where SUBSCRIBE_OK, REQUEST_ERROR
2499    /// and later PUBLISH_DONE arrive — read them with
2500    /// [`recv_on_request_stream`](Self::recv_on_request_stream). **Hold it for
2501    /// the subscription's life**: dropping it resets the stream, which
2502    /// cancels the subscription.
2503    pub async fn subscribe(
2504        &mut self,
2505        track_namespace: TrackNamespace,
2506        track_name: Vec<u8>,
2507        parameters: Vec<KeyValuePair>,
2508    ) -> Result<RequestStream, ConnectionError> {
2509        let mut halves = self.open_request_bi().await?;
2510        let (req_id, msg) = Self::or_abandon(
2511            &mut halves,
2512            self.endpoint.subscribe(track_namespace, track_name, parameters),
2513        )?;
2514        self.begin_request(halves, RequestKind::Subscribe, req_id, &msg).await
2515    }
2516
2517    // Draft-18 keeps draft-17's removal of UNSUBSCRIBE. Subscribers end a
2518    // subscription by resetting its request stream — `RequestStream::cancel`
2519    // — or wait for PublishDone.
2520
2521    // -- Fetch flow -------------------------------------------------
2522
2523    /// Send a standalone FETCH on a bidirectional stream of its own.
2524    ///
2525    /// FETCH_OK or REQUEST_ERROR comes back on the returned
2526    /// [`RequestStream`]; the fetched objects arrive on separate
2527    /// unidirectional data streams. Dropping the handle cancels the fetch.
2528    #[allow(clippy::too_many_arguments)]
2529    pub async fn fetch(
2530        &mut self,
2531        track_namespace: TrackNamespace,
2532        track_name: Vec<u8>,
2533        start_group: VarInt,
2534        start_object: VarInt,
2535        end_group: VarInt,
2536        end_object: VarInt,
2537        parameters: Vec<KeyValuePair>,
2538    ) -> Result<RequestStream, ConnectionError> {
2539        let mut halves = self.open_request_bi().await?;
2540        let (req_id, msg) = Self::or_abandon(
2541            &mut halves,
2542            self.endpoint.fetch(
2543                track_namespace,
2544                track_name,
2545                start_group,
2546                start_object,
2547                end_group,
2548                end_object,
2549                parameters,
2550            ),
2551        )?;
2552        self.begin_request(halves, RequestKind::Fetch, req_id, &msg).await
2553    }
2554
2555    /// Send a Relative Joining Fetch (Fetch Type 0x2) on a bidirectional
2556    /// stream of its own.
2557    ///
2558    /// A joining FETCH names an existing subscription's request id but is
2559    /// still a FETCH, so it opens its own request stream rather than sharing
2560    /// the subscription's.
2561    ///
2562    /// `joining_start` counts groups back from the subscription's largest
2563    /// group. To name the starting group outright, use
2564    /// [`absolute_joining_fetch`](Self::absolute_joining_fetch).
2565    pub async fn joining_fetch(
2566        &mut self,
2567        joining_request_id: VarInt,
2568        joining_start: VarInt,
2569        parameters: Vec<KeyValuePair>,
2570    ) -> Result<RequestStream, ConnectionError> {
2571        let mut halves = self.open_request_bi().await?;
2572        let (req_id, msg) = Self::or_abandon(
2573            &mut halves,
2574            self.endpoint.joining_fetch(joining_request_id, joining_start, parameters),
2575        )?;
2576        self.begin_request(halves, RequestKind::Fetch, req_id, &msg).await
2577    }
2578
2579    /// Send an Absolute Joining Fetch (Fetch Type 0x3) on a bidirectional
2580    /// stream of its own.
2581    ///
2582    /// Here `joining_start` is the group to begin at rather than an offset:
2583    /// draft-18 Section 10.12.2.1 has the publisher set the Start Location to
2584    /// {Joining Start, 0}.
2585    pub async fn absolute_joining_fetch(
2586        &mut self,
2587        joining_request_id: VarInt,
2588        joining_start: VarInt,
2589        parameters: Vec<KeyValuePair>,
2590    ) -> Result<RequestStream, ConnectionError> {
2591        let mut halves = self.open_request_bi().await?;
2592        let (req_id, msg) = Self::or_abandon(
2593            &mut halves,
2594            self.endpoint.absolute_joining_fetch(joining_request_id, joining_start, parameters),
2595        )?;
2596        self.begin_request(halves, RequestKind::Fetch, req_id, &msg).await
2597    }
2598
2599    // Draft-18 keeps draft-17's removal of FETCH_CANCEL. Fetchers abort with
2600    // `RequestStream::cancel`, which resets the request stream.
2601
2602    // -- Namespace flows --------------------------------------------
2603
2604    /// Send a SUBSCRIBE_NAMESPACE on a bidirectional stream of its own.
2605    ///
2606    /// Draft-18 split the draft-17 SUBSCRIBE_NAMESPACE into two messages.
2607    /// This call sends the renumbered SUBSCRIBE_NAMESPACE (type 0x50), which
2608    /// subscribes to NAMESPACE / NAMESPACE_DONE announcements only. To
2609    /// receive PUBLISH messages for matching tracks, use
2610    /// [`Self::subscribe_tracks`] instead.
2611    ///
2612    /// Draft-17's `subscribe_options` field went with the split and this
2613    /// signature does not carry it.
2614    pub async fn subscribe_namespace(
2615        &mut self,
2616        namespace_prefix: TrackNamespace,
2617        parameters: Vec<KeyValuePair>,
2618    ) -> Result<RequestStream, ConnectionError> {
2619        let mut halves = self.open_request_bi().await?;
2620        let (req_id, msg) = Self::or_abandon(
2621            &mut halves,
2622            self.endpoint.subscribe_namespace(namespace_prefix, parameters),
2623        )?;
2624        self.begin_request(halves, RequestKind::SubscribeNamespace, req_id, &msg).await
2625    }
2626
2627    /// Send a SUBSCRIBE_TRACKS (type 0x51, new in draft-18) on a bidirectional
2628    /// stream of its own. Causes the relay to PUBLISH matching tracks back to
2629    /// us.
2630    ///
2631    /// Draft-18 Section 3.3 lists SUBSCRIBE_TRACKS among the message types a
2632    /// bidirectional stream may begin with, so it is a request like the other
2633    /// six and not a control-stream message. It has no draft-17 counterpart.
2634    pub async fn subscribe_tracks(
2635        &mut self,
2636        namespace_prefix: TrackNamespace,
2637        parameters: Vec<KeyValuePair>,
2638    ) -> Result<RequestStream, ConnectionError> {
2639        let mut halves = self.open_request_bi().await?;
2640        let (req_id, msg) = Self::or_abandon(
2641            &mut halves,
2642            self.endpoint.subscribe_tracks(namespace_prefix, parameters),
2643        )?;
2644        self.begin_request(halves, RequestKind::SubscribeTracks, req_id, &msg).await
2645    }
2646
2647    /// Send a PUBLISH_NAMESPACE on a bidirectional stream of its own.
2648    pub async fn publish_namespace(
2649        &mut self,
2650        track_namespace: TrackNamespace,
2651        parameters: Vec<KeyValuePair>,
2652    ) -> Result<RequestStream, ConnectionError> {
2653        let mut halves = self.open_request_bi().await?;
2654        let (req_id, msg) = Self::or_abandon(
2655            &mut halves,
2656            self.endpoint.publish_namespace(track_namespace, parameters),
2657        )?;
2658        self.begin_request(halves, RequestKind::PublishNamespace, req_id, &msg).await
2659    }
2660
2661    // -- Track Status flow ------------------------------------------
2662
2663    /// Send a TRACK_STATUS on a bidirectional stream of its own.
2664    pub async fn track_status(
2665        &mut self,
2666        track_namespace: TrackNamespace,
2667        track_name: Vec<u8>,
2668        parameters: Vec<KeyValuePair>,
2669    ) -> Result<RequestStream, ConnectionError> {
2670        let mut halves = self.open_request_bi().await?;
2671        let (req_id, msg) = Self::or_abandon(
2672            &mut halves,
2673            self.endpoint.track_status(track_namespace, track_name, parameters),
2674        )?;
2675        self.begin_request(halves, RequestKind::TrackStatus, req_id, &msg).await
2676    }
2677
2678    // -- Publish flow (publisher side) ------------------------------
2679
2680    /// Send a PUBLISH on a bidirectional stream of its own.
2681    ///
2682    /// REQUEST_OK or REQUEST_ERROR comes back on the returned
2683    /// [`RequestStream`] — draft-18 folded PUBLISH_OK into REQUEST_OK — and
2684    /// [`publish_done`](Self::publish_done) is written back on it when the
2685    /// publication ends, so the handle must be held for as long as the
2686    /// publication lasts.
2687    pub async fn publish(
2688        &mut self,
2689        track_namespace: TrackNamespace,
2690        track_name: Vec<u8>,
2691        track_alias: VarInt,
2692        parameters: Vec<KeyValuePair>,
2693        track_properties: Vec<KeyValuePair>,
2694    ) -> Result<RequestStream, ConnectionError> {
2695        let mut halves = self.open_request_bi().await?;
2696        let (req_id, msg) = Self::or_abandon(
2697            &mut halves,
2698            self.endpoint.publish(
2699                track_namespace,
2700                track_name,
2701                track_alias,
2702                parameters,
2703                track_properties,
2704            ),
2705        )?;
2706        self.begin_request(halves, RequestKind::Publish, req_id, &msg).await
2707    }
2708
2709    /// Send a PUBLISH_DONE on the request stream the PUBLISH opened.
2710    ///
2711    /// PUBLISH_DONE is a response and carries no request id on the wire, so
2712    /// the stream is the only thing that says which publication ended. The id
2713    /// the endpoint needs is taken off `stream`, which makes the correlation
2714    /// unforgeable — there is no way to name one request and write on
2715    /// another's stream.
2716    pub async fn publish_done(
2717        &mut self,
2718        stream: &mut RequestStream,
2719        status_code: VarInt,
2720        stream_count: VarInt,
2721        reason_phrase: Vec<u8>,
2722    ) -> Result<(), ConnectionError> {
2723        let request_id = stream.request_id();
2724        let msg = self.endpoint.send_publish_done(
2725            request_id,
2726            status_code,
2727            stream_count,
2728            reason_phrase,
2729        )?;
2730        self.send_on_request_stream(stream, &msg).await
2731    }
2732
2733    // -- Data streams -----------------------------------------------
2734
2735    /// Open a new unidirectional stream for sending subgroup data.
2736    pub async fn open_subgroup_stream(
2737        &self,
2738        header: &AnySubgroupHeader,
2739    ) -> Result<FramedSendStream, ConnectionError> {
2740        let send = self.transport.open_uni().await?;
2741        let mut framed = FramedSendStream::new(send, self.draft);
2742        let sid = framed.stream_id();
2743        framed.write_subgroup_header(header).await?;
2744        self.emit(ClientEvent::StreamOpened {
2745            direction: Direction::Send,
2746            stream_kind: StreamKind::Subgroup,
2747            stream_id: sid,
2748        });
2749        self.emit(ClientEvent::DataStreamHeader {
2750            stream_id: sid,
2751            direction: Direction::Send,
2752            header: header.clone(),
2753        });
2754        Ok(framed)
2755    }
2756
2757    /// Open a new unidirectional stream for sending a FETCH's objects.
2758    ///
2759    /// The objects answering a FETCH do not go on the request's own stream:
2760    /// they go on a unidirectional stream of their own, which opens with a
2761    /// FETCH_HEADER naming the request they belong to. This writes that header
2762    /// and hands back the stream, the same way
2763    /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
2764    /// subgroup.
2765    ///
2766    /// The caller owns the stream that comes back. Nothing here remembers
2767    /// which request it belongs to, so an endpoint serving several fetches at
2768    /// once keeps its own map from Request ID to stream.
2769    pub async fn open_fetch_stream(
2770        &self,
2771        header: &AnyFetchHeader,
2772    ) -> Result<FramedSendStream, ConnectionError> {
2773        let send = self.transport.open_uni().await?;
2774        let mut framed = FramedSendStream::new(send, self.draft);
2775        let sid = framed.stream_id();
2776        framed.write_fetch_header(header).await?;
2777        self.emit(ClientEvent::StreamOpened {
2778            direction: Direction::Send,
2779            stream_kind: StreamKind::Fetch,
2780            stream_id: sid,
2781        });
2782        Ok(framed)
2783    }
2784
2785    /// Open the data stream for a FETCH this endpoint is answering, and keep
2786    /// the handle on the request.
2787    ///
2788    /// The same stream [`open_fetch_stream`](Self::open_fetch_stream) returns,
2789    /// parked on the request stream it belongs to. That is what lets a rule
2790    /// about the fetch reach the objects it is serving: a refused
2791    /// REQUEST_UPDATE has to reset this stream, and the connection cannot
2792    /// reset a handle the caller walked away with.
2793    ///
2794    /// Write objects through
2795    /// [`RequestStream::fetch_data`](RequestStream::fetch_data), or through
2796    /// the borrow this returns.
2797    pub async fn open_fetch_stream_on<'s>(
2798        &self,
2799        stream: &'s mut RequestStream,
2800        header: &AnyFetchHeader,
2801    ) -> Result<&'s mut FramedSendStream, ConnectionError> {
2802        let framed = self.open_fetch_stream(header).await?;
2803        stream.fetch_data = Some(framed);
2804        Ok(stream.fetch_data.as_mut().expect("just stored"))
2805    }
2806
2807    /// Accept an incoming unidirectional data stream and read its subgroup
2808    /// header.
2809    ///
2810    /// Streams the peer opened before its control stream are returned first,
2811    /// in arrival order, before any new one is accepted from the transport:
2812    /// [`connect`](Self::connect) had to look at them to find the control
2813    /// stream and set the rest aside rather than drop them. They are
2814    /// otherwise ordinary — the type varint `connect` read is still on the
2815    /// front of each one.
2816    pub async fn accept_subgroup_stream(
2817        &self,
2818    ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
2819        let mut framed = match self.take_deferred_uni() {
2820            Some(framed) => framed,
2821            None => FramedRecvStream::new(self.transport.accept_uni().await?, self.draft),
2822        };
2823        let sid = framed.stream_id();
2824        let header = framed.read_subgroup_header().await?;
2825        self.emit(ClientEvent::StreamOpened {
2826            direction: Direction::Receive,
2827            stream_kind: StreamKind::Subgroup,
2828            stream_id: sid,
2829        });
2830        self.emit(ClientEvent::DataStreamHeader {
2831            stream_id: sid,
2832            direction: Direction::Receive,
2833            header: header.clone(),
2834        });
2835        // The track is resolved here and not inside the stream: it takes the
2836        // endpoint's alias table, which a stream handle has no way back to.
2837        // Handed over rather than offered, so measuring is not something a
2838        // caller has to remember to ask for.
2839        if let Some(objects) = self.endpoint.track_objects(header.track_alias()) {
2840            framed.measure_objects_against(objects, header.group_id());
2841        }
2842        Ok((header, framed))
2843    }
2844
2845    /// Accept the next unidirectional stream and read its fetch header.
2846    ///
2847    /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
2848    /// are separate because the header decides how every object after it is
2849    /// framed, so a caller has to know which it is expecting before the first
2850    /// byte is read.
2851    ///
2852    /// Objects come off the returned stream with
2853    /// [`FramedRecvStream::read_fetch_object`].
2854    pub async fn accept_fetch_stream(
2855        &self,
2856    ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
2857        let mut framed = match self.take_deferred_uni() {
2858            Some(framed) => framed,
2859            None => FramedRecvStream::new(self.transport.accept_uni().await?, self.draft),
2860        };
2861        let sid = framed.stream_id();
2862        let header = framed.read_fetch_header().await?;
2863        self.emit(ClientEvent::StreamOpened {
2864            direction: Direction::Receive,
2865            stream_kind: StreamKind::Fetch,
2866            stream_id: sid,
2867        });
2868        self.emit(ClientEvent::FetchStreamHeader {
2869            stream_id: sid,
2870            direction: Direction::Receive,
2871            header: header.clone(),
2872        });
2873        // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
2874        // carries an `AnySubgroupHeader` and cannot express one. What
2875        // `accept_subgroup_stream` does beyond this - the forwarding-preference
2876        // note, the object measurement - is about a subgroup and has no
2877        // counterpart on a fetch stream.
2878        Ok((header, framed))
2879    }
2880
2881    /// Take the oldest stream [`connect`](Self::connect) set aside, if any.
2882    ///
2883    /// Synchronous on purpose: the guard is dropped before the caller awaits,
2884    /// so the lock is never held across a suspension point. A poisoned lock
2885    /// is recovered rather than propagated — nothing here can leave the queue
2886    /// in a state a later reader could be misled by, since the only mutation
2887    /// is a `pop_front`.
2888    fn take_deferred_uni(&self) -> Option<FramedRecvStream> {
2889        self.deferred_uni.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).pop_front()
2890    }
2891
2892    /// How many unidirectional streams [`connect`](Self::connect) set aside
2893    /// and [`accept_subgroup_stream`](Self::accept_subgroup_stream) has not
2894    /// yet handed back.
2895    ///
2896    /// Zero for a peer that opened its control stream first, which is the
2897    /// ordinary case.
2898    pub fn deferred_stream_count(&self) -> usize {
2899        self.deferred_uni.lock().unwrap_or_else(|poisoned| poisoned.into_inner()).len()
2900    }
2901
2902    /// Send an object via datagram.
2903    ///
2904    /// The header goes through `AnyDatagramHeader::encode`, which refuses a
2905    /// header whose Object Status the framing it names cannot carry. Such a
2906    /// header errors here and nothing is sent, rather than going out as an
2907    /// ordinary payload datagram with the status quietly dropped.
2908    pub fn send_datagram(
2909        &self,
2910        header: &AnyDatagramHeader,
2911        payload: &[u8],
2912    ) -> Result<(), ConnectionError> {
2913        let mut buf = Vec::new();
2914        header.encode(&mut buf)?;
2915        buf.extend_from_slice(payload);
2916        self.emit(ClientEvent::DatagramReceived {
2917            direction: Direction::Send,
2918            header: header.clone(),
2919            payload_len: payload.len(),
2920        });
2921        self.transport.send_datagram(bytes::Bytes::from(buf))?;
2922        Ok(())
2923    }
2924
2925    /// Receive a datagram and decode its header.
2926    pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
2927        let data = self.transport.recv_datagram().await?;
2928        let mut cursor = &data[..];
2929        let header = AnyDatagramHeader::decode(self.draft, &mut cursor)?;
2930        let consumed = data.len() - cursor.len();
2931        let payload = data.slice(consumed..);
2932        self.emit(ClientEvent::DatagramReceived {
2933            direction: Direction::Receive,
2934            header: header.clone(),
2935            payload_len: payload.len(),
2936        });
2937        // Refutable only in a build with more than one draft enabled;
2938        // in a single-draft build `AnyDatagramHeader` has one variant.
2939        #[allow(irrefutable_let_patterns)]
2940        if let AnyDatagramHeader::Draft18(h) = &header {
2941            if !h.permits_payload() && !payload.is_empty() {
2942                return Err(ConnectionError::PayloadOnStatusDatagram {
2943                    object_id: h.object_id.into_inner(),
2944                    payload_len: payload.len(),
2945                    status: h.object_status,
2946                });
2947            }
2948        }
2949        // A datagram is a whole object, so the connection can measure it
2950        // without help from the caller. It cannot *answer* the condition,
2951        // though: the answer is a reset of a request stream the caller holds,
2952        // so both data paths report and neither withdraws - see
2953        // `Connection::requests_to_cancel`.
2954        let meta = header.meta();
2955        self.endpoint.note_received_object(
2956            meta.track_alias,
2957            ObjectLocation { group: meta.group_id, object: meta.object_id },
2958            object_role(meta.status),
2959        )?;
2960        Ok((header, payload))
2961    }
2962
2963    // -- Accessors --------------------------------------------------
2964
2965    /// Access the underlying endpoint state machine.
2966    pub fn endpoint(&self) -> &Endpoint {
2967        &self.endpoint
2968    }
2969
2970    /// Mutable access to the endpoint state machine.
2971    pub fn endpoint_mut(&mut self) -> &mut Endpoint {
2972        &mut self.endpoint
2973    }
2974
2975    /// The SETUP message the server answered the handshake with.
2976    ///
2977    /// `SERVER_SETUP` through draft-16, the server's half of the unified
2978    /// `SETUP` from draft-17. [`AnyControlMessage::fields`] renders it under
2979    /// this draft's own parameter names, in the order they arrived.
2980    pub fn server_setup(&self) -> &AnyControlMessage {
2981        &self.server_setup
2982    }
2983
2984    /// The framed wire bytes of [`Self::server_setup`], as they arrived.
2985    ///
2986    /// Kept beside the decoded form because the encoding is evidence the
2987    /// decoding discards: two relays sending the same parameter can still
2988    /// disagree on how wide a varint they wrote it in.
2989    pub fn server_setup_raw(&self) -> Option<&[u8]> {
2990        self.server_setup_raw.as_deref()
2991    }
2992
2993    /// Returns the draft version this connection is using.
2994    pub fn draft(&self) -> DraftVersion {
2995        self.draft
2996    }
2997
2998    /// Close the session on the wire when the endpoint says a violation is
2999    /// fatal to it.
3000    ///
3001    /// [`EndpointError::session_error_code`] answers `Some` for exactly the
3002    /// errors draft-18 tells the receiver to close the session over, and the
3003    /// endpoint has already moved its own state machine to Closed by the time
3004    /// this runs. Without this step that move is purely internal: the local
3005    /// endpoint refuses to start anything new while the peer, which is the one
3006    /// that broke the rule, sees a session that is still open and goes on
3007    /// sending. "MUST close the session with a PROTOCOL_VIOLATION" is a
3008    /// statement about the wire, so it takes a CONNECTION_CLOSE to satisfy it.
3009    ///
3010    /// The reason phrase is the error's own `Display` text, which names the
3011    /// message and the rule rather than repeating the numeric code the close
3012    /// already carries.
3013    ///
3014    /// Errors that answer `None` are recoverable and nothing is sent.
3015    fn close_for(&self, err: &EndpointError) {
3016        if let Some(code) = err.session_error_code() {
3017            // QUIC application error codes are 62-bit; every code in this
3018            // registry is far below `u32::MAX`, and saturating rather than
3019            // truncating means a future code that is not could never be
3020            // reported as a different, assigned one.
3021            let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
3022            self.close(wire_code, err.to_string().as_bytes());
3023        }
3024    }
3025
3026    /// [`close_for`](Self::close_for), then the error unchanged, for the
3027    /// common case where the endpoint's error is also what the caller returns.
3028    fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
3029        self.close_for(&err);
3030        ConnectionError::Endpoint(err)
3031    }
3032
3033    /// Which of this draft's *own* `ConnectionError` variants this error is,
3034    /// and which kind of thing it says.
3035    ///
3036    /// The ten every draft carries answer `None` here: [`AnyConnectionError`]
3037    /// classifies those itself, once, and never asks a draft about them. What
3038    /// is left splits two ways, and the split is the reason this function
3039    /// exists — before it, both halves reached a caller as a sentence and read
3040    /// exactly alike. A [`LocalRefusal`] is this endpoint declining to write
3041    /// something, so nothing reached the wire and no relay is implicated; a
3042    /// [`PeerViolation`] is a peer having done something draft-18 forbids, and
3043    /// carries the session error code draft-18's own text answers it with.
3044    ///
3045    /// Matched exhaustively, with no wildcard arm and deliberately so: a
3046    /// variant added to this draft's error type has to arrive here as a compile
3047    /// error, beside the doc comment quoting the sentence it enforces, rather
3048    /// than as a silent [`ErrorCause::Unclassified`] in the facade.
3049    ///
3050    /// [`AnyConnectionError`]: crate::dispatch::AnyConnectionError
3051    /// [`ErrorCause::Unclassified`]: crate::dispatch::ErrorCause::Unclassified
3052    /// [`LocalRefusal`]: crate::above_codec_rules::DraftSpecificCause::LocalRefusal
3053    /// [`PeerViolation`]: crate::above_codec_rules::DraftSpecificCause::PeerViolation
3054    pub fn draft_specific_cause(
3055        err: &ConnectionError,
3056    ) -> Option<crate::above_codec_rules::DraftSpecificCause> {
3057        use crate::above_codec_rules::{AboveCodecRule, DraftSpecificCause};
3058        use moqtap_codec::draft18::error_codes::SessionErrorCode;
3059
3060        match err {
3061            ConnectionError::Endpoint(_)
3062            | ConnectionError::Codec(_)
3063            | ConnectionError::Transport(_)
3064            | ConnectionError::VarInt(_)
3065            | ConnectionError::NoControlStream
3066            | ConnectionError::UnexpectedEnd
3067            | ConnectionError::StreamFinished
3068            | ConnectionError::InvalidAddress(_)
3069            | ConnectionError::TlsConfig(_)
3070            | ConnectionError::DataStreamState(_) => None,
3071            // This build decoding a message and then failing to narrow it to
3072            // its own draft. Nothing reached the wire and no peer is
3073            // implicated, which is the whole reason it is not
3074            // `ConnectionError::Codec`: under that name it would carry
3075            // `Some(PROTOCOL_VIOLATION)` out of `codec_session_error_code` and
3076            // publish a relay for this build's defect. See the variant's own
3077            // doc.
3078            ConnectionError::ControlMessageNarrowing => {
3079                Some(crate::above_codec_rules::DraftSpecificCause::LocalRefusal)
3080            }
3081            // Section 11.2.1.2 states the rule and names the code in the same
3082            // sentence. The codec decodes such an Object without complaint —
3083            // the frame is well formed — so this layer is the only one that
3084            // can raise it, and `close_for_data_stream` performs the close by
3085            // reading this same answer.
3086            ConnectionError::PropertiesOnNonNormalStatus { .. } => {
3087                Some(DraftSpecificCause::PeerViolation {
3088                    rule: AboveCodecRule::PropertiesOnNonNormalStatus,
3089                    close: Some(SessionErrorCode::ProtocolViolation.as_u64()),
3090                })
3091            }
3092            // Section 11.2.1.1 states this one as a property of a conforming
3093            // Object rather than as one of the cases a draft answers with a
3094            // close. That phrase carries no quotation marks and must not: it
3095            // is this crate naming a shape of drafting, and the marks would
3096            // file the words on the section named right beside them — which is
3097            // the one section here that pointedly does not carry them. The
3098            // datagram is refused and the session is left running. `None` is
3099            // that reading, and it is what keeps a relay from being published
3100            // for a rule its draft attaches no consequence to.
3101            ConnectionError::PayloadOnStatusDatagram { .. } => {
3102                Some(DraftSpecificCause::PeerViolation {
3103                    rule: AboveCodecRule::PayloadOnStatusDatagram,
3104                    close: None,
3105                })
3106            }
3107            // Section 3.3: "Bidirectional streams MUST NOT begin with any
3108            // other message type unless negotiated. If they do, the peer MUST
3109            // close the Session with a PROTOCOL_VIOLATION." The session has
3110            // already been closed on the wire by the time this is returned, so
3111            // the code is carried here for a caller to read which rule was
3112            // answered, not for it to answer one again.
3113            ConnectionError::NonRequestOnRequestStream(_) => {
3114                Some(DraftSpecificCause::PeerViolation {
3115                    rule: AboveCodecRule::BidiStreamOpener,
3116                    close: Some(SessionErrorCode::ProtocolViolation.as_u64()),
3117                })
3118            }
3119            // Three ways of handing this endpoint a message it will not write,
3120            // and one answer: nothing reached the wire, so nothing here is
3121            // evidence about a peer. Two are messages put on the control stream
3122            // that belong on a request stream of their own; the third is a
3123            // `respond_*` helper pointed at a request this endpoint opened,
3124            // which only the endpoint a request was opened *toward* may answer.
3125            ConnectionError::RequestOnControlStream(_)
3126            | ConnectionError::RequestStreamMessageOnControlStream(_)
3127            | ConnectionError::RespondedToOwnRequest(_) => Some(DraftSpecificCause::LocalRefusal),
3128        }
3129    }
3130
3131    /// The code to close the session with when a control message could not be
3132    /// decoded because the peer broke a rule draft-18 answers with a close.
3133    ///
3134    /// Every variant listed here comes from a sentence in the draft that names
3135    /// the consequence: the reason phrase and GOAWAY URI maxima (Sections
3136    /// 1.4.4 and 10.4), the KVP value maximum and the delta-encoded
3137    /// type overflow (Section 1.4.3), the duplicate-parameter rule (Section
3138    /// 10.2), the Track Namespace field, count and length rules
3139    /// (Section 2.4.1), and the Object ID delta wrap (Section 11.4.2). Each of
3140    /// those reads "MUST close the session with a PROTOCOL_VIOLATION".
3141    ///
3142    /// The Object ID wrap is the one that arrives here from a data stream
3143    /// rather than a control message, and it is draft-18 and draft-19 only:
3144    /// "The Object ID Delta + 1 is added to the previous Object ID in the
3145    /// Subgroup stream if there was one... If the resulting Object ID would be
3146    /// greater than 2^64 - 1, the endpoint MUST close the session with a
3147    /// PROTOCOL_VIOLATION." Draft-17 describes the same arithmetic and states
3148    /// no consequence for overflowing it, so its connection deliberately leaves
3149    /// the wrap off this list and treats it as a decode failure alone.
3150    ///
3151    /// One more rule reaches this table without naming a code: "An endpoint
3152    /// that receives an unknown message type MUST close the session", stated in
3153    /// those words by all the drafts. Protocol Violation is what carries
3154    /// it, as it does on every draft below this one.
3155    ///
3156    /// `None` for everything else, including [`CodecError::InvalidField`]. That
3157    /// variant is shared by a dozen unrelated malformations, only some of which
3158    /// the draft answers with a close, so treating it as fatal would close
3159    /// sessions the draft does not ask to be closed. Splitting it is the way to
3160    /// bring the rest of those rules under this function; widening the match is
3161    /// not — the Object ID wrap is answerable here because it has a variant of
3162    /// its own rather than being one more reading of `InvalidField`.
3163    pub fn codec_session_error_code(
3164        err: &CodecError,
3165    ) -> Option<moqtap_codec::draft18::error_codes::SessionErrorCode> {
3166        use moqtap_codec::draft18::error_codes::SessionErrorCode;
3167        use moqtap_codec::kvp::KvpError;
3168        match err {
3169            // The declared Length disagreeing with the fields, which every
3170            // draft answers with a close. Drafts 07 through 10 name no code for
3171            // it, so it takes the one their other unnamed rules take.
3172            // A Filter Type outside the four this draft assigns, Section 5.1.2:
3173            // "An endpoint that receives a filter type other than the above MUST
3174            // close the session with PROTOCOL_VIOLATION."
3175            //
3176            // Drafts 07 through 14 carried the Filter Type as a field of
3177            // SUBSCRIBE. From draft-15 it is the first field inside the
3178            // length-prefixed filter parameter, where a codec that carries the
3179            // value as opaque bytes never reads it — the rule did not change and
3180            // the place it has to be enforced did.
3181            CodecError::InvalidFilterType(_) => Some(SessionErrorCode::ProtocolViolation),
3182            // An AbsoluteRange filter whose End Group Delta carries the range
3183            // past the end of the number space, Section 5.1.2: "the last Group
3184            // ID to be delivered will be the Group ID in Start Location plus the
3185            // End Group Delta. If the resulting Group ID would be greater than
3186            // 2^64 - 1, the endpoint MUST close the session with a
3187            // PROTOCOL_VIOLATION." New in draft-18; draft-17, which introduced
3188            // the delta, states no such sentence.
3189            CodecError::FilterEndGroupOverflow { .. } => {
3190                Some(SessionErrorCode::ProtocolViolation)
3191            }
3192            // A Fetch Type outside the three this draft assigns: "An endpoint
3193            // that receives a Fetch Type other than 0x1, 0x2 or 0x3 MUST close
3194            // the session with a PROTOCOL_VIOLATION." The value decides which
3195            // fields follow it — a Standalone fetch carries a track name and a
3196            // range where a joining fetch carries a Request ID and an offset —
3197            // so a reader that cannot name the type cannot find the end of the
3198            // message.
3199            CodecError::InvalidFetchType(_) => Some(SessionErrorCode::ProtocolViolation),
3200            CodecError::ControlMessageLengthMismatch { .. } => {
3201                Some(SessionErrorCode::ProtocolViolation)
3202            }
3203            CodecError::KeyDeltaOverflow(..)
3204            | CodecError::DuplicateParameter(_)
3205            | CodecError::TrackNameTooLong
3206            | CodecError::InvalidNamespaceTupleSize(_)
3207            | CodecError::ReasonPhraseTooLong
3208            | CodecError::GoAwayUriTooLong
3209            | CodecError::UnknownMessageType(_)
3210            | CodecError::Kvp(KvpError::ValueTooLong(_))
3211            | CodecError::EmptyNamespaceField
3212            | CodecError::ObjectIdOverflow(..) => Some(SessionErrorCode::ProtocolViolation),
3213            // An unknown data-plane type. Drafts 17 and later split the sentence
3214            // in two: Section 3.4 for streams, Section 11 for datagrams, both
3215            // ending "MUST close the session" and neither naming a code, so both
3216            // take the one this draft's other unnamed rules take.
3217            // A Message Parameter whose value is outside the range its type
3218            // allows: FORWARD in Section 10.2.12 and GROUP_ORDER in Section 10.2.8.
3219            // Each states that a receiver "MUST close the session with
3220            // PROTOCOL_VIOLATION".
3221            CodecError::ParameterValueOutOfRange { .. } => {
3222                Some(SessionErrorCode::ProtocolViolation)
3223            }
3224            // A Track Extension or Track Property whose value is outside the
3225            // range its type allows: DEFAULT_PUBLISHER_GROUP_ORDER in Section 12.5
3226            // and DYNAMIC_GROUPS in Section 12.6.
3227            // Each states that a receiver "MUST close the session with
3228            // PROTOCOL_VIOLATION".
3229            //
3230            // A separate arm from the parameter rule above because the two
3231            // registries are separate: 0x22 is GROUP_ORDER as a parameter and
3232            // DEFAULT_PUBLISHER_GROUP_ORDER as a Track Property, and a log that
3233            // named only the number would not say which.
3234            CodecError::TrackPropertyValueOutOfRange { .. } => {
3235                Some(SessionErrorCode::ProtocolViolation)
3236            }
3237            CodecError::UnknownStreamType(_) | CodecError::UnknownDatagramType(_) => {
3238                Some(SessionErrorCode::ProtocolViolation)
3239            }
3240            // A Type inside a form this draft defines but on a list it names as
3241            // invalid: Section 11.4.2 for a subgroup header whose SUBGROUP_ID_MODE
3242            // is the reserved 0b11, Section 11.3.1 for a datagram asking to be both
3243            // an object status and an end-of-group marker. Unlike the rule above,
3244            // these two name their code outright.
3245            CodecError::InvalidStreamTypeValue { .. }
3246            | CodecError::InvalidDatagramTypeValue { .. } => {
3247                Some(SessionErrorCode::ProtocolViolation)
3248            }
3249            // A key-value pair whose value is not the serialization its own
3250            // Type defines, Section 1.4.3: "If a receiver understands a Type,
3251            // and the following Value or Length/Value does not match the
3252            // serialization defined by that Type, the receiver MUST close the
3253            // session with error code KEY_VALUE_FORMATTING_ERROR."
3254            //
3255            // Section 10.2.2 states the same answer for the one structure this
3256            // draft spells out: "If the Token structure cannot be decoded, the
3257            // receiver MUST close the Session with KEY_VALUE_FORMATTING_ERROR."
3258            //
3259            // The one rule in this table that names a code other than Protocol
3260            // Violation.
3261            CodecError::KeyValueFormatting { .. }
3262            // A filter parameter whose value is not a filter reaches the same
3263            // sentence. Drafts 15 and 16 answered it with PROTOCOL_VIOLATION
3264            // instead, on the strength of a sentence of the parameter's own that
3265            // this draft dropped; what remains is the general rule above, so the
3266            // code changed with it.
3267            | CodecError::SubscriptionFilterMalformed { .. } => {
3268                Some(SessionErrorCode::KeyValueFormattingError)
3269            }
3270            // A Message Parameter whose type this draft does not define, Section
3271            // 10.2: "All Message Parameters MUST be defined in the negotiated
3272            // version of MOQT or negotiated via Setup Options. An endpoint that
3273            // receives an unknown Message Parameter MUST close the session with
3274            // PROTOCOL_VIOLATION."
3275            //
3276            // One namespace only. This draft also says a receiver ignores an
3277            // unrecognised Setup Option, so an unknown type in a SETUP is carried and
3278            // the codec never raises this for one.
3279            CodecError::UnknownMessageParameter(_) => Some(SessionErrorCode::ProtocolViolation),
3280            // A Message Parameter in a message type its own definition does not
3281            // name, Section 10.2.1: "Each Message Parameter definition indicates
3282            // the message types in which it can appear. If it appears in some
3283            // other type of message, the receiving endpoint MUST close the
3284            // connection with a PROTOCOL_VIOLATION."
3285            //
3286            // Draft-16 and every draft before it end that same sentence "it MUST
3287            // be ignored", so this is a rule whose answer reverses rather than
3288            // one that arrives.
3289            CodecError::ParameterOutOfScope { .. } => Some(SessionErrorCode::ProtocolViolation),
3290            // Everything this draft does not answer, named rather than swept up
3291            // by a wildcard. The arm is exhaustive deliberately: a new
3292            // `CodecError` variant will not compile until it has been placed on
3293            // one side or the other, on this draft, which is the decision a `_`
3294            // arm makes silently and invisibly in every draft module at once.
3295            //
3296            // Adding one variant to `CodecError` produces an `E0004` in every
3297            // draft module that matches it exhaustively, each naming the
3298            // variant that has nowhere to go. That is the whole mechanism.
3299            //
3300            // The nesting stops at `VarInt`, whose variants report how the bytes
3301            // ran out rather than a rule an endpoint states, so there is nothing
3302            // in it for a draft to answer. `Kvp` is spelled out because it does
3303            // carry one.
3304            // Neither field exists from draft-15 on. Forwarding became the
3305            // FORWARD parameter, which carries the same rule in a different
3306            // shape and is answered above under its own variant; Content Exists
3307            // became the presence or absence of a LARGEST_OBJECT parameter.
3308            CodecError::InvalidForward(_)
3309            | CodecError::InvalidContentExists(_)
3310            | CodecError::UnexpectedEnd
3311            | CodecError::MessageTooLong(_)
3312            | CodecError::VarInt(_)
3313            | CodecError::InvalidField
3314            | CodecError::InvalidRange(..)
3315            | CodecError::ParameterLengthMismatch(_)
3316            | CodecError::EndOfTrackObjectId(_)
3317            | CodecError::ParametersOutOfOrder(..)
3318            | CodecError::ExtensionsOnNonExistentObject(_)
3319            | CodecError::InvalidRequiredRequestIdDelta(..)
3320            // The object payload rule, Section 11.2.1.1: "Any object with a status
3321            // code other than zero MUST have an empty payload." A MUST on the
3322            // sender with no receiver action named anywhere — the "SHOULD be
3323            // treated as a protocol error" in the same paragraph belongs to the
3324            // sentence before it, which is about a status value this draft does
3325            // not assign — so an object carrying a payload it may not is refused
3326            // and the session stays open.
3327            | CodecError::PayloadNotPermitted { .. }
3328            | CodecError::UnsupportedDraft(_)
3329            | CodecError::Kvp(
3330                KvpError::MissingLength | KvpError::UnexpectedEnd | KvpError::VarInt(_),
3331            ) => None,
3332        }
3333    }
3334
3335    /// Close the session on the wire when a decode failure is one draft-18
3336    /// answers with a close, and hand the error back unchanged.
3337    ///
3338    /// The codec's counterpart to
3339    /// [`close_if_session_fatal`](Self::close_if_session_fatal). Without it
3340    /// every bound the decoder enforces would stop at *this endpoint refused the
3341    /// frame* while the peer, which is the one that broke the rule, saw a
3342    /// session that was still open and went on sending. "MUST close the session
3343    /// with a PROTOCOL_VIOLATION" is a statement about the wire.
3344    fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
3345        if let ConnectionError::Codec(inner) = &err {
3346            if let Some(code) = Self::codec_session_error_code(inner) {
3347                // QUIC application error codes are 62-bit; every code in this
3348                // registry is far below `u32::MAX`, and saturating rather than
3349                // truncating means a future code that is not could never be
3350                // reported as a different, assigned one.
3351                let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
3352                self.close(wire_code, inner.to_string().as_bytes());
3353            }
3354        }
3355        err
3356    }
3357
3358    /// Name every request whose stream the caller must reset, for a track a
3359    /// data path has just found malformed.
3360    ///
3361    /// Section 2.4.2 answers its whole list of conditions at once: "it MUST
3362    /// cancel any corresponding subscription or fetches for that Track from
3363    /// that publisher". On this draft cancelling a request is a transport
3364    /// operation rather than a message — Section 3.3.2: "Implementations SHOULD cancel requests
3365    /// by abruptly terminating any directions of a stream that are still open
3366    /// by resetting or sending STOP_SENDING."
3367    ///
3368    /// Draft-18 drops the QUIC frame names draft-17 spelled out, and adds a
3369    /// sentence asking the application to pick a relevant error code.
3370    ///
3371    /// # Why this returns ids instead of doing it
3372    ///
3373    /// Because the streams are the caller's. Every request on this draft lives
3374    /// at the front of a bidirectional stream of its own, and
3375    /// [`Connection::recv_on_request_stream`] hands that stream back as a
3376    /// [`RequestStream`]. There is no handle here to reset. So the connection
3377    /// does the half it can — note the track, and work out which requests
3378    /// receive it — and the caller passes each id to
3379    /// [`Connection::cancel_request_stream`], which resets the stream *and*
3380    /// moves the endpoint's record.
3381    ///
3382    /// **This is the one place in this crate where the two halves of an answer
3383    /// are split across the API boundary**, and it is the draft that splits
3384    /// them: drafts 12 through 16 answer with a control message, which the
3385    /// connection owns, so `withdraw_for_data_stream` there does the whole
3386    /// thing.
3387    ///
3388    /// # Both data paths come here
3389    ///
3390    /// Unlike the drafts that answer with a message, where a datagram is read
3391    /// through the connection and answers itself. Here neither path can, for
3392    /// the same reason, so there is one entry point rather than two. Pass it
3393    /// whatever error a read returned; anything that is not this condition
3394    /// gives back an empty list.
3395    ///
3396    /// Empty is not "the track was fine" — it is also what an alias no live
3397    /// binding names gives, and what a track this endpoint only publishes
3398    /// gives.
3399    pub fn requests_to_cancel(&self, err: &ConnectionError) -> Vec<VarInt> {
3400        let ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject { alias, .. }) = err
3401        else {
3402            return Vec::new();
3403        };
3404        self.endpoint
3405            .requests_for_malformed_track(*alias, MalformedTrackCondition::ObjectPastFinalObject)
3406    }
3407
3408    /// Close the session when a failure raised while reading a *data* stream is
3409    /// one draft-18 answers with a close. Reports whether it closed.
3410    ///
3411    /// [`recv_control`](Self::recv_control) does this for itself, because it
3412    /// owns both the stream and the connection. A data stream does not:
3413    /// [`accept_subgroup_stream`](Self::accept_subgroup_stream) hands the caller
3414    /// a [`FramedRecvStream`], which holds no connection and so cannot close
3415    /// one, and the reads that raise these failures happen there. The caller is
3416    /// the only party holding both halves, which is what this is for.
3417    ///
3418    /// Splitting it this way rather than closing inside the reader keeps a
3419    /// caller that is deliberately permissive — a tool reproducing a capture,
3420    /// say — able to read a violating stream and report it without tearing the
3421    /// session down. The rule is stated at endpoints, and this is where an
3422    /// endpoint decides it is one.
3423    ///
3424    /// Only [`ConnectionError::Codec`] failures are matched, against the same
3425    /// `codec_session_error_code` table the
3426    /// control path uses, so a rule is answered with one code whichever stream
3427    /// carried it. The Object ID delta wrap of Section 11.4.2 is the entry that
3428    /// can only arrive this way.
3429    pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
3430        use crate::above_codec_rules::DraftSpecificCause;
3431
3432        match err {
3433            ConnectionError::Codec(inner) => {
3434                let Some(code) = Self::codec_session_error_code(inner) else { return false };
3435                let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
3436                self.close(wire_code, inner.to_string().as_bytes());
3437                true
3438            }
3439            // Not a `Codec` failure: the codec decodes such an Object without
3440            // complaint, because the frame is well formed. It is being an
3441            // endpoint that makes it a violation, so the variant is this
3442            // crate's own and the mapping table above never sees it.
3443            //
3444            // The code comes from `draft_specific_cause` rather than from a
3445            // constant here, so this draft's reading of its own sentence is
3446            // written down once and a caller who reads the error as a value
3447            // sees the same code the peer was sent.
3448            ConnectionError::PropertiesOnNonNormalStatus { .. } => {
3449                let Some(DraftSpecificCause::PeerViolation { close: Some(code), .. }) =
3450                    Self::draft_specific_cause(err)
3451                else {
3452                    return false;
3453                };
3454                // Saturate rather than truncate, so a future code above
3455                // `u32::MAX` is never reported as a different assigned one.
3456                self.close(u32::try_from(code).unwrap_or(u32::MAX), err.to_string().as_bytes());
3457                true
3458            }
3459            _ => false,
3460        }
3461    }
3462
3463    /// Close the connection.
3464    pub fn close(&self, code: u32, reason: &[u8]) {
3465        self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
3466        self.transport.close(code, reason);
3467    }
3468}
3469
3470#[cfg(test)]
3471mod tests {
3472    use super::*;
3473
3474    /// This build failing to narrow a message it decoded is never a finding
3475    /// about the peer.
3476    ///
3477    /// The arm that raises `ControlMessageNarrowing` is unreachable — this
3478    /// draft's decoder can only hand back this draft's variant — and nothing
3479    /// pins that. What is pinned here is the half that matters.
3480    /// `CodecError::UnknownMessageType(0)` is what the arm must not raise:
3481    /// `codec_session_error_code` answers it `Some(PROTOCOL_VIOLATION)` on
3482    /// every draft in range, so the day the narrowing failed a conformance
3483    /// probe would publish a relay for sending a control message type this
3484    /// draft does not assign — with `0x00` attached as the codepoint that
3485    /// proved it, which is an accusation better evidenced than any real one
3486    /// this build makes. The section stating that rule is numbered differently
3487    /// on every draft, and the point does not turn on the number.
3488    ///
3489    /// Ablated by putting the arm back to
3490    /// `ConnectionError::Codec(CodecError::UnknownMessageType(0))`: this test
3491    /// reddens on the cause, and so does the probe's own
3492    /// `violation::a_message_this_build_could_not_narrow_names_nobody`.
3493    #[test]
3494    fn a_message_this_build_could_not_narrow_names_nobody() {
3495        use crate::dispatch::{AnyConnectionError, ErrorCause};
3496
3497        let err: AnyConnectionError = ConnectionError::ControlMessageNarrowing.into();
3498        assert!(err.is_local(), "a narrowing this build could not do is this build's");
3499        assert_eq!(
3500            err.cause(),
3501            &ErrorCause::Facade,
3502            "nothing reached the wire, so there is no rule and no close code to read"
3503        );
3504    }
3505
3506    /// Draft-18 uses MoQT's variable-length integer, whose length is the
3507    /// number of leading 1 bits in the first byte, not RFC 9000's two-bit
3508    /// prefix. Control framing measures the type field with it before any
3509    /// bytes past the first have arrived.
3510    #[test]
3511    fn varint_len_follows_the_moqt_encoding() {
3512        let draft = DraftVersion::Draft18;
3513        assert_eq!(draft.varint_len(0x00), 1);
3514        assert_eq!(draft.varint_len(0x7F), 1);
3515        assert_eq!(draft.varint_len(0x80), 2);
3516        assert_eq!(draft.varint_len(0xBF), 2);
3517        assert_eq!(draft.varint_len(0xC0), 3);
3518        assert_eq!(draft.varint_len(0xFF), 9);
3519        // SETUP's type id, 0x2F00, is two bytes here and four under RFC 9000.
3520        assert_eq!(draft.varint_len(0xAF), 2);
3521    }
3522
3523    #[test]
3524    fn client_config_alpn_quic_draft18() {
3525        let config = ClientConfig {
3526            draft: DraftVersion::Draft18,
3527            transport: TransportType::Quic,
3528            skip_cert_verification: false,
3529            ca_certs: Vec::new(),
3530            setup_parameters: Vec::new(),
3531        };
3532        assert_eq!(config.alpn(), vec![b"moqt-18".to_vec()]);
3533    }
3534
3535    #[test]
3536    fn client_config_alpn_webtransport() {
3537        let config = ClientConfig {
3538            draft: DraftVersion::Draft18,
3539            transport: TransportType::WebTransport { url: "https://example.com".to_string() },
3540            skip_cert_verification: false,
3541            ca_certs: Vec::new(),
3542            setup_parameters: Vec::new(),
3543        };
3544        assert_eq!(config.alpn(), vec![b"h3".to_vec()]);
3545    }
3546
3547    /// `MOQT_ALPN` is the ALPN a client configured for this draft offers.
3548    ///
3549    /// Putting `moq-00` back — the value this constant held on all five of
3550    /// drafts 15-19 — fails with:
3551    ///
3552    /// ```text
3553    /// assertion `left == right` failed: MOQT_ALPN is "moq-00"; a draft-19 client offers ["moqt-19"]
3554    /// ```
3555    #[test]
3556    fn moqt_alpn_is_the_one_a_client_offers() {
3557        // A literal on its own is what let this constant keep `moq-00` for
3558        // five drafts after draft-15 stopped using it, so the value is
3559        // checked against what a client configured for this draft actually
3560        // puts on the wire, and only then against the literal.
3561        let config = ClientConfig {
3562            draft: DraftVersion::Draft18,
3563            transport: TransportType::Quic,
3564            skip_cert_verification: false,
3565            ca_certs: Vec::new(),
3566            setup_parameters: Vec::new(),
3567        };
3568        assert_eq!(
3569            config.alpn(),
3570            vec![MOQT_ALPN.to_vec()],
3571            "MOQT_ALPN is {:?}; a draft-{} client offers {:?}",
3572            String::from_utf8_lossy(MOQT_ALPN),
3573            18,
3574            config
3575                .alpn()
3576                .iter()
3577                .map(|a| String::from_utf8_lossy(a).into_owned())
3578                .collect::<Vec<_>>(),
3579        );
3580        assert_eq!(MOQT_ALPN, b"moqt-18");
3581    }
3582
3583    /// Draft-18 Section 3.3 names seven message types a bidirectional stream
3584    /// may begin with, and no others. The set is checked against the raw
3585    /// numbers this draft's registry assigns rather than against the names,
3586    /// so a variant that is renumbered — SUBSCRIBE_NAMESPACE moved from 0x11
3587    /// to 0x50 between draft-17 and draft-18 — is caught even though the
3588    /// spelling did not change, and a variant that is missing — draft-18's
3589    /// own SUBSCRIBE_TRACKS, 0x51 — is caught even though nothing else in the
3590    /// file would notice.
3591    ///
3592    /// Every type this draft assigns is classified: the loop walks the whole
3593    /// assigned range and asks the classifier about each one it finds.
3594    ///
3595    /// Dropping `MessageType::SubscribeTracks` from the true arm into the
3596    /// control-stream arm — the copy of draft-17's six-type classifier a
3597    /// blind port would leave behind — fails with:
3598    ///
3599    /// ```text
3600    /// assertion `left == right` failed: the types that open a request stream are [3, 6, 13, 22, 29, 80]; draft-18 Section 3.3 names [3, 6, 13, 22, 29, 80, 81]
3601    ///   left: [3, 6, 13, 22, 29, 80]
3602    ///  right: [3, 6, 13, 22, 29, 80, 81]
3603    /// ```
3604    #[test]
3605    fn only_seven_message_types_open_a_request_stream() {
3606        // TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
3607        // SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS, written as the numbers
3608        // draft-18 assigns them.
3609        let mut expected = vec![0x0D, 0x03, 0x1D, 0x16, 0x06, 0x50, 0x51];
3610        expected.sort_unstable();
3611
3612        let mut opens = Vec::new();
3613        for id in 0..=CONTROL_STREAM_TYPE {
3614            if let Some(ty) = MessageType::from_id(id) {
3615                if starts_a_request_stream(ty) {
3616                    opens.push(id);
3617                }
3618            }
3619        }
3620        opens.sort_unstable();
3621
3622        assert_eq!(
3623            opens, expected,
3624            "the types that open a request stream are {opens:?}; \
3625             draft-18 Section 3.3 names {expected:?}"
3626        );
3627    }
3628
3629    /// The kind a request helper labels its stream with must name the message
3630    /// that helper actually writes, and the check is made against the type
3631    /// varint the encoded message leads with — the byte a peer reads to
3632    /// decide whether the bidirectional stream is legal.
3633    ///
3634    /// This is the mislabelling a port from another draft is most likely to
3635    /// introduce, because the numbers move between drafts while the names do
3636    /// not: SUBSCRIBE_NAMESPACE is 0x11 on draft-17 and 0x50 here.
3637    ///
3638    /// Pointing `RequestKind::SubscribeTracks` at
3639    /// `MessageType::SubscribeNamespace` — the two draft-18 types whose names
3640    /// are closest and whose numbers are adjacent — fails with:
3641    ///
3642    /// ```text
3643    /// assertion `left == right` failed: SubscribeTracks is labelled 80 but its message leads with 81
3644    ///   left: 80
3645    ///  right: 81
3646    /// ```
3647    #[test]
3648    fn each_request_kind_labels_the_message_its_helper_writes() {
3649        use crate::draft18::endpoint::Endpoint;
3650        use moqtap_codec::draft18::message::Setup;
3651
3652        let v = |n: u64| VarInt::from_u64(n).unwrap();
3653        let ns = TrackNamespace(vec![b"ns".to_vec()]);
3654
3655        let mut ep = Endpoint::new(Role::Client);
3656        ep.connect().unwrap();
3657        let _ = ep.send_setup(vec![]).unwrap();
3658        ep.receive_setup(&Setup { options: vec![] }).unwrap();
3659
3660        let (sub_id, subscribe) = ep.subscribe(ns.clone(), b"t".to_vec(), vec![]).unwrap();
3661        let built = vec![
3662            (RequestKind::Subscribe, subscribe),
3663            (
3664                RequestKind::Fetch,
3665                ep.fetch(ns.clone(), b"t".to_vec(), v(0), v(0), v(1), v(1), vec![]).unwrap().1,
3666            ),
3667            (RequestKind::Fetch, ep.joining_fetch(sub_id, v(2), Vec::new()).unwrap().1),
3668            (
3669                RequestKind::SubscribeNamespace,
3670                ep.subscribe_namespace(ns.clone(), vec![]).unwrap().1,
3671            ),
3672            (RequestKind::SubscribeTracks, ep.subscribe_tracks(ns.clone(), vec![]).unwrap().1),
3673            (RequestKind::PublishNamespace, ep.publish_namespace(ns.clone(), vec![]).unwrap().1),
3674            (
3675                RequestKind::TrackStatus,
3676                ep.track_status(ns.clone(), b"t".to_vec(), vec![]).unwrap().1,
3677            ),
3678            (
3679                RequestKind::Publish,
3680                ep.publish(ns.clone(), b"t".to_vec(), v(7), vec![], vec![]).unwrap().1,
3681            ),
3682        ];
3683
3684        for (kind, msg) in built {
3685            let mut wire = Vec::new();
3686            msg.encode(&mut wire).unwrap();
3687            let mut cursor = &wire[..];
3688            let on_the_wire =
3689                DraftVersion::Draft18.decode_varint(&mut cursor).unwrap().into_inner();
3690            assert_eq!(
3691                kind.message_type().id(),
3692                on_the_wire,
3693                "{kind:?} is labelled {} but its message leads with {on_the_wire}",
3694                kind.message_type().id(),
3695            );
3696            assert!(
3697                starts_a_request_stream(kind.message_type()),
3698                "{kind:?} labels a message type that may not begin a bidirectional stream",
3699            );
3700        }
3701    }
3702
3703    /// The classifier the accept path runs and the one
3704    /// [`Connection::send_control`] runs must answer alike for every message
3705    /// type this draft assigns, or a message could be refused on the control
3706    /// stream and refused again as the opening of a request stream — leaving
3707    /// no legal place for it.
3708    ///
3709    /// Dropping `MessageType::SubscribeTracks` to `None` in
3710    /// `from_message_type` — the draft-18 kind a port from draft-17 is most
3711    /// likely to leave out — fails with:
3712    ///
3713    /// ```text
3714    /// assertion `left == right` failed: type 81 opens a request stream but from_message_type calls it None
3715    ///   left: false
3716    ///  right: true
3717    /// ```
3718    #[test]
3719    fn the_two_request_stream_classifiers_agree() {
3720        let mut classified = 0;
3721        for id in 0..=CONTROL_STREAM_TYPE {
3722            let Some(ty) = MessageType::from_id(id) else { continue };
3723            classified += 1;
3724            let kind = RequestKind::from_message_type(ty);
3725            assert_eq!(
3726                kind.is_some(),
3727                starts_a_request_stream(ty),
3728                "type {id} {} a request stream but from_message_type calls it {kind:?}",
3729                if starts_a_request_stream(ty) { "opens" } else { "does not open" },
3730            );
3731            if let Some(kind) = kind {
3732                assert_eq!(
3733                    kind.message_type(),
3734                    ty,
3735                    "from_message_type sent type {id} to {kind:?}, which names a different message",
3736                );
3737            }
3738        }
3739        assert!(classified > 7, "the loop found only {classified} assigned message types");
3740    }
3741
3742    /// A stream this endpoint opened is cancelled when its handle is dropped;
3743    /// one the peer opened is reset as unserved. Both codes are on the wire,
3744    /// so they may not be the same number.
3745    #[test]
3746    fn the_two_abandonment_codes_are_distinct() {
3747        assert_eq!(REQUEST_CANCELLED, 0x1);
3748        assert_eq!(REQUEST_UNANSWERED, 0x0);
3749        assert_ne!(
3750            REQUEST_CANCELLED, REQUEST_UNANSWERED,
3751            "a peer cannot tell a rejected request from a dropped one if both reset with the same code",
3752        );
3753    }
3754
3755    #[test]
3756    fn transport_type_debug() {
3757        let quic = TransportType::Quic;
3758        assert!(format!("{quic:?}").contains("Quic"));
3759
3760        let wt = TransportType::WebTransport { url: "https://example.com".to_string() };
3761        assert!(format!("{wt:?}").contains("WebTransport"));
3762    }
3763}
3764#[cfg(test)]
3765mod accept_on_the_wire {
3766    //! The accept path against a real QUIC peer.
3767    //!
3768    //! Draft-18's connection module has had one of these since it was written,
3769    //! and every transport rule this draft shares with it was gated only there
3770    //! — which meant the rules were carried here by the shape of the edit
3771    //! rather than by anything that observes them. What a peer can see, only a
3772    //! peer can check.
3773
3774    use super::*;
3775    use std::sync::Arc;
3776
3777    use std::net::SocketAddr;
3778    use std::time::Duration;
3779
3780    use moqtap_codec::draft18::message::{Setup, SubscribeNamespace};
3781
3782    /// Long enough that a loaded machine cannot fail a test that would
3783    /// otherwise pass, short enough that a hang is reported rather than run to
3784    /// the harness timeout.
3785    const PATIENCE: Duration = Duration::from_secs(10);
3786
3787    fn v(n: u64) -> VarInt {
3788        VarInt::from_u64(n).unwrap()
3789    }
3790
3791    fn ns() -> TrackNamespace {
3792        TrackNamespace(vec![b"live".to_vec()])
3793    }
3794
3795    fn encode(msg: ControlMessage) -> Vec<u8> {
3796        let mut buf = Vec::new();
3797        AnyControlMessage::Draft18(msg).encode(&mut buf).expect("encode");
3798        buf
3799    }
3800
3801    fn request_update(id: u64) -> ControlMessage {
3802        ControlMessage::RequestUpdate(moqtap_codec::draft18::message::RequestUpdate {
3803            request_id: v(id),
3804            parameters: vec![],
3805        })
3806    }
3807
3808    fn request_error() -> RequestError {
3809        RequestError {
3810            error_code: v(0x1),
3811            retry_interval: v(0),
3812            reason_phrase: b"no".to_vec(),
3813            redirect: None,
3814        }
3815    }
3816
3817    fn peer_fetch(id: u64) -> ControlMessage {
3818        ControlMessage::Fetch(moqtap_codec::draft18::message::Fetch {
3819            request_id: v(id),
3820            fetch_type: moqtap_codec::draft18::message::FetchType::Standalone,
3821            fetch_payload: moqtap_codec::draft18::message::FetchPayload::Standalone {
3822                track_namespace: ns(),
3823                track_name: b"video".to_vec(),
3824                start_group: v(0),
3825                start_object: v(0),
3826                end_group: v(1),
3827                end_object: v(0),
3828            },
3829            parameters: vec![],
3830        })
3831    }
3832
3833    fn fetch_ok() -> FetchOk {
3834        FetchOk {
3835            end_of_track: 0,
3836            end_group: v(1),
3837            end_object: v(0),
3838            parameters: vec![],
3839            track_properties: vec![],
3840        }
3841    }
3842
3843    fn init_crypto() {
3844        let _ = rustls::crypto::ring::default_provider().install_default();
3845    }
3846
3847    /// A quinn server on a loopback port, offering this draft's ALPN.
3848    fn server_endpoint() -> (quinn::Endpoint, SocketAddr) {
3849        use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P256_SHA256};
3850        use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
3851
3852        let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).expect("keypair");
3853        let params = CertificateParams::new(vec!["localhost".into()]).expect("params");
3854        let cert = params.self_signed(&key_pair).expect("self-sign");
3855        let cert_der = CertificateDer::from(cert.der().to_vec());
3856        let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
3857
3858        let mut server_crypto = rustls::ServerConfig::builder()
3859            .with_no_client_auth()
3860            .with_single_cert(vec![cert_der], key_der)
3861            .expect("server cert");
3862        server_crypto.alpn_protocols = vec![DraftVersion::Draft18.quic_alpn().to_vec()];
3863        let server_crypto =
3864            quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto).expect("quic crypto");
3865        let server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto));
3866        let endpoint = quinn::Endpoint::server(server_config, "127.0.0.1:0".parse().unwrap())
3867            .expect("bind server");
3868        let addr = endpoint.local_addr().expect("local_addr");
3869        (endpoint, addr)
3870    }
3871
3872    async fn connect_client(addr: SocketAddr) -> Result<Connection, ConnectionError> {
3873        Connection::connect(
3874            &addr.to_string(),
3875            ClientConfig {
3876                draft: DraftVersion::Draft18,
3877                transport: TransportType::Quic,
3878                skip_cert_verification: true,
3879                ca_certs: Vec::new(),
3880                setup_parameters: Vec::new(),
3881            },
3882        )
3883        .await
3884    }
3885
3886    /// The peer's half of the setup exchange: read the client's SETUP off its
3887    /// unidirectional control stream, answer with one of our own.
3888    ///
3889    /// Both control streams are handed back so they stay open for the
3890    /// connection's life. Dropping a quinn receive stream sends STOP_SENDING
3891    /// and dropping a send stream resets it, either of which would look to the
3892    /// client like the control plane failing.
3893    async fn peer_handshake(
3894        endpoint: &quinn::Endpoint,
3895    ) -> (quinn::Connection, quinn::SendStream, quinn::RecvStream) {
3896        let conn = endpoint.accept().await.expect("accept").await.expect("tls handshake");
3897        let mut client_control = conn.accept_uni().await.expect("accept_uni");
3898        let mut seen = Vec::new();
3899        let mut chunk = [0u8; 1024];
3900        while seen.len() < 3 {
3901            match client_control.read(&mut chunk).await.expect("read SETUP") {
3902                Some(n) => seen.extend_from_slice(&chunk[..n]),
3903                None => break,
3904            }
3905        }
3906        assert!(!seen.is_empty(), "the client sent no SETUP");
3907        let mut ours = conn.open_uni().await.expect("open_uni");
3908        ours.write_all(&encode(ControlMessage::Setup(Setup { options: Vec::new() })))
3909            .await
3910            .expect("write SETUP");
3911        (conn, ours, client_control)
3912    }
3913
3914    /// A connected client and the peer holding the other end.
3915    struct Loopback {
3916        conn: Connection,
3917        peer: quinn::Connection,
3918        _endpoint: quinn::Endpoint,
3919        _control_send: quinn::SendStream,
3920        _control_recv: quinn::RecvStream,
3921    }
3922
3923    async fn loopback() -> Loopback {
3924        init_crypto();
3925        let (endpoint, addr) = server_endpoint();
3926        let (client, peer) = tokio::join!(connect_client(addr), peer_handshake(&endpoint));
3927        let (peer, control_send, control_recv) = peer;
3928        Loopback {
3929            conn: client.expect("client connect"),
3930            peer,
3931            _endpoint: endpoint,
3932            _control_send: control_send,
3933            _control_recv: control_recv,
3934        }
3935    }
3936
3937    fn framed(recv: quinn::RecvStream) -> FramedRecvStream {
3938        FramedRecvStream::new(RecvStream::Quic(recv), DraftVersion::Draft18)
3939    }
3940
3941    /// Read one control message the client wrote, failing rather than hanging.
3942    async fn next_control(recv: &mut FramedRecvStream) -> ControlMessage {
3943        let (any, _) = tokio::time::timeout(PATIENCE, recv.read_control(false))
3944            .await
3945            .expect("the client wrote nothing")
3946            .expect("read control");
3947        match any {
3948            AnyControlMessage::Draft18(msg) => msg,
3949            #[allow(unreachable_patterns)]
3950            other => panic!("expected a draft-18 message, got {other:?}"),
3951        }
3952    }
3953
3954    /// An update on a PUBLISH this endpoint sent is answered here.
3955    ///
3956    /// Section 10.9 names the one case where a requester answers rather than
3957    /// asks: "A subscriber can also send REQUEST_UPDATE to modify parameters
3958    /// of a subscription established with PUBLISH." The receiver of that
3959    /// update "MUST respond with exactly one REQUEST_OK or REQUEST_ERROR
3960    /// message indicating if the update was successful", and on a PUBLISH this
3961    /// endpoint sent, the receiver is this endpoint.
3962    ///
3963    /// # What it catches
3964    ///
3965    /// Restoring the origin guard on this draft's `respond`, so that no
3966    /// response is written on a stream this endpoint opened:
3967    ///
3968    /// ```text
3969    /// the subscriber's update is this endpoint's to answer:
3970    /// RespondedToOwnRequest(0)
3971    /// ```
3972    ///
3973    /// It reddens this gate and the one below it, on draft-18's own line, and
3974    /// nothing else in the client or the proxy.
3975    #[tokio::test]
3976    async fn an_update_on_a_publish_we_sent_is_answered_here() {
3977        let mut lb = loopback().await;
3978
3979        let mut outbound =
3980            lb.conn.publish(ns(), b"video".to_vec(), v(7), vec![], vec![]).await.expect("publish");
3981        let (mut their_send, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
3982            .await
3983            .expect("the client opened no request stream")
3984            .expect("accept_bi");
3985        let mut their_recv = framed(their_recv);
3986        assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Publish(_)));
3987
3988        // The subscriber accepts the publication, then updates it.
3989        their_send
3990            .write_all(&encode(ControlMessage::RequestOk(RequestOk {
3991                parameters: vec![],
3992                track_properties: vec![],
3993            })))
3994            .await
3995            .expect("write REQUEST_OK");
3996        let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
3997            .await
3998            .expect("no REQUEST_OK arrived")
3999            .expect("read REQUEST_OK");
4000        assert!(matches!(msg, ControlMessage::RequestOk(_)), "{msg:?}");
4001
4002        their_send.write_all(&encode(request_update(0))).await.expect("write REQUEST_UPDATE");
4003        let msg = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
4004            .await
4005            .expect("no REQUEST_UPDATE arrived")
4006            .expect("read REQUEST_UPDATE");
4007        assert!(matches!(msg, ControlMessage::RequestUpdate(_)), "{msg:?}");
4008
4009        lb.conn
4010            .respond_ok(&mut outbound, RequestOk { parameters: vec![], track_properties: vec![] })
4011            .await
4012            .expect("the subscriber's update is this endpoint's to answer");
4013        assert!(matches!(next_control(&mut their_recv).await, ControlMessage::RequestOk(_)));
4014
4015        // The publication is untouched by the update, and the ending it still
4016        // owes goes out without complaint. Draft-19 keeps that obligation on
4017        // the request stream and can be asked; this draft does not, so the
4018        // ending being accepted is the observation.
4019        lb.conn
4020            .publish_done(&mut outbound, v(0), v(0), Vec::new())
4021            .await
4022            .expect("an accepted update leaves the ending free");
4023    }
4024
4025    /// Refusing that update owes the same ending as refusing any other.
4026    ///
4027    /// Section 10.9.1: "When a REQUEST_UPDATE is unsuccessful, the publisher
4028    /// MUST also terminate the subscription by sending a PUBLISH_DONE with
4029    /// error code UPDATE_FAILED." The publisher of a subscription established
4030    /// with PUBLISH is the endpoint that sent it, and the ending goes on the
4031    /// stream that endpoint opened rather than on one the peer opened.
4032    ///
4033    /// # What it catches
4034    ///
4035    /// The same cut as the gate above, restoring the origin guard:
4036    ///
4037    /// ```text
4038    /// refuse the subscriber's update: RespondedToOwnRequest(0)
4039    /// ```
4040    ///
4041    /// And narrowing the refusal's debt back to a peer's SUBSCRIBE, so the
4042    /// endpoint that sent the PUBLISH owes nothing for refusing an update on
4043    /// it — after which the ending goes out under any status and the stream
4044    /// has already been finished:
4045    ///
4046    /// ```text
4047    /// transport error: write error: closed stream
4048    /// ```
4049    #[tokio::test]
4050    async fn a_refused_update_on_a_publish_we_sent_owes_its_ending() {
4051        let mut lb = loopback().await;
4052
4053        let mut outbound =
4054            lb.conn.publish(ns(), b"video".to_vec(), v(7), vec![], vec![]).await.expect("publish");
4055        let (mut their_send, their_recv) = tokio::time::timeout(PATIENCE, lb.peer.accept_bi())
4056            .await
4057            .expect("the client opened no request stream")
4058            .expect("accept_bi");
4059        let mut their_recv = framed(their_recv);
4060        assert!(matches!(next_control(&mut their_recv).await, ControlMessage::Publish(_)));
4061
4062        their_send
4063            .write_all(&encode(ControlMessage::RequestOk(RequestOk {
4064                parameters: vec![],
4065                track_properties: vec![],
4066            })))
4067            .await
4068            .expect("write REQUEST_OK");
4069        tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
4070            .await
4071            .expect("no REQUEST_OK arrived")
4072            .expect("read REQUEST_OK");
4073
4074        their_send.write_all(&encode(request_update(0))).await.expect("write REQUEST_UPDATE");
4075        tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut outbound))
4076            .await
4077            .expect("no REQUEST_UPDATE arrived")
4078            .expect("read REQUEST_UPDATE");
4079
4080        lb.conn
4081            .respond_error(&mut outbound, request_error())
4082            .await
4083            .expect("refuse the subscriber's update");
4084        assert!(matches!(next_control(&mut their_recv).await, ControlMessage::RequestError(_)));
4085
4086        // The ending is owed under one status, and asking for another leaves
4087        // the publication exactly where it was rather than half ended.
4088        let err = lb
4089            .conn
4090            .publish_done(&mut outbound, v(0), v(0), Vec::new())
4091            .await
4092            .expect_err("a refused update fixes the status of the ending");
4093        assert!(
4094            matches!(
4095                err,
4096                ConnectionError::Endpoint(EndpointError::WrongUpdateFailureStatus {
4097                    request: 0,
4098                    required: 0x8,
4099                })
4100            ),
4101            "{err}",
4102        );
4103        lb.conn
4104            .publish_done(&mut outbound, v(0x8), v(0), Vec::new())
4105            .await
4106            .expect("the termination the refusal owes");
4107        assert!(matches!(next_control(&mut their_recv).await, ControlMessage::PublishDone(_)));
4108    }
4109
4110    /// A refused namespace update closes the stream rather than owing a message.
4111    ///
4112    /// Section 10.9.1 sorts a refused REQUEST_UPDATE by what was being
4113    /// updated, and a namespace subscription falls in the clause that ends
4114    /// with the transport rather than with a message: "When a REQUEST_UPDATE
4115    /// fails for a SUBSCRIBE_NAMESPACE or PUBLISH_NAMESPACE, the responder
4116    /// MUST close the bidi stream." Draft-19 adds SUBSCRIBE_TRACKS to that
4117    /// list and a pointer to its own Section 3.3.2; this draft has neither. There is
4118    /// no subscription to terminate here and no PUBLISH_DONE that could go
4119    /// out, so a send half held open for one would stay open for good.
4120    ///
4121    /// # What it catches
4122    ///
4123    /// Recording the refusal's debt for a namespace subscription, which is
4124    /// what makes the connection hold a stream open for a message that
4125    /// subscription has no way to send:
4126    ///
4127    /// ```text
4128    /// the peer is still reading: the refusal never closed the send half:
4129    /// Elapsed(())
4130    /// ```
4131    ///
4132    /// Measured on draft-18's own predicate, not inherited from draft-19's.
4133    #[tokio::test]
4134    async fn a_refused_namespace_update_closes_the_stream() {
4135        let mut lb = loopback().await;
4136
4137        let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
4138        ps.write_all(&encode(ControlMessage::SubscribeNamespace(SubscribeNamespace {
4139            request_id: v(1),
4140            namespace_prefix: ns(),
4141            parameters: vec![],
4142        })))
4143        .await
4144        .expect("write SUBSCRIBE_NAMESPACE");
4145        let mut pr = framed(pr);
4146
4147        let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
4148            .await
4149            .expect("accept hung")
4150            .expect("accept");
4151        lb.conn
4152            .respond_ok(&mut stream, RequestOk { parameters: vec![], track_properties: vec![] })
4153            .await
4154            .expect("respond_ok");
4155        assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestOk(_)));
4156
4157        // The peer asks to move the prefix and this endpoint refuses.
4158        ps.write_all(&encode(request_update(1))).await.expect("write REQUEST_UPDATE");
4159        let update = tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
4160            .await
4161            .expect("read hung")
4162            .expect("read REQUEST_UPDATE");
4163        assert!(matches!(update, ControlMessage::RequestUpdate(_)));
4164        lb.conn.respond_error(&mut stream, request_error()).await.expect("refuse the update");
4165        assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
4166
4167        // The refusal ends this request stream, so the peer's next read finds
4168        // the send half closed rather than waiting on a message a namespace
4169        // subscription has no way to send.
4170        let ended = tokio::time::timeout(PATIENCE, pr.read_control(false))
4171            .await
4172            .expect("the peer is still reading: the refusal never closed the send half")
4173            .expect_err("a refused namespace update left the stream open");
4174        assert!(
4175            matches!(ended, ConnectionError::UnexpectedEnd),
4176            "the peer should have seen the FIN, got {ended:?}",
4177        );
4178    }
4179
4180    /// A refused fetch update resets the stream the objects were going out on.
4181    ///
4182    /// Section 10.9.1: "When a REQUEST_UPDATE fails for a FETCH, the publisher
4183    /// MUST reset the FETCH data stream." The objects are not on the request
4184    /// stream, so refusing the update on that stream is only half of it; the
4185    /// data stream has to go too, and the connection can only reset a handle
4186    /// it still holds.
4187    ///
4188    /// # What it catches
4189    ///
4190    /// Refusing the update on the request stream and leaving the data stream
4191    /// alone, which is half the sentence:
4192    ///
4193    /// ```text
4194    /// the peer is still waiting on a stream that should have been reset:
4195    /// Elapsed(())
4196    /// ```
4197    ///
4198    /// It reddens this gate and nothing else in the client or the proxy.
4199    #[tokio::test]
4200    async fn a_refused_fetch_update_resets_the_fetch_data_stream() {
4201        let mut lb = loopback().await;
4202
4203        let (mut ps, pr) = lb.peer.open_bi().await.expect("open_bi");
4204        ps.write_all(&encode(peer_fetch(1))).await.expect("write FETCH");
4205        let mut pr = framed(pr);
4206
4207        let (_, mut stream) = tokio::time::timeout(PATIENCE, lb.conn.accept_request_stream())
4208            .await
4209            .expect("accept hung")
4210            .expect("accept");
4211        assert_eq!(stream.kind(), RequestKind::Fetch);
4212        lb.conn.respond_fetch_ok(&mut stream, fetch_ok()).await.expect("respond_fetch_ok");
4213        assert!(matches!(next_control(&mut pr).await, ControlMessage::FetchOk(_)));
4214
4215        // The objects go out on a stream of their own, which the request now
4216        // holds.
4217        lb.conn
4218            .open_fetch_stream_on(
4219                &mut stream,
4220                &AnyFetchHeader::Draft18(FetchHeader { request_id: v(1) }),
4221            )
4222            .await
4223            .expect("open the fetch data stream");
4224        let data = tokio::time::timeout(PATIENCE, lb.peer.accept_uni())
4225            .await
4226            .expect("no data stream arrived")
4227            .expect("accept_uni");
4228        let mut data = framed(data);
4229        tokio::time::timeout(PATIENCE, data.read_fetch_header())
4230            .await
4231            .expect("the header never arrived")
4232            .expect("the data stream opens with a FETCH_HEADER");
4233
4234        // The peer updates the fetch and this endpoint refuses it.
4235        ps.write_all(&encode(request_update(1))).await.expect("write REQUEST_UPDATE");
4236        tokio::time::timeout(PATIENCE, lb.conn.recv_on_request_stream(&mut stream))
4237            .await
4238            .expect("read hung")
4239            .expect("read REQUEST_UPDATE");
4240        lb.conn.respond_error(&mut stream, request_error()).await.expect("refuse the update");
4241        assert!(matches!(next_control(&mut pr).await, ControlMessage::RequestError(_)));
4242
4243        // The data stream went with it: the peer's next read on it fails
4244        // rather than waiting for objects that are not coming.
4245        let err = tokio::time::timeout(PATIENCE, data.read_control(false))
4246            .await
4247            .expect("the peer is still waiting on a stream that should have been reset")
4248            .expect_err("the data stream should have been reset");
4249        assert!(
4250            format!("{err}").to_lowercase().contains("reset"),
4251            "the peer should see a reset, got {err}",
4252        );
4253    }
4254}