Skip to main content

moqtap_client/draft19/
connection.rs

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