Skip to main content

moqtap_client/draft20/
connection.rs

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