Skip to main content

moqtap_client/draft19/
connection.rs

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