Skip to main content

moqtap_client/draft20/
connection.rs

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