Skip to main content

moqtap_client/draft19/
connection.rs

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