Skip to main content

moqtap_client/draft23/
connection.rs

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