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