Skip to main content

moqtap_client/draft21/
connection.rs

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