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