moqtap_client/draft12/connection.rs
1use bytes::{Buf, Bytes, BytesMut};
2
3use crate::draft12::endpoint::{Endpoint, EndpointError};
4use crate::draft12::event::{ClientEvent, Direction, FetchObject, StreamKind, SubgroupObject};
5use crate::draft12::observer::ConnectionObserver;
6use crate::draft12::session::request_id::Role;
7use crate::draft12::session::setup;
8use crate::forwarding_preference::ObjectForwardingPreference;
9use crate::malformed_tracks::MalformedTrackCondition;
10use crate::track_locations::{EndOfTrackForm, ObjectLocation, ObjectRole, TrackObjects};
11use crate::transport::{RecvStream, SendStream, Transport, TransportError};
12use moqtap_codec::dispatch::{
13 AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
14};
15use moqtap_codec::draft12::data_stream::{FetchObjectHeader, ObjectHeader};
16use moqtap_codec::draft12::message::ControlMessage;
17use moqtap_codec::error::CodecError;
18use moqtap_codec::kvp::KeyValuePair;
19use moqtap_codec::types::*;
20use moqtap_codec::varint::VarInt;
21use moqtap_codec::version::DraftVersion;
22
23/// MoQT ALPN identifier (used by raw QUIC transport).
24pub const MOQT_ALPN: &[u8] = b"moq-00";
25
26/// Errors from the draft-12 connection layer.
27#[derive(Debug, thiserror::Error)]
28pub enum ConnectionError {
29 /// Endpoint state machine error.
30 #[error("endpoint error: {0}")]
31 Endpoint(#[from] EndpointError),
32 /// Wire codec error.
33 #[error("codec error: {0}")]
34 Codec(#[from] CodecError),
35 /// Transport-level error.
36 #[error("transport error: {0}")]
37 Transport(#[from] TransportError),
38 /// Variable-length integer decoding error.
39 #[error("varint error: {0}")]
40 VarInt(#[from] moqtap_codec::varint::VarIntError),
41 /// Control stream was not opened.
42 #[error("control stream not open")]
43 NoControlStream,
44 /// Stream ended before a complete message was read.
45 #[error("unexpected end of stream")]
46 UnexpectedEnd,
47 /// Stream was finished by the peer.
48 #[error("stream finished")]
49 StreamFinished,
50 /// Invalid server address string.
51 #[error("invalid server address: {0}")]
52 InvalidAddress(String),
53 /// TLS configuration error.
54 #[error("TLS config error: {0}")]
55 TlsConfig(String),
56 /// Data stream used out of order: an object before its header, or an
57 /// Object ID that does not advance on the last one written.
58 #[error("data stream state error: {0}")]
59 DataStreamState(&'static str),
60}
61
62impl From<crate::transport::DialError> for ConnectionError {
63 /// Preserves the variants this error had when the dial was inlined here,
64 /// so a caller matching on `InvalidAddress` or `TlsConfig` sees no change.
65 fn from(e: crate::transport::DialError) -> Self {
66 match e {
67 crate::transport::DialError::InvalidAddress(s) => ConnectionError::InvalidAddress(s),
68 crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
69 crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
70 }
71 }
72}
73
74/// Transport type for the connection.
75#[derive(Debug, Clone)]
76pub enum TransportType {
77 /// Raw QUIC via quinn. The `addr` field should be `host:port`.
78 Quic,
79 /// WebTransport via wtransport. The `url` field is the WebTransport URL.
80 WebTransport {
81 /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
82 url: String,
83 },
84}
85
86/// Configuration for a draft-12 MoQT client connection.
87pub struct ClientConfig {
88 /// Additional draft versions to offer in CLIENT_SETUP (draft-12 is always
89 /// offered first).
90 pub additional_versions: Vec<DraftVersion>,
91 /// The transport type (QUIC or WebTransport).
92 pub transport: TransportType,
93 /// Whether to skip TLS certificate verification (for testing).
94 pub skip_cert_verification: bool,
95 /// Custom CA certificates to trust (DER-encoded).
96 pub ca_certs: Vec<Vec<u8>>,
97 /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
98 pub setup_parameters: Vec<moqtap_codec::kvp::KeyValuePair>,
99}
100
101impl ClientConfig {
102 /// Returns the MoQT version varints for the CLIENT_SETUP message.
103 /// Draft-12 first, then any additional versions.
104 pub fn supported_versions(&self) -> Vec<VarInt> {
105 let mut versions = vec![DraftVersion::Draft12.version_varint()];
106 for v in &self.additional_versions {
107 let varint = v.version_varint();
108 if !versions.contains(&varint) {
109 versions.push(varint);
110 }
111 }
112 versions
113 }
114
115 /// Returns the ALPN protocol identifiers for the transport.
116 pub fn alpn(&self) -> Vec<Vec<u8>> {
117 match &self.transport {
118 TransportType::Quic => vec![DraftVersion::Draft12.quic_alpn().to_vec()],
119 TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
120 }
121 }
122}
123
124/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
125pub struct FramedSendStream {
126 inner: SendStream,
127 /// What the subgroup header opened on this stream settled, once one has
128 /// been written.
129 ///
130 /// `None` until a subgroup header has been written, which is what makes an
131 /// object sent before its header answerable rather than unframed bytes.
132 subgroup_objects: Option<SubgroupStreamState>,
133}
134
135/// What a subgroup header fixes for every object written after it.
136///
137/// Two facts, and neither is recoverable from an object on its own: the
138/// Object ID it has to advance past, and whether the stream's type puts an
139/// extension block on each object. The second is the one that used to be
140/// guessed - always "absent" - which wrote a stream a reader could not follow
141/// whenever the header said otherwise.
142#[derive(Debug, Clone, Copy)]
143struct SubgroupStreamState {
144 /// The last Object ID written, or `None` before the first object.
145 previous_object_id: Option<u64>,
146 /// Whether each object writes an Extension Headers Length field.
147 carries_extension_block: bool,
148}
149
150impl FramedSendStream {
151 /// Create a new framed send stream.
152 pub fn new(inner: SendStream) -> Self {
153 Self { inner, subgroup_objects: None }
154 }
155
156 /// Get the transport-level stream ID.
157 pub fn stream_id(&self) -> u64 {
158 self.inner.stream_id()
159 }
160
161 /// Write a control message to the stream with type+length framing.
162 /// Returns the raw bytes that were written (for event capture).
163 pub async fn write_control(
164 &mut self,
165 msg: &AnyControlMessage,
166 ) -> Result<Vec<u8>, ConnectionError> {
167 let mut buf = Vec::new();
168 msg.encode(&mut buf)?;
169 self.inner.write_all(&buf).await?;
170 Ok(buf)
171 }
172
173 /// Write a subgroup stream header. Also opens the Object ID bookkeeping
174 /// [`FramedSendStream::write_subgroup_object`] holds the stream to.
175 ///
176 /// The header is refused, and nothing is written, if its fields disagree
177 /// with its own stream type. That check has to happen here rather than at
178 /// the first object: the type is what every object after it is framed
179 /// against, so a header that went out saying the wrong thing cannot be
180 /// taken back.
181 pub async fn write_subgroup_header(
182 &mut self,
183 header: &AnySubgroupHeader,
184 ) -> Result<(), ConnectionError> {
185 let mut buf = Vec::new();
186 header.encode_stream_checked(&mut buf)?;
187 self.inner.write_all(&buf).await?;
188 // The framing is read off the header now, while the header is in hand.
189 // An object arriving later cannot say whether the stream carries an
190 // extension block, and writing one either way is not a recoverable
191 // mistake: the reader takes the next field along as the missing length
192 // and misframes every object after it.
193 self.subgroup_objects = Some(SubgroupStreamState {
194 previous_object_id: None,
195 carries_extension_block: header.carries_extension_block(),
196 });
197 Ok(())
198 }
199
200 /// Write a fetch response header.
201 pub async fn write_fetch_header(
202 &mut self,
203 header: &AnyFetchHeader,
204 ) -> Result<(), ConnectionError> {
205 let mut buf = Vec::new();
206 header.encode_stream(&mut buf);
207 self.inner.write_all(&buf).await?;
208 Ok(())
209 }
210
211 /// Append a draft-12 subgroup object (header + payload) to the stream.
212 ///
213 /// Section 9.4.2: "A publisher MUST NOT send an Object on a stream if its
214 /// Object ID is less than a previously sent Object ID within a given group
215 /// in that stream." A subgroup stream carries one group, so the Object IDs
216 /// written here are exactly the ones that sentence compares, and the
217 /// comparison needs the object before - which no per-header check can see.
218 /// The state advances only once the object has been written, so declining to
219 /// write an object leaves the next one measured against the last one kept.
220 ///
221 /// An equal Object ID is refused as well as a smaller one. The draft's own
222 /// sentence forbids only "less than", but an Object ID names an Object
223 /// within a Group: writing one twice on a stream describes the same Object
224 /// with two different payloads, and a reader has no way to choose. The
225 /// dispatch-level writer in the codec draws the line in the same place, and
226 /// two writers that disagreed about it would be worse than either answer.
227 ///
228 /// # Errors
229 ///
230 /// [`ConnectionError::DataStreamState`] if no subgroup header has been
231 /// written yet, or if `object` does not advance past the last one written.
232 pub async fn write_subgroup_object(
233 &mut self,
234 object: &SubgroupObject,
235 ) -> Result<(), ConnectionError> {
236 let state = self
237 .subgroup_objects
238 .as_mut()
239 .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
240 let object_id = object.header.object_id.into_inner();
241 if matches!(state.previous_object_id, Some(prev) if object_id <= prev) {
242 return Err(ConnectionError::DataStreamState(
243 "object id does not advance on the last one written to this stream",
244 ));
245 }
246 // The declared length comes from the payload rather than from the
247 // caller's field: a header that disagrees with the bytes beside it
248 // desynchronises every object after it on the stream, and nothing
249 // downstream can recover.
250 let mut header = object.header.clone();
251 header.payload_length = VarInt::from_usize(object.payload.len());
252 let mut buf = Vec::new();
253 header.encode_checked_with_extensions(state.carries_extension_block, &mut buf)?;
254 buf.extend_from_slice(&object.payload);
255 self.inner.write_all(&buf).await?;
256 state.previous_object_id = Some(object_id);
257 Ok(())
258 }
259
260 /// Append a draft-12 fetch object (header + payload) to the stream.
261 pub async fn write_fetch_object(
262 &mut self,
263 object: &FetchObject,
264 ) -> Result<(), ConnectionError> {
265 // The declared length comes from the payload rather than from the
266 // caller's field: a header that disagrees with the bytes beside it
267 // desynchronises every object after it on the stream, and nothing
268 // downstream can recover.
269 let mut header = object.header.clone();
270 header.payload_length = VarInt::from_usize(object.payload.len());
271 let mut buf = Vec::new();
272 header.encode_checked(&mut buf)?;
273 buf.extend_from_slice(&object.payload);
274 self.inner.write_all(&buf).await?;
275 Ok(())
276 }
277
278 /// Finish the stream (send FIN).
279 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
280 self.inner.finish()?;
281 Ok(())
282 }
283}
284
285/// What an Object Status makes of an object, for Section 9.2.1.1's table.
286///
287/// `None` is Normal and not "no status": a datagram carrying a payload has no
288/// status field at all, and an object with a payload is an ordinary object.
289fn object_role(status: Option<u64>) -> ObjectRole {
290 match status {
291 None | Some(0x0) => ObjectRole::Produced,
292 // 0x4, end of Track. This draft folded drafts 08 through 10's second
293 // end-of-track status into this one and kept the looser of the two
294 // conditions, so the Group ID may equal the largest group seen.
295 Some(0x4) => ObjectRole::EndsTrack(Some(EndOfTrackForm::LastGroup)),
296 // Every other status is a statement about objects rather than one of
297 // them. Two of them name an Object ID one past the largest on purpose,
298 // so counting one as produced would refuse the end-of-track object the
299 // draft goes on to define.
300 _ => ObjectRole::Neither,
301 }
302}
303
304/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
305pub struct FramedRecvStream {
306 inner: RecvStream,
307 buf: BytesMut,
308 /// The record this stream's objects are measured against, and the Group ID
309 /// its header named.
310 ///
311 /// One group for the whole stream: a subgroup header names it once and no
312 /// object header repeats it. `None` on a stream that was never given one —
313 /// a stream for an alias no live binding names, and every stream built
314 /// outside [`Connection::accept_subgroup_stream`] — and such a stream reads
315 /// exactly as it did before this existed.
316 tracking: Option<(TrackObjects, u64)>,
317 /// Whether the subgroup stream this reader is on carries an extension block
318 /// on every object.
319 ///
320 /// The stream's Type says so and nothing in an object header repeats it, so
321 /// a reader that does not remember the Type cannot parse the objects at all:
322 /// on an extensions-bearing stream it reads the Extension Headers Length as
323 /// the Object Payload Length and every field after it is nonsense.
324 ///
325 /// Set by [`FramedRecvStream::read_subgroup_header`] and read by
326 /// [`FramedRecvStream::read_subgroup_object`]. `false` until a subgroup
327 /// header has been read, which is the only order those two may be called
328 /// in.
329 subgroup_has_extensions: bool,
330}
331
332impl FramedRecvStream {
333 /// Create a new framed receive stream.
334 pub fn new(inner: RecvStream) -> Self {
335 Self {
336 inner,
337 buf: BytesMut::with_capacity(4096),
338 subgroup_has_extensions: false,
339 tracking: None,
340 }
341 }
342
343 /// Measure this stream's objects against `objects`, all of them in `group`.
344 ///
345 /// Called by [`Connection::accept_subgroup_stream`] once the header has been
346 /// read, which is the only point at which both the track and the group are
347 /// known.
348 fn measure_objects_against(&mut self, objects: TrackObjects, group: u64) {
349 self.tracking = Some((objects, group));
350 }
351
352 /// Record or judge one object this stream carried.
353 ///
354 /// The whole of Section 9.2.1.1's rule that a single header cannot settle: the
355 /// object's Group ID is the stream's, its Object ID is its own, and what
356 /// they are measured against is everything the track has carried on any
357 /// stream.
358 fn note_subgroup_object(&self, header: &ObjectHeader) -> Result<(), ConnectionError> {
359 let Some((objects, group)) = &self.tracking else { return Ok(()) };
360 let at = ObjectLocation { group: *group, object: header.object_id.into_inner() };
361 objects.note_with_final_object(at, object_role(Some(header.object_status as u64))).map_err(
362 |fault| {
363 ConnectionError::Endpoint(EndpointError::for_track_fault(
364 objects.alias(),
365 at,
366 fault,
367 ))
368 },
369 )
370 }
371
372 /// Get the transport-level stream ID.
373 pub fn stream_id(&self) -> u64 {
374 self.inner.stream_id()
375 }
376
377 /// Read more data from the stream into the internal buffer.
378 async fn fill(&mut self) -> Result<bool, ConnectionError> {
379 let mut tmp = [0u8; 4096];
380 match self.inner.read(&mut tmp).await {
381 Ok(Some(n)) => {
382 self.buf.extend_from_slice(&tmp[..n]);
383 Ok(true)
384 }
385 Ok(None) => Ok(false),
386 Err(e) => Err(ConnectionError::Transport(e)),
387 }
388 }
389
390 /// Ensure at least `n` bytes are available in the buffer.
391 async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
392 while self.buf.len() < n {
393 if !self.fill().await? {
394 return Err(ConnectionError::UnexpectedEnd);
395 }
396 }
397 Ok(())
398 }
399
400 /// Read a control message from the stream.
401 ///
402 /// When `capture_raw` is true, the returned tuple includes a clone of the
403 /// framed wire bytes (for observer emission). When false, the second
404 /// element is `None` and the payload clone is skipped.
405 pub async fn read_control(
406 &mut self,
407 capture_raw: bool,
408 ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
409 // Read type ID varint
410 self.ensure(1).await?;
411 let type_len = varint_len(self.buf[0]);
412 self.ensure(type_len).await?;
413
414 let mut cursor = &self.buf[..type_len];
415 let _type_id = VarInt::decode(&mut cursor)?;
416
417 // Draft-12 uses a fixed 16-bit length after the type id.
418 let len_field_size = 2;
419 self.ensure(type_len + len_field_size).await?;
420 let payload_len = u16::from_be_bytes([self.buf[type_len], self.buf[type_len + 1]]) as usize;
421
422 // Read full payload
423 let total = type_len + len_field_size + payload_len;
424 self.ensure(total).await?;
425
426 // Capture raw bytes only if requested (observer attached).
427 let raw = capture_raw.then(|| self.buf[..total].to_vec());
428
429 // Now decode the whole message using the draft-12 dispatcher
430 let mut frame = &self.buf[..total];
431 let msg = AnyControlMessage::decode(DraftVersion::Draft12, &mut frame)?;
432 self.buf.advance(total);
433 Ok((msg, raw))
434 }
435
436 /// Read a subgroup stream header.
437 pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
438 self.ensure(1).await?;
439 loop {
440 let mut cursor = &self.buf[..];
441 match AnySubgroupHeader::decode_stream(DraftVersion::Draft12, &mut cursor) {
442 Ok(header) => {
443 let consumed = self.buf.len() - cursor.remaining();
444 self.buf.advance(consumed);
445 // Clippy would rather see these two arms as an `if let`, and rustc rejects
446 // that in a single-draft build, where the pattern is irrefutable. Only a
447 // `match` satisfies both.
448 #[allow(clippy::single_match)]
449 match header {
450 AnySubgroupHeader::Draft12(ref d) => {
451 self.subgroup_has_extensions = d.stream_type.has_extensions();
452 }
453 // Only this draft's header sets the flag. With draft 12 the only enabled
454 // draft `AnySubgroupHeader` has a single variant, the arm above is
455 // exhaustive and this one unreachable. Compiled in every configuration with
456 // the lint allowed, rather than gated on a `cfg` naming the other thirteen
457 // drafts: that list had to be edited in every draft module whenever a draft
458 // was added, and a copy that omitted one left this match non-exhaustive.
459 #[allow(unreachable_patterns)]
460 _ => {}
461 }
462 return Ok(header);
463 }
464 Err(CodecError::UnexpectedEnd) => {
465 if !self.fill().await? {
466 return Err(ConnectionError::UnexpectedEnd);
467 }
468 }
469 Err(e) => return Err(ConnectionError::Codec(e)),
470 }
471 }
472 }
473
474 /// Read a fetch response header.
475 pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
476 self.ensure(1).await?;
477 loop {
478 let mut cursor = &self.buf[..];
479 match AnyFetchHeader::decode_stream(DraftVersion::Draft12, &mut cursor) {
480 Ok(header) => {
481 let consumed = self.buf.len() - cursor.remaining();
482 self.buf.advance(consumed);
483 return Ok(header);
484 }
485 Err(CodecError::UnexpectedEnd) => {
486 if !self.fill().await? {
487 return Err(ConnectionError::UnexpectedEnd);
488 }
489 }
490 Err(e) => return Err(ConnectionError::Codec(e)),
491 }
492 }
493 }
494
495 /// Read the next draft-12 subgroup object (header + payload).
496 ///
497 /// Whether an object carries an extension block is a property of the
498 /// stream's Type, not of the object, so this reads it from the header
499 /// [`FramedRecvStream::read_subgroup_header`] recorded rather than assuming
500 /// either answer. Assuming "no extensions" is not a conservative default: on
501 /// a stream whose Type announces them the Extension Headers Length is read
502 /// as the Object Payload Length, and every object on the stream comes back
503 /// wrong instead of being refused.
504 ///
505 /// It is also what puts the rule binding extension headers to Object Status
506 /// within reach on a subgroup stream. A reader that never reads the block
507 /// cannot notice that a non-existent Object carried one.
508 pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
509 loop {
510 let mut cursor = &self.buf[..];
511 match ObjectHeader::decode_with_extensions(self.subgroup_has_extensions, &mut cursor) {
512 Ok(header) => {
513 let header_consumed = self.buf.len() - cursor.remaining();
514 let payload_len = header.payload_length.into_inner() as usize;
515 let total = header_consumed + payload_len;
516 if self.buf.len() < total {
517 if !self.fill().await? {
518 return Err(ConnectionError::UnexpectedEnd);
519 }
520 continue;
521 }
522 let payload = self.buf[header_consumed..total].to_vec();
523 self.buf.advance(total);
524 self.note_subgroup_object(&header)?;
525 return Ok(SubgroupObject { header, payload });
526 }
527 Err(CodecError::UnexpectedEnd) => {
528 if !self.fill().await? {
529 return Err(ConnectionError::UnexpectedEnd);
530 }
531 }
532 Err(e) => return Err(ConnectionError::Codec(e)),
533 }
534 }
535 }
536
537 /// Read the next draft-12 fetch object (header + payload).
538 pub async fn read_fetch_object(&mut self) -> Result<FetchObject, ConnectionError> {
539 loop {
540 let mut cursor = &self.buf[..];
541 match FetchObjectHeader::decode(&mut cursor) {
542 Ok(header) => {
543 let header_consumed = self.buf.len() - cursor.remaining();
544 let payload_len = header.payload_length.into_inner() as usize;
545 let total = header_consumed + payload_len;
546 if self.buf.len() < total {
547 if !self.fill().await? {
548 return Err(ConnectionError::UnexpectedEnd);
549 }
550 continue;
551 }
552 let payload = self.buf[header_consumed..total].to_vec();
553 self.buf.advance(total);
554 return Ok(FetchObject { header, payload });
555 }
556 Err(CodecError::UnexpectedEnd) => {
557 if !self.fill().await? {
558 return Err(ConnectionError::UnexpectedEnd);
559 }
560 }
561 Err(e) => return Err(ConnectionError::Codec(e)),
562 }
563 }
564 }
565}
566
567/// A live draft-12 MoQT connection over QUIC or WebTransport.
568pub struct Connection {
569 transport: Transport,
570 endpoint: Endpoint,
571 /// Behind a lock because a control message is written from two kinds of
572 /// place. Most of them are the caller's own request, made through `&mut
573 /// self`. The UNSUBSCRIBE that answers a Malformed Track is not: the
574 /// conditions that make a track malformed are detected on the data plane,
575 /// where this connection is reached through a shared reference. The lock
576 /// also makes one message the unit of writing, so two of them cannot
577 /// interleave on the stream.
578 control_send: Option<tokio::sync::Mutex<FramedSendStream>>,
579 control_recv: Option<FramedRecvStream>,
580 observer: Option<Box<dyn ConnectionObserver>>,
581 /// Setup events buffered during `connect()` and replayed when an
582 /// observer attaches via `set_observer` — without this, an observer
583 /// attached after `connect` returns would never see the handshake.
584 pending_events: Vec<ClientEvent>,
585}
586
587impl Connection {
588 /// Connect to a draft-12 MoQT server as a client.
589 pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
590 // PATH is for native QUIC only, and the transport is known here and
591 // nowhere further in. Refusing before dialling means a session that
592 // the server would close on sight is never opened.
593 setup::validate_client_path_transport(
594 &config.setup_parameters,
595 matches!(config.transport, TransportType::WebTransport { .. }),
596 )
597 .map_err(EndpointError::from)?;
598
599 let transport = match &config.transport {
600 TransportType::Quic => Self::connect_quic(addr, &config).await?,
601 TransportType::WebTransport { url } => {
602 let url = url.clone();
603 Self::connect_webtransport(&url, &config).await?
604 }
605 };
606
607 Self::adopt(transport, config).await
608 }
609
610 /// Run the MoQT setup handshake over a transport somebody else established.
611 ///
612 /// For choosing the draft from what the server selected: dial once through
613 /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
614 /// connection to the module its answer names. [`Self::connect`] cannot do
615 /// this — it derives its single ALPN from the draft it was given.
616 ///
617 /// `config.draft` must match this module. The transport is adopted as
618 /// given; nothing here re-checks the ALPN it was negotiated with.
619 pub async fn adopt(
620 transport: Transport,
621 config: ClientConfig,
622 ) -> Result<Self, ConnectionError> {
623 // PATH is for native QUIC only, and the transport is known here and
624 // nowhere further in. Refusing before dialling means a session that
625 // the server would close on sight is never opened.
626 setup::validate_client_path_transport(
627 &config.setup_parameters,
628 matches!(config.transport, TransportType::WebTransport { .. }),
629 )
630 .map_err(EndpointError::from)?;
631
632 // Open bidirectional control stream
633 let (send, recv) = transport.open_bi().await?;
634 let mut control_send = FramedSendStream::new(send);
635 let mut control_recv = FramedRecvStream::new(recv);
636
637 // Perform setup handshake
638 let mut endpoint = Endpoint::new(Role::Client);
639 endpoint.connect()?;
640 let setup_msg = endpoint
641 .send_client_setup(config.supported_versions(), config.setup_parameters.clone())?;
642 let any_setup = AnyControlMessage::Draft12(setup_msg);
643 let raw_setup = control_send.write_control(&any_setup).await?;
644
645 let (server_setup, raw_server_setup) = control_recv.read_control(true).await?;
646 match &server_setup {
647 AnyControlMessage::Draft12(ControlMessage::ServerSetup(ref ss)) => {
648 endpoint.receive_server_setup(ss)?;
649 }
650 _ => {
651 return Err(ConnectionError::Endpoint(EndpointError::NotActive));
652 }
653 }
654
655 let mut pending_events = Vec::with_capacity(3);
656 pending_events.push(ClientEvent::ControlMessage {
657 direction: Direction::Send,
658 message: any_setup,
659 raw: Some(raw_setup),
660 });
661 pending_events.push(ClientEvent::ControlMessage {
662 direction: Direction::Receive,
663 message: server_setup,
664 raw: raw_server_setup,
665 });
666 if let Some(v) = endpoint.negotiated_version() {
667 pending_events.push(ClientEvent::SetupComplete { negotiated_version: v.into_inner() });
668 }
669
670 Ok(Self {
671 transport,
672 endpoint,
673 control_send: Some(tokio::sync::Mutex::new(control_send)),
674 control_recv: Some(control_recv),
675 observer: None,
676 pending_events,
677 })
678 }
679
680 /// Establish a raw QUIC connection.
681 ///
682 /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
683 /// TLS and endpoint setup.
684 async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
685 let (transport, _negotiated) = crate::transport::dial_quic(
686 addr,
687 &crate::transport::QuicDialOptions {
688 skip_cert_verification: config.skip_cert_verification,
689 ca_certs: config.ca_certs.clone(),
690 alpn: config.alpn(),
691 },
692 )
693 .await?;
694 Ok(transport)
695 }
696
697 /// Establish a WebTransport connection.
698 #[cfg(feature = "webtransport")]
699 async fn connect_webtransport(
700 url: &str,
701 config: &ClientConfig,
702 ) -> Result<Transport, ConnectionError> {
703 use crate::transport::webtransport::WebTransportTransport;
704
705 let wt_config = if config.skip_cert_verification {
706 wtransport::ClientConfig::builder()
707 .with_bind_default()
708 .with_no_cert_validation()
709 .build()
710 } else {
711 wtransport::ClientConfig::builder().with_bind_default().with_native_certs().build()
712 };
713
714 let endpoint = wtransport::Endpoint::client(wt_config)
715 .map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
716
717 let connection = endpoint
718 .connect(url)
719 .await
720 .map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
721
722 Ok(Transport::WebTransport(WebTransportTransport::new(connection)))
723 }
724
725 /// Stub for when the webtransport feature is not enabled.
726 #[cfg(not(feature = "webtransport"))]
727 async fn connect_webtransport(
728 _url: &str,
729 _config: &ClientConfig,
730 ) -> Result<Transport, ConnectionError> {
731 Err(ConnectionError::Transport(TransportError::Connect(
732 "webtransport feature not enabled".into(),
733 )))
734 }
735
736 // ── Observer ───────────────────────────────────────────────
737
738 /// Attach an observer. Buffered handshake events from `connect()` are
739 /// flushed in arrival order before this returns.
740 pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
741 self.observer = Some(observer);
742 for event in self.pending_events.drain(..) {
743 if let Some(ref obs) = self.observer {
744 obs.on_event_owned(event);
745 }
746 }
747 }
748
749 /// Remove the observer.
750 pub fn clear_observer(&mut self) {
751 self.observer = None;
752 }
753
754 /// Emit an event to the observer, if one is attached.
755 fn emit(&self, event: ClientEvent) {
756 if let Some(ref obs) = self.observer {
757 obs.on_event_owned(event);
758 }
759 }
760
761 // ── Control message I/O ─────────────────────────────────
762
763 /// Send a control message on the control stream.
764 pub async fn send_control(&self, msg: &ControlMessage) -> Result<(), ConnectionError> {
765 let any = AnyControlMessage::Draft12(msg.clone());
766 let mut send =
767 self.control_send.as_ref().ok_or(ConnectionError::NoControlStream)?.lock().await;
768 let raw = send.write_control(&any).await?;
769 drop(send);
770 self.emit(ClientEvent::ControlMessage {
771 direction: Direction::Send,
772 message: any,
773 raw: Some(raw),
774 });
775 Ok(())
776 }
777
778 /// Read the next control message from the control stream.
779 pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
780 let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
781 let capture_raw = self.observer.is_some();
782 let (any, raw) = match recv.read_control(capture_raw).await {
783 Ok(v) => v,
784 Err(e) => return Err(self.close_for_codec(e)),
785 };
786 if capture_raw {
787 self.emit(ClientEvent::ControlMessage {
788 direction: Direction::Receive,
789 message: any.clone(),
790 raw,
791 });
792 }
793 match any {
794 AnyControlMessage::Draft12(msg) => Ok(msg),
795 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 12 the
796 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
797 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
798 // naming the other thirteen drafts: that list had to be edited in every draft module
799 // whenever a draft was added, and a copy that omitted one left this match
800 // non-exhaustive.
801 #[allow(unreachable_patterns)]
802 _ => Err(ConnectionError::Codec(CodecError::UnknownMessageType(0))),
803 }
804 }
805
806 /// Read and dispatch the next incoming control message through the endpoint
807 /// state machine. Returns the decoded message for inspection.
808 pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
809 let msg = self.recv_control().await?;
810 self.endpoint.receive_message(msg.clone()).map_err(|e| self.close_if_session_fatal(e))?;
811
812 if let ControlMessage::GoAway(ref ga) = msg {
813 self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
814 }
815
816 Ok(msg)
817 }
818
819 // ── Subscribe flow ──────────────────────────────────────
820
821 /// Send a SUBSCRIBE and return the allocated request ID.
822 ///
823 /// Draft-12: the subscriber no longer chooses a track alias. The alias is
824 /// returned by the publisher in SUBSCRIBE_OK and can be retrieved later
825 /// from the endpoint via `endpoint().track_alias_for(request_id)`.
826 pub async fn subscribe(
827 &mut self,
828 track_namespace: TrackNamespace,
829 track_name: Vec<u8>,
830 subscriber_priority: u8,
831 group_order: GroupOrder,
832 filter_type: VarInt,
833 parameters: Vec<KeyValuePair>,
834 ) -> Result<VarInt, ConnectionError> {
835 let (req_id, msg) = self.endpoint.subscribe(
836 track_namespace,
837 track_name,
838 subscriber_priority,
839 group_order,
840 filter_type,
841 parameters,
842 )?;
843 self.send_control(&msg).await?;
844 Ok(req_id)
845 }
846
847 /// Send a SUBSCRIBE for a range of the track and return the allocated ID.
848 ///
849 /// The Filter Type comes from the arguments, so the message cannot name a
850 /// filter whose fields it does not carry.
851 #[allow(clippy::too_many_arguments)]
852 pub async fn subscribe_range(
853 &mut self,
854 track_namespace: TrackNamespace,
855 track_name: Vec<u8>,
856 subscriber_priority: u8,
857 group_order: GroupOrder,
858 start_location: Location,
859 end_group: Option<VarInt>,
860 parameters: Vec<KeyValuePair>,
861 ) -> Result<VarInt, ConnectionError> {
862 let (req_id, msg) = self.endpoint.subscribe_range(
863 track_namespace,
864 track_name,
865 subscriber_priority,
866 group_order,
867 start_location,
868 end_group,
869 parameters,
870 )?;
871 self.send_control(&msg).await?;
872 Ok(req_id)
873 }
874
875 /// Send an UNSUBSCRIBE for the given request ID.
876 pub async fn unsubscribe(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
877 let msg = self.endpoint.unsubscribe(request_id)?;
878 self.send_control(&msg).await
879 }
880
881 /// Accept a subscription the peer opened, sending SUBSCRIBE_OK and giving
882 /// its track a Track Alias.
883 ///
884 /// The endpoint refuses an alias a live track of its own already holds and
885 /// refuses a second answer to one SUBSCRIBE, so nothing is written on the
886 /// wire when it does either.
887 pub async fn subscribe_ok(
888 &mut self,
889 request_id: VarInt,
890 track_alias: VarInt,
891 expires: VarInt,
892 group_order: GroupOrder,
893 parameters: Vec<KeyValuePair>,
894 ) -> Result<(), ConnectionError> {
895 let msg = self.endpoint.send_subscribe_ok(
896 request_id,
897 track_alias,
898 expires,
899 group_order,
900 parameters,
901 )?;
902 self.send_control(&msg).await
903 }
904
905 /// Reject a subscription the peer opened, sending SUBSCRIBE_ERROR.
906 ///
907 /// The endpoint refuses a second answer to one SUBSCRIBE, so nothing is
908 /// written on the wire when it does.
909 pub async fn subscribe_error(
910 &mut self,
911 request_id: VarInt,
912 error_code: VarInt,
913 reason_phrase: Vec<u8>,
914 ) -> Result<(), ConnectionError> {
915 let msg = self.endpoint.send_subscribe_error(request_id, error_code, reason_phrase)?;
916 self.send_control(&msg).await
917 }
918
919 /// End a subscription this endpoint accepted, sending SUBSCRIBE_DONE.
920 pub async fn subscribe_done(
921 &mut self,
922 request_id: VarInt,
923 status_code: VarInt,
924 stream_count: VarInt,
925 reason_phrase: Vec<u8>,
926 ) -> Result<(), ConnectionError> {
927 let msg = self.endpoint.send_subscribe_done(
928 request_id,
929 status_code,
930 stream_count,
931 reason_phrase,
932 )?;
933 self.send_control(&msg).await
934 }
935
936 /// Narrow a subscription this endpoint opened, sending SUBSCRIBE_UPDATE.
937 ///
938 /// No Request ID is spent: on this draft the message's one identifier
939 /// field names the subscription it modifies.
940 #[allow(clippy::too_many_arguments)]
941 pub async fn subscribe_update(
942 &mut self,
943 request_id: VarInt,
944 start_group: VarInt,
945 start_object: VarInt,
946 end_group: VarInt,
947 subscriber_priority: u8,
948 forward: Forward,
949 parameters: Vec<KeyValuePair>,
950 ) -> Result<(), ConnectionError> {
951 let msg = self.endpoint.subscribe_update(
952 request_id,
953 start_group,
954 start_object,
955 end_group,
956 subscriber_priority,
957 forward,
958 parameters,
959 )?;
960 self.send_control(&msg).await
961 }
962
963 /// Offer the peer a subscription to a track this endpoint publishes, and
964 /// return the Request ID the offer was allocated.
965 ///
966 /// Nothing is written on the wire when the endpoint refuses to build the
967 /// offer, which it does when the Track Alias is one another live track of
968 /// this session already holds.
969 #[allow(clippy::too_many_arguments)]
970 pub async fn publish(
971 &mut self,
972 track_namespace: TrackNamespace,
973 track_name: Vec<u8>,
974 track_alias: VarInt,
975 group_order: GroupOrder,
976 largest_location: Option<Location>,
977 forward: Forward,
978 parameters: Vec<KeyValuePair>,
979 ) -> Result<VarInt, ConnectionError> {
980 let (req_id, msg) = self.endpoint.publish(
981 track_namespace,
982 track_name,
983 track_alias,
984 group_order,
985 largest_location,
986 forward,
987 parameters,
988 )?;
989 self.send_control(&msg).await?;
990 Ok(req_id)
991 }
992
993 /// Accept a PUBLISH the peer sent, which establishes the subscription it
994 /// opened.
995 ///
996 /// The endpoint refuses a second answer to one PUBLISH, so nothing is
997 /// written on the wire when it does.
998 #[allow(clippy::too_many_arguments)]
999 pub async fn publish_ok(
1000 &mut self,
1001 request_id: VarInt,
1002 forward: Forward,
1003 subscriber_priority: u8,
1004 group_order: GroupOrder,
1005 filter_type: VarInt,
1006 start_group: Option<VarInt>,
1007 start_object: Option<VarInt>,
1008 end_group: Option<VarInt>,
1009 ) -> Result<(), ConnectionError> {
1010 let msg = self.endpoint.send_publish_ok(
1011 request_id,
1012 forward,
1013 subscriber_priority,
1014 group_order,
1015 filter_type,
1016 start_group,
1017 start_object,
1018 end_group,
1019 )?;
1020 self.send_control(&msg).await
1021 }
1022
1023 /// Reject a PUBLISH the peer sent, which ends the subscription it opened
1024 /// before it was established.
1025 ///
1026 /// The endpoint refuses a second answer to one PUBLISH, so nothing is
1027 /// written on the wire when it does.
1028 pub async fn publish_error(
1029 &mut self,
1030 request_id: VarInt,
1031 error_code: VarInt,
1032 reason_phrase: Vec<u8>,
1033 ) -> Result<(), ConnectionError> {
1034 let msg = self.endpoint.send_publish_error(request_id, error_code, reason_phrase)?;
1035 self.send_control(&msg).await
1036 }
1037
1038 // ── Fetch flow ──────────────────────────────────────────
1039
1040 /// Send a FETCH and return the allocated request ID.
1041 #[allow(clippy::too_many_arguments)]
1042 pub async fn fetch(
1043 &mut self,
1044 track_namespace: TrackNamespace,
1045 track_name: Vec<u8>,
1046 subscriber_priority: u8,
1047 group_order: GroupOrder,
1048 start_group: VarInt,
1049 start_object: VarInt,
1050 end_group: VarInt,
1051 end_object: VarInt,
1052 parameters: Vec<KeyValuePair>,
1053 ) -> Result<VarInt, ConnectionError> {
1054 let (req_id, msg) = self.endpoint.fetch(
1055 track_namespace,
1056 track_name,
1057 subscriber_priority,
1058 group_order,
1059 start_group,
1060 start_object,
1061 end_group,
1062 end_object,
1063 parameters,
1064 )?;
1065 self.send_control(&msg).await?;
1066 Ok(req_id)
1067 }
1068
1069 /// Send a Relative Joining Fetch and return the allocated request ID.
1070 ///
1071 /// `joining_start` counts groups back from the live edge of the
1072 /// subscription it names. Section 8.16.1 computes the range from "the
1073 /// Preceding Group Offset", which is this field under the name it carried
1074 /// two drafts ago; the field list calls it Joining Start and says that for
1075 /// a Relative Joining Fetch "a value of 0 indicates the Fetch starts at
1076 /// the beginning of the Current Group". For the form that names the group
1077 /// outright, see
1078 /// [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1079 pub async fn joining_fetch(
1080 &mut self,
1081 subscriber_priority: u8,
1082 group_order: GroupOrder,
1083 joining_request_id: VarInt,
1084 joining_start: VarInt,
1085 parameters: Vec<KeyValuePair>,
1086 ) -> Result<VarInt, ConnectionError> {
1087 let (req_id, msg) = self.endpoint.joining_fetch(
1088 subscriber_priority,
1089 group_order,
1090 joining_request_id,
1091 joining_start,
1092 parameters,
1093 )?;
1094 self.send_control(&msg).await?;
1095 Ok(req_id)
1096 }
1097
1098 /// Send an Absolute Joining Fetch and return the allocated request ID.
1099 ///
1100 /// `joining_start` is the group to begin at. Section 8.16 has an Absolute
1101 /// Joining Fetch "Identical to a Relative Joining Fetch except that the
1102 /// Start Group is determined by an absolute Group value rather than a
1103 /// relative offset to the subscription", so a subscriber that knows the
1104 /// group it wants can ask for it without first being told a Largest
1105 /// Location to count back from.
1106 ///
1107 /// Two calls rather than one taking a Fetch Type, because these are the
1108 /// only two the joining form admits. The endpoint is named the same way,
1109 /// so no call between an application and the wire has a Fetch Type in it
1110 /// to be given a wrong one.
1111 pub async fn absolute_joining_fetch(
1112 &mut self,
1113 subscriber_priority: u8,
1114 group_order: GroupOrder,
1115 joining_request_id: VarInt,
1116 joining_start: VarInt,
1117 parameters: Vec<KeyValuePair>,
1118 ) -> Result<VarInt, ConnectionError> {
1119 let (req_id, msg) = self.endpoint.absolute_joining_fetch(
1120 subscriber_priority,
1121 group_order,
1122 joining_request_id,
1123 joining_start,
1124 parameters,
1125 )?;
1126 self.send_control(&msg).await?;
1127 Ok(req_id)
1128 }
1129
1130 /// Send a FETCH_CANCEL for the given request ID.
1131 pub async fn fetch_cancel(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1132 let msg = self.endpoint.fetch_cancel(request_id)?;
1133 self.send_control(&msg).await
1134 }
1135
1136 /// Accept a fetch the peer opened, sending FETCH_OK.
1137 ///
1138 /// The endpoint refuses a Joining Fetch naming a subscription this session
1139 /// cannot join and refuses a second answer to one FETCH, so nothing is
1140 /// written on the wire when it does either.
1141 pub async fn fetch_ok(
1142 &mut self,
1143 request_id: VarInt,
1144 group_order: GroupOrder,
1145 end_of_track: u8,
1146 end_location: Location,
1147 parameters: Vec<KeyValuePair>,
1148 ) -> Result<(), ConnectionError> {
1149 let msg = self.endpoint.send_fetch_ok(
1150 request_id,
1151 group_order,
1152 end_of_track,
1153 end_location,
1154 parameters,
1155 )?;
1156 self.send_control(&msg).await
1157 }
1158
1159 /// Refuse a fetch the peer opened, sending FETCH_ERROR.
1160 ///
1161 /// The endpoint refuses a second answer to one FETCH, and refuses a
1162 /// Joining Fetch's refusal under any code but the one the draft names for
1163 /// it, so nothing is written on the wire when it does either.
1164 pub async fn fetch_error(
1165 &mut self,
1166 request_id: VarInt,
1167 error_code: VarInt,
1168 reason_phrase: Vec<u8>,
1169 ) -> Result<(), ConnectionError> {
1170 let msg = self.endpoint.send_fetch_error(request_id, error_code, reason_phrase)?;
1171 self.send_control(&msg).await
1172 }
1173
1174 // ── Namespace flows ─────────────────────────────────────
1175
1176 /// Send a SUBSCRIBE_ANNOUNCES. Returns the allocated request ID.
1177 pub async fn subscribe_announces(
1178 &mut self,
1179 track_namespace_prefix: TrackNamespace,
1180 parameters: Vec<KeyValuePair>,
1181 ) -> Result<VarInt, ConnectionError> {
1182 let (req_id, msg) =
1183 self.endpoint.subscribe_announces(track_namespace_prefix, parameters)?;
1184 self.send_control(&msg).await?;
1185 Ok(req_id)
1186 }
1187
1188 /// Accept a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_OK.
1189 ///
1190 /// The endpoint refuses a second answer to one SUBSCRIBE_ANNOUNCES, so nothing is
1191 /// written on the wire when it does.
1192 pub async fn subscribe_announces_ok(
1193 &mut self,
1194 request_id: VarInt,
1195 ) -> Result<(), ConnectionError> {
1196 let msg = self.endpoint.send_subscribe_announces_ok(request_id)?;
1197 self.send_control(&msg).await
1198 }
1199
1200 /// Refuse a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_ERROR.
1201 ///
1202 /// The other half of the same sentence: one answer, and this is the other
1203 /// one it can be.
1204 pub async fn subscribe_announces_error(
1205 &mut self,
1206 request_id: VarInt,
1207 error_code: VarInt,
1208 reason_phrase: Vec<u8>,
1209 ) -> Result<(), ConnectionError> {
1210 let msg =
1211 self.endpoint.send_subscribe_announces_error(request_id, error_code, reason_phrase)?;
1212 self.send_control(&msg).await
1213 }
1214
1215 /// Send an ANNOUNCE. Returns the allocated request ID.
1216 pub async fn announce(
1217 &mut self,
1218 track_namespace: TrackNamespace,
1219 parameters: Vec<KeyValuePair>,
1220 ) -> Result<VarInt, ConnectionError> {
1221 let (req_id, msg) = self.endpoint.announce(track_namespace, parameters)?;
1222 self.send_control(&msg).await?;
1223 Ok(req_id)
1224 }
1225
1226 /// Send an UNANNOUNCE.
1227 pub async fn unannounce(
1228 &mut self,
1229 track_namespace: TrackNamespace,
1230 ) -> Result<(), ConnectionError> {
1231 let msg = self.endpoint.unannounce(track_namespace)?;
1232 self.send_control(&msg).await
1233 }
1234
1235 /// Accept an announcement the peer made, sending ANNOUNCE_OK.
1236 ///
1237 /// The endpoint refuses a second answer to one ANNOUNCE, so nothing is
1238 /// written on the wire when it does.
1239 pub async fn announce_ok(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
1240 let msg = self.endpoint.send_announce_ok(request_id)?;
1241 self.send_control(&msg).await
1242 }
1243
1244 /// Refuse an announcement the peer made, sending ANNOUNCE_ERROR.
1245 ///
1246 /// The other half of the same sentence: one answer, and this is the other
1247 /// one it can be.
1248 pub async fn announce_error(
1249 &mut self,
1250 request_id: VarInt,
1251 error_code: VarInt,
1252 reason_phrase: Vec<u8>,
1253 ) -> Result<(), ConnectionError> {
1254 let msg = self.endpoint.send_announce_error(request_id, error_code, reason_phrase)?;
1255 self.send_control(&msg).await
1256 }
1257
1258 /// Revoke an acceptance, sending ANNOUNCE_CANCEL.
1259 ///
1260 /// The endpoint refuses one for an announcement it never accepted, so
1261 /// nothing is written on the wire when it does.
1262 pub async fn announce_cancel(
1263 &mut self,
1264 track_namespace: TrackNamespace,
1265 error_code: VarInt,
1266 reason_phrase: Vec<u8>,
1267 ) -> Result<(), ConnectionError> {
1268 let msg = self.endpoint.announce_cancel(track_namespace, error_code, reason_phrase)?;
1269 self.send_control(&msg).await
1270 }
1271 // ── Track Status flow ────────────────────────────────────
1272
1273 /// Send a TRACK_STATUS_REQUEST. Returns the allocated request ID.
1274 pub async fn track_status_request(
1275 &mut self,
1276 track_namespace: TrackNamespace,
1277 track_name: Vec<u8>,
1278 parameters: Vec<KeyValuePair>,
1279 ) -> Result<VarInt, ConnectionError> {
1280 let (req_id, msg) =
1281 self.endpoint.track_status_request(track_namespace, track_name, parameters)?;
1282 self.send_control(&msg).await?;
1283 Ok(req_id)
1284 }
1285
1286 /// Answer a TRACK_STATUS_REQUEST the peer sent, sending TRACK_STATUS.
1287 ///
1288 /// The endpoint refuses a second answer to one request, so nothing is
1289 /// written on the wire when it does.
1290 pub async fn track_status(
1291 &mut self,
1292 request_id: VarInt,
1293 status_code: VarInt,
1294 largest_location: Location,
1295 parameters: Vec<KeyValuePair>,
1296 ) -> Result<(), ConnectionError> {
1297 let msg = self.endpoint.send_track_status(
1298 request_id,
1299 status_code,
1300 largest_location,
1301 parameters,
1302 )?;
1303 self.send_control(&msg).await
1304 }
1305
1306 // ── Malformed Tracks ────────────────────────────────────
1307
1308 /// Answer Section 2.5 for the track `alias` names: withdraw from it, and
1309 /// send the UNSUBSCRIBE that withdrawal is.
1310 ///
1311 /// "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE from
1312 /// the Track and SHOULD deliver an error to the application." The caller
1313 /// delivers the error by returning it; this is the other half, and the
1314 /// caller does not have to remember to ask for it.
1315 ///
1316 /// Nothing here ends the session, and that is the point of it. The
1317 /// conditions in Section 2.5 are answered by giving up one track.
1318 ///
1319 /// # Why a write that fails is not reported
1320 ///
1321 /// The error the caller is about to receive is the one that says what
1322 /// went wrong with the track. A control stream that will not take an
1323 /// UNSUBSCRIBE is a session on its way out for a reason of its own, and
1324 /// replacing the condition's report with a transport error would lose the
1325 /// only account of why the track was withdrawn. The rest of the
1326 /// withdrawal is abandoned, because a stream that refused one message
1327 /// will refuse the next.
1328 async fn withdraw_malformed_track(&self, alias: u64, condition: MalformedTrackCondition) {
1329 for msg in self.endpoint.withdraw_malformed_track(alias, condition) {
1330 if self.send_control(&msg).await.is_err() {
1331 break;
1332 }
1333 }
1334 }
1335
1336 /// Record the framing an arriving object was sent with, and withdraw from
1337 /// the track when it is the second framing that track has been sent.
1338 ///
1339 /// The receiving half of a pair. The two writing paths call the endpoint
1340 /// directly and answer a mixed track by refusing to write it, because
1341 /// Section 2.5's sentence is a subscriber's: an endpoint about to send an
1342 /// object is that object's Original Publisher, and a publisher has no
1343 /// subscription of its own to withdraw.
1344 async fn note_received_framing(
1345 &self,
1346 alias: u64,
1347 seen: ObjectForwardingPreference,
1348 ) -> Result<(), ConnectionError> {
1349 let Err(err) = self.endpoint.note_object_forwarding_preference(alias, seen) else {
1350 return Ok(());
1351 };
1352 self.withdraw_malformed_track(alias, MalformedTrackCondition::MixedForwardingPreference)
1353 .await;
1354 Err(err.into())
1355 }
1356
1357 // ── Data streams ────────────────────────────────────────
1358
1359 /// Open a new unidirectional stream for sending subgroup data.
1360 pub async fn open_subgroup_stream(
1361 &self,
1362 header: &AnySubgroupHeader,
1363 ) -> Result<FramedSendStream, ConnectionError> {
1364 // Before the stream is opened: the Original Publisher is who the rule
1365 // binds, so a header that would mix this track's framing is refused
1366 // here rather than written and answered by the peer.
1367 self.endpoint.note_object_forwarding_preference(
1368 header.track_alias(),
1369 ObjectForwardingPreference::Subgroup,
1370 )?;
1371 let send = self.transport.open_uni().await?;
1372 let mut framed = FramedSendStream::new(send);
1373 let sid = framed.stream_id();
1374 framed.write_subgroup_header(header).await?;
1375 self.emit(ClientEvent::StreamOpened {
1376 direction: Direction::Send,
1377 stream_kind: StreamKind::Subgroup,
1378 stream_id: sid,
1379 });
1380 self.emit(ClientEvent::DataStreamHeader {
1381 stream_id: sid,
1382 direction: Direction::Send,
1383 header: header.clone(),
1384 });
1385 Ok(framed)
1386 }
1387
1388 /// Open a new unidirectional stream for sending a FETCH's objects.
1389 ///
1390 /// The objects answering a FETCH do not go on the request's own stream:
1391 /// they go on a unidirectional stream of their own, which opens with a
1392 /// FETCH_HEADER naming the request they belong to. This writes that header
1393 /// and hands back the stream, the same way
1394 /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
1395 /// subgroup.
1396 ///
1397 /// The caller owns the stream that comes back. Nothing here remembers
1398 /// which request it belongs to, so an endpoint serving several fetches at
1399 /// once keeps its own map from Request ID to stream.
1400 pub async fn open_fetch_stream(
1401 &self,
1402 header: &AnyFetchHeader,
1403 ) -> Result<FramedSendStream, ConnectionError> {
1404 let send = self.transport.open_uni().await?;
1405 let mut framed = FramedSendStream::new(send);
1406 let sid = framed.stream_id();
1407 framed.write_fetch_header(header).await?;
1408 self.emit(ClientEvent::StreamOpened {
1409 direction: Direction::Send,
1410 stream_kind: StreamKind::Fetch,
1411 stream_id: sid,
1412 });
1413 Ok(framed)
1414 }
1415
1416 /// Accept the next unidirectional stream and read its fetch header.
1417 ///
1418 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
1419 /// are separate because the header decides how every object after it is
1420 /// framed, so a caller has to know which it is expecting before the first
1421 /// byte is read.
1422 ///
1423 /// Objects come off the returned stream with
1424 /// [`FramedRecvStream::read_fetch_object`].
1425 pub async fn accept_fetch_stream(
1426 &self,
1427 ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
1428 let recv = self.transport.accept_uni().await?;
1429 let mut framed = FramedRecvStream::new(recv);
1430 let sid = framed.stream_id();
1431 let header = framed.read_fetch_header().await?;
1432 self.emit(ClientEvent::StreamOpened {
1433 direction: Direction::Receive,
1434 stream_kind: StreamKind::Fetch,
1435 stream_id: sid,
1436 });
1437 self.emit(ClientEvent::FetchStreamHeader {
1438 stream_id: sid,
1439 direction: Direction::Receive,
1440 header: header.clone(),
1441 });
1442 // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
1443 // carries an `AnySubgroupHeader` and cannot express one. What
1444 // `accept_subgroup_stream` does beyond this - the forwarding-preference
1445 // note, the object measurement - is about a subgroup and has no
1446 // counterpart on a fetch stream.
1447 Ok((header, framed))
1448 }
1449
1450 /// Accept an incoming unidirectional data stream and read its subgroup
1451 /// header.
1452 pub async fn accept_subgroup_stream(
1453 &self,
1454 ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
1455 let recv = self.transport.accept_uni().await?;
1456 let mut framed = FramedRecvStream::new(recv);
1457 let sid = framed.stream_id();
1458 let header = framed.read_subgroup_header().await?;
1459 self.emit(ClientEvent::StreamOpened {
1460 direction: Direction::Receive,
1461 stream_kind: StreamKind::Subgroup,
1462 stream_id: sid,
1463 });
1464 self.emit(ClientEvent::DataStreamHeader {
1465 stream_id: sid,
1466 direction: Direction::Receive,
1467 header: header.clone(),
1468 });
1469 // Every object on a subgroup stream has the Subgroup preference, so
1470 // the header settles the track's framing before a single object is
1471 // read.
1472 self.note_received_framing(header.track_alias(), ObjectForwardingPreference::Subgroup)
1473 .await?;
1474 // The track is resolved here and not inside the stream: it takes the
1475 // endpoint's alias table, which a stream handle has no way back to.
1476 if let Some(objects) = self.endpoint.track_objects(header.track_alias()) {
1477 framed.measure_objects_against(objects, header.group_id());
1478 }
1479 Ok((header, framed))
1480 }
1481
1482 /// Send an object via datagram.
1483 ///
1484 /// The header goes through `AnyDatagramHeader::encode`, which refuses a
1485 /// header whose Object Status the framing it names cannot carry. Such a
1486 /// header errors here and nothing is sent, rather than going out as an
1487 /// ordinary payload datagram with the status quietly dropped.
1488 pub fn send_datagram(
1489 &self,
1490 header: &AnyDatagramHeader,
1491 payload: &[u8],
1492 ) -> Result<(), ConnectionError> {
1493 // Before anything is encoded, for the reason `open_subgroup_stream`
1494 // gives.
1495 self.endpoint.note_object_forwarding_preference(
1496 header.meta().track_alias,
1497 ObjectForwardingPreference::Datagram,
1498 )?;
1499 let mut buf = Vec::new();
1500 header.encode(&mut buf)?;
1501 buf.extend_from_slice(payload);
1502 self.emit(ClientEvent::DatagramReceived {
1503 direction: Direction::Send,
1504 header: header.clone(),
1505 payload_len: payload.len(),
1506 });
1507 self.transport.send_datagram(bytes::Bytes::from(buf))?;
1508 Ok(())
1509 }
1510
1511 /// Receive a datagram and decode its header.
1512 pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
1513 let data = self.transport.recv_datagram().await?;
1514 let mut cursor = &data[..];
1515 let header = AnyDatagramHeader::decode(DraftVersion::Draft12, &mut cursor)?;
1516 let consumed = data.len() - cursor.len();
1517 let payload = data.slice(consumed..);
1518 self.emit(ClientEvent::DatagramReceived {
1519 direction: Direction::Receive,
1520 header: header.clone(),
1521 payload_len: payload.len(),
1522 });
1523 // A datagram is the other framing, and it settles the track's just as a
1524 // subgroup header does.
1525 self.note_received_framing(header.meta().track_alias, ObjectForwardingPreference::Datagram)
1526 .await?;
1527 // A datagram is a whole object, so the connection can measure it
1528 // without help from the caller.
1529 let meta = header.meta();
1530 if let Err(err) = self.endpoint.note_received_object(
1531 meta.track_alias,
1532 ObjectLocation { group: meta.group_id, object: meta.object_id },
1533 object_role(meta.status),
1534 ) {
1535 // One of the two rules that record answers is a Malformed Track,
1536 // and this path can give the answer itself: a datagram is read
1537 // through the connection, which is what an UNSUBSCRIBE takes. The
1538 // other rule ends the session and is the close table's.
1539 if matches!(err, EndpointError::ObjectPastFinalObject { .. }) {
1540 self.withdraw_malformed_track(
1541 meta.track_alias,
1542 MalformedTrackCondition::ObjectPastFinalObject,
1543 )
1544 .await;
1545 }
1546 return Err(err.into());
1547 }
1548 Ok((header, payload))
1549 }
1550
1551 // ── Accessors ───────────────────────────────────────────
1552
1553 /// Access the underlying endpoint state machine.
1554 pub fn endpoint(&self) -> &Endpoint {
1555 &self.endpoint
1556 }
1557
1558 /// Mutable access to the endpoint state machine.
1559 pub fn endpoint_mut(&mut self) -> &mut Endpoint {
1560 &mut self.endpoint
1561 }
1562
1563 /// Get the negotiated MoQT version.
1564 pub fn negotiated_version(&self) -> Option<VarInt> {
1565 self.endpoint.negotiated_version()
1566 }
1567
1568 /// The code to close the session with when a message could not be decoded
1569 /// because the peer broke a rule draft-12 answers with a close.
1570 ///
1571 /// Every variant listed here comes from a sentence in this draft that names
1572 /// the consequence, and the list is per draft: answering a bound this draft
1573 /// does not state would close a session over traffic a conforming peer may
1574 /// send.
1575 ///
1576 /// - Reason Phrase, maximum 1024 bytes: "If an endpoint receives a length
1577 /// exceeding the maximum, it MUST close the session with a Protocol
1578 /// Violation."
1579 /// - GOAWAY New Session URI, maximum 8,192 bytes, with the same sentence.
1580 /// Drafts 11 through 19 state it; 07 through 10 state no maximum for
1581 /// the field at all.
1582 /// - Key-Value-Pair value, maximum 2^16-1 bytes, with the same sentence.
1583 /// - Track Namespace tuple size: "If an endpoint receives a Track
1584 /// Namespace tuple with an N of 0 or more than 32, it MUST close the
1585 /// session with a Protocol Violation." Note the lower bound - an empty
1586 /// tuple is refused here, where drafts 17 and later permit one.
1587 /// - Full Track Name, maximum 4,096 bytes, "computed as the sum of the
1588 /// lengths of each Track Namespace tuple field and the Track Name
1589 /// length field". This draft bounds the pair and not the namespace
1590 /// alone; draft-16 widened it.
1591 /// - Duplicate parameters, a SHOULD rather than a MUST: "Receivers SHOULD
1592 /// check that there are no unauthorized duplicate parameters and close
1593 /// the session as a 'Protocol Violation' if found." The rule is
1594 /// asymmetric here - "Receivers MUST allow duplicates of unknown
1595 /// parameters", and one known type is granted repeats - and the codec
1596 /// reports only the repeats this draft actually forbids.
1597 /// - Unknown control message type: "An endpoint that receives an unknown
1598 /// message type MUST close the session."
1599 /// - Extension headers on an Object whose status is Object Does Not
1600 /// Exist, Section 9.2.1.2. That one arrives on a data stream or a
1601 /// datagram, so [`Connection::close_for_data_stream`] is what carries
1602 /// it.
1603 ///
1604 /// **Not** the zero-length Track Namespace Field, the delta-encoded
1605 /// parameter type overflow, or the Object ID delta wrap. Those enter the
1606 /// specification at drafts 16 and 18, and this draft states none of them.
1607 ///
1608 /// **Not** the 2^16-1 control message length either. This draft states the
1609 /// limit - "the total length of a control message is limited to 2^16-1
1610 /// bytes" - and states no consequence for exceeding it, so an oversized
1611 /// message is refused by the decoder and stops there.
1612 ///
1613 /// **Not** [`CodecError::UnexpectedEnd`], which reports no rule at all: the
1614 /// reader raises it whenever a message is still arriving, and
1615 /// `read_control` loops on it. Closing over it would end a session on an
1616 /// ordinary short read.
1617 ///
1618 /// **Not** the unknown Message Parameter rule. Drafts 16 through 19 require
1619 /// a close for a Message Parameter whose type the negotiated version does
1620 /// not define. This draft states the opposite and states it about the same
1621 /// parameters: "Receivers MUST allow duplicates of unknown parameters",
1622 /// which presumes an unknown parameter arrives and is carried. Refusing one
1623 /// here would close a session over an extension this draft leaves room for.
1624 ///
1625 /// `None` for everything else, including [`CodecError::InvalidField`]. That
1626 /// variant is shared by a dozen unrelated malformations, only some of which
1627 /// the draft answers with a close, so a session cannot be ended on it
1628 /// without ending sessions the draft does not ask to be ended. Splitting it
1629 /// is the way to bring the rest of those rules under this function;
1630 /// widening the match is not - the extension-header rule above reached this
1631 /// table by being split out of it.
1632 fn codec_session_error_code(
1633 err: &CodecError,
1634 ) -> Option<moqtap_codec::draft12::error_codes::SessionErrorCode> {
1635 use moqtap_codec::draft12::error_codes::SessionErrorCode;
1636 use moqtap_codec::kvp::KvpError;
1637 match err {
1638 // The declared Length disagreeing with the fields, which every
1639 // draft answers with a close. Drafts 07 through 10 name no code for
1640 // it, so it takes the one their other unnamed rules take.
1641 CodecError::ControlMessageLengthMismatch { .. } => {
1642 Some(SessionErrorCode::ProtocolViolation)
1643 }
1644 CodecError::ReasonPhraseTooLong
1645 | CodecError::GoAwayUriTooLong
1646 | CodecError::InvalidNamespaceTupleSize(_)
1647 | CodecError::TrackNameTooLong
1648 | CodecError::DuplicateParameter(_)
1649 | CodecError::UnknownMessageType(_)
1650 | CodecError::ExtensionsOnNonExistentObject(_)
1651 | CodecError::Kvp(KvpError::ValueTooLong(_)) => {
1652 Some(SessionErrorCode::ProtocolViolation)
1653 }
1654 // An unknown data-plane type, Section 9: "An endpoint that
1655 // receives an unknown stream or datagram type MUST close the
1656 // session." One sentence covering two tables, which is why both
1657 // variants sit here.
1658 // A Content Exists field that is neither zero nor one,
1659 // Sections 8.8 and 8.13: "Any other value is a protocol error and
1660 // MUST terminate the session with a Protocol Violation".
1661 CodecError::InvalidContentExists(_) => Some(SessionErrorCode::ProtocolViolation),
1662 // A Forward field that is neither zero nor one. Sections 8.7 and
1663 // 8.10: "Any other value is a protocol error and MUST terminate the
1664 // session with a Protocol Violation". Section 8.13 says "Any value
1665 // other than 0 or 1 is a Protocol Violation". Section 8.14 names the
1666 // two legal values without a consequence and is answered the same
1667 // way, for the reason given on the variant.
1668 CodecError::InvalidForward(_) => Some(SessionErrorCode::ProtocolViolation),
1669 CodecError::UnknownStreamType(_) | CodecError::UnknownDatagramType(_) => {
1670 Some(SessionErrorCode::ProtocolViolation)
1671 }
1672 // A key-value pair whose value is not the serialization its own
1673 // Type defines, Section 1.3.2: "If a receiver understands a Type,
1674 // and the following Value or Length/Value does not match the
1675 // serialization defined by that Type, the receiver MUST terminate the
1676 // session with error code 'Key-Value Formatting Error'."
1677 // Section 8.2.1.1 states the same answer for the one structure this
1678 // draft spells out: "If the Token structure cannot be decoded, the
1679 // receiver MUST close the Session with Key-Value Formatting error."
1680 //
1681 // The one rule in this table that names a code other than Protocol
1682 // Violation.
1683 CodecError::KeyValueFormatting { .. } => {
1684 Some(SessionErrorCode::KeyValueFormattingError)
1685 }
1686 // Everything this draft does not answer, named rather than swept up
1687 // by a wildcard. The arm is exhaustive deliberately: a new
1688 // `CodecError` variant will not compile until it has been placed on
1689 // one side or the other, on this draft, which is the decision a `_`
1690 // arm makes silently and invisibly on all thirteen at once.
1691 //
1692 // Adding one variant to `CodecError` was tried, and produces
1693 // thirteen `E0004`s, one per draft, each naming the variant that has
1694 // nowhere to go. That is the whole mechanism.
1695 //
1696 // The nesting stops at `VarInt`, whose variants report how the bytes
1697 // ran out rather than a rule an endpoint states, so there is nothing
1698 // in it for a draft to answer. `Kvp` is spelled out because it does
1699 // carry one.
1700 // Not `ParameterValueOutOfRange`: no parameter this draft defines
1701 // restricts its value's range. Forwarding is a message field here
1702 // and is answered above.
1703 CodecError::ParameterValueOutOfRange { .. }
1704 | CodecError::UnexpectedEnd
1705 | CodecError::MessageTooLong(_)
1706 | CodecError::VarInt(_)
1707 | CodecError::InvalidField
1708 | CodecError::EmptyNamespaceField
1709 | CodecError::InvalidRange(..)
1710 | CodecError::ParameterLengthMismatch(_)
1711 | CodecError::EndOfTrackObjectId(_)
1712 | CodecError::KeyDeltaOverflow(..)
1713 // Not `TrackPropertyValueOutOfRange`: this draft has neither
1714 // namespace the variant is about. Draft-16 opens an extension header
1715 // registry with value rules of its own, and draft-17 renames it to
1716 // the Track Property registry. Before that, everything with a
1717 // restricted range is either a message field or a Message Parameter.
1718 | CodecError::TrackPropertyValueOutOfRange { .. }
1719 | CodecError::ParametersOutOfOrder(..)
1720 | CodecError::ObjectIdOverflow(..)
1721 | CodecError::InvalidRequiredRequestIdDelta(..)
1722 | CodecError::InvalidTypeValue { .. }
1723 | CodecError::UnknownMessageParameter(_)
1724 // Not `ParameterOutOfScope`: this draft states the scope rule and
1725 // answers it the other way. Section 8.2.1 Version Specific Parameters: "Each
1726 // version-specific parameter definition indicates the message types in which it can
1727 // appear. If it appears in some other type of message, it MUST be
1728 // ignored." The codec carries such a parameter on this draft and never
1729 // raises the variant, so this arm records a rule this draft has and
1730 // does not close over, not one it is missing. Draft-17 is where the
1731 // second sentence becomes a close.
1732 | CodecError::ParameterOutOfScope { .. }
1733 // A Filter Type outside the set this draft assigns. Section 8.7
1734 // states the rule and stops there: "A filter type other than the
1735 // above MUST be treated as error." No code, no close, and no
1736 // sentence elsewhere in the draft that turns an error into one — so
1737 // the message is refused and the session stays open.
1738 // Draft-14 Section 9.7 is where the same sentence gained "MUST be
1739 // close the session with PROTOCOL_VIOLATION", and it is answered
1740 // there.
1741 //
1742 // The assigned set is not the same on every draft either: 07 and 08
1743 // assign 0x1 as Latest Group, 09 and 10 withdraw it, and 11 and
1744 // later reinstate it as Next Group Start. The decoder holds each
1745 // draft to its own list; this arm only decides what a refusal does
1746 // to the session.
1747 //
1748 // The two rules below belong to the parameter form of the filter,
1749 // which arrives at draft-15. This draft carries the Filter Type as a
1750 // field of SUBSCRIBE, so there is no parameter for either to be
1751 // about.
1752 | CodecError::InvalidFilterType(_)
1753 | CodecError::SubscriptionFilterMalformed { .. }
1754 | CodecError::FilterEndGroupOverflow { .. }
1755 // A Fetch Type outside the set this draft assigns. Section 8.16
1756 // states the rule and stops there: "A Fetch Type other than 0x1,
1757 // 0x2 or 0x3 MUST be treated as an error", naming no code and no
1758 // close, exactly as this draft's Filter Type sentence does.
1759 // Draft-14 Section 9.16 is where both gained "MUST be close the
1760 // session with a PROTOCOL_VIOLATION", and it answers them there.
1761 | CodecError::InvalidFetchType(_)
1762 // The object payload rule, Section 9.2.1.1: "Any object with a status
1763 // code other than zero MUST have an empty payload." A MUST on the
1764 // sender with no receiver action named anywhere — the "SHOULD be
1765 // treated as a protocol error" in the same paragraph belongs to the
1766 // sentence before it, which is about a status value this draft does
1767 // not assign — so an object carrying a payload it may not is refused
1768 // and the session stays open.
1769 //
1770 // That was already the answer. The bytes used to arrive as
1771 // `InvalidField`, which is on this side too; naming the rule changes
1772 // nothing a peer can observe and makes the decision legible.
1773 | CodecError::PayloadNotPermitted { .. }
1774 | CodecError::UnsupportedDraft(_)
1775 | CodecError::Kvp(
1776 KvpError::MissingLength | KvpError::UnexpectedEnd | KvpError::VarInt(_),
1777 ) => None,
1778 }
1779 }
1780
1781 /// Close the session on the wire when a decode failure is one draft-12
1782 /// answers with a close, and hand the error back unchanged.
1783 /// Without it every bound the decoder enforces would stop at *this endpoint
1784 /// refused the frame* while the peer, which is the one that broke the rule,
1785 /// saw a session that was still open and went on sending. "MUST close the
1786 /// session" is a statement about the wire.
1787 fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
1788 if let ConnectionError::Codec(inner) = &err {
1789 if let Some(code) = Self::codec_session_error_code(inner) {
1790 // QUIC application error codes are 62-bit; every code in this
1791 // registry is far below `u32::MAX`, and saturating rather than
1792 // truncating means a future code that is not could never be
1793 // reported as a different, assigned one.
1794 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1795 self.close(wire_code, inner.to_string().as_bytes());
1796 }
1797 }
1798 err
1799 }
1800
1801 /// Close the session on the wire when the endpoint says a violation is
1802 /// fatal to it, and hand the error back unchanged.
1803 ///
1804 /// [`EndpointError::session_error_code`] answers `Some` for exactly the
1805 /// errors this draft ends the session over, and the endpoint has already
1806 /// moved its own state machine to Closed by the time this runs. Without
1807 /// this step that move is purely internal: the local endpoint refuses to
1808 /// start anything new while the peer, which is the one that broke the
1809 /// rule, sees a session that is still open and goes on sending. A rule
1810 /// that names a session termination code is a statement about the wire,
1811 /// so it takes a CONNECTION_CLOSE to satisfy it.
1812 ///
1813 /// The reason phrase is the error's own `Display` text, which names the
1814 /// rule rather than repeating the numeric code the close already carries.
1815 ///
1816 /// Errors that answer `None` are recoverable and nothing is sent.
1817 fn close_for(&self, err: &EndpointError) {
1818 if let Some(code) = err.session_error_code() {
1819 // QUIC application error codes are 62-bit; every code in this
1820 // registry is far below `u32::MAX`, and saturating rather than
1821 // truncating means a future code that is not could never be
1822 // reported as a different, assigned one.
1823 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1824 self.close(wire_code, err.to_string().as_bytes());
1825 }
1826 }
1827
1828 /// [`close_for`](Self::close_for), then the error unchanged, for the
1829 /// common case where the endpoint's error is also what the caller returns.
1830 fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
1831 self.close_for(&err);
1832 ConnectionError::Endpoint(err)
1833 }
1834
1835 /// Withdraw from a track a data stream found malformed, reporting whether
1836 /// it did.
1837 ///
1838 /// The Malformed Track twin of [`Connection::close_for_data_stream`], and
1839 /// separate from it for the same reason and one more. The same one: a
1840 /// [`FramedRecvStream`] holds no connection, so the reader that finds the
1841 /// fault is not the object that can send an UNSUBSCRIBE. The one more: the
1842 /// two answers are opposites — that call ends the session, this one gives
1843 /// up a track and leaves it running — and a single entry point would have
1844 /// to decide between them from the error alone, which is exactly the
1845 /// decision a caller reproducing a capture wants to make itself.
1846 ///
1847 /// The datagram path needs none of this. It is read through the connection,
1848 /// so [`Connection::recv_datagram`] answers the condition where it finds
1849 /// it, and this is only for the objects that arrive on a stream the caller
1850 /// holds.
1851 pub async fn withdraw_for_data_stream(&self, err: &ConnectionError) -> bool {
1852 let ConnectionError::Endpoint(EndpointError::ObjectPastFinalObject { alias, .. }) = err
1853 else {
1854 return false;
1855 };
1856 self.withdraw_malformed_track(*alias, MalformedTrackCondition::ObjectPastFinalObject).await;
1857 true
1858 }
1859
1860 /// Close the session over a rule broken on a data stream, reporting whether
1861 /// it did.
1862 ///
1863 /// A data stream cannot close for itself the way `recv_control` does:
1864 /// [`Connection::accept_subgroup_stream`] hands the caller a
1865 /// [`FramedRecvStream`] holding no connection, so the reader that finds the
1866 /// violation is not the object that can act on it. Keeping it a separate
1867 /// call is deliberate as well - a permissive caller, one reproducing a
1868 /// capture, can read a violating stream and report it without tearing the
1869 /// session down.
1870 ///
1871 /// The rule this draft answers here is extension headers on an Object whose
1872 /// status is Object Does Not Exist, which reaches subgroup streams, fetch
1873 /// streams and status datagrams alike. It shares `codec_session_error_code`
1874 /// with the control path, so a rule is answered with one code whichever
1875 /// stream carried it.
1876 ///
1877 /// Not every rule that reaches here is the decoder's. A track whose objects
1878 /// mix forwarding preferences is the endpoint's to notice — it takes the
1879 /// alias table to know which track an object belongs to — and it arrives on
1880 /// exactly these streams. Both kinds are asked for a code the same way, and
1881 /// a rule with no code is declined rather than guessed at.
1882 pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
1883 match err {
1884 ConnectionError::Codec(inner) => {
1885 let Some(code) = Self::codec_session_error_code(inner) else { return false };
1886 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1887 self.close(wire_code, inner.to_string().as_bytes());
1888 true
1889 }
1890 // A rule the endpoint raises rather than the decoder. The two
1891 // reach their codes through different tables and mean the same
1892 // thing here: `Some` is a rule this draft ends the session over.
1893 ConnectionError::Endpoint(inner) => {
1894 let Some(code) = inner.session_error_code() else { return false };
1895 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1896 self.close(wire_code, inner.to_string().as_bytes());
1897 true
1898 }
1899 _ => false,
1900 }
1901 }
1902
1903 /// Close the connection.
1904 pub fn close(&self, code: u32, reason: &[u8]) {
1905 self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
1906 self.transport.close(code, reason);
1907 }
1908}
1909
1910/// Determine the encoded length of a varint from its first byte.
1911fn varint_len(first_byte: u8) -> usize {
1912 1 << (first_byte >> 6)
1913}
1914
1915#[cfg(test)]
1916mod tests {
1917 use super::*;
1918
1919 #[test]
1920 fn client_config_supported_versions_default() {
1921 let config = ClientConfig {
1922 additional_versions: Vec::new(),
1923 transport: TransportType::Quic,
1924 skip_cert_verification: false,
1925 ca_certs: Vec::new(),
1926 setup_parameters: Vec::new(),
1927 };
1928 let versions = config.supported_versions();
1929 assert_eq!(versions.len(), 1);
1930 assert_eq!(versions[0].into_inner(), 0xff000000 + 12);
1931 }
1932
1933 #[test]
1934 fn client_config_alpn_quic() {
1935 let config = ClientConfig {
1936 additional_versions: Vec::new(),
1937 transport: TransportType::Quic,
1938 skip_cert_verification: false,
1939 ca_certs: Vec::new(),
1940 setup_parameters: Vec::new(),
1941 };
1942 assert_eq!(config.alpn(), vec![DraftVersion::Draft12.quic_alpn().to_vec()]);
1943 }
1944
1945 #[test]
1946 fn moqt_alpn_value() {
1947 assert_eq!(MOQT_ALPN, b"moq-00");
1948 }
1949}