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