moqtap_client/draft07/connection.rs
1use bytes::{Buf, Bytes, BytesMut};
2
3use crate::draft07::endpoint::{Endpoint, EndpointError, Role};
4use crate::draft07::event::{ClientEvent, Direction, FetchObject, StreamKind, SubgroupObject};
5use crate::draft07::observer::ConnectionObserver;
6use crate::draft07::session::setup;
7use crate::forwarding_preference::ObjectForwardingPreference;
8use crate::transport::{RecvStream, SendStream, Transport, TransportError};
9use moqtap_codec::dispatch::{
10 AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
11};
12use moqtap_codec::draft07::data_stream::{FetchObjectHeader, ObjectHeader};
13use moqtap_codec::draft07::message::ControlMessage;
14use moqtap_codec::error::CodecError;
15use moqtap_codec::kvp::KeyValuePair;
16use moqtap_codec::types::*;
17use moqtap_codec::varint::VarInt;
18use moqtap_codec::version::DraftVersion;
19
20/// MoQT ALPN identifier (used by raw QUIC transport).
21pub const MOQT_ALPN: &[u8] = b"moq-00";
22
23/// Errors from the draft-07 connection layer.
24#[derive(Debug, thiserror::Error)]
25pub enum ConnectionError {
26 /// Endpoint state machine error.
27 #[error("endpoint error: {0}")]
28 Endpoint(#[from] EndpointError),
29 /// Wire codec error.
30 #[error("codec error: {0}")]
31 Codec(#[from] CodecError),
32 /// Transport-level error.
33 #[error("transport error: {0}")]
34 Transport(#[from] TransportError),
35 /// Variable-length integer decoding error.
36 #[error("varint error: {0}")]
37 VarInt(#[from] moqtap_codec::varint::VarIntError),
38 /// Control stream was not opened.
39 #[error("control stream not open")]
40 NoControlStream,
41 /// Stream ended before a complete message was read.
42 #[error("unexpected end of stream")]
43 UnexpectedEnd,
44 /// Stream was finished by the peer.
45 #[error("stream finished")]
46 StreamFinished,
47 /// Invalid server address string.
48 #[error("invalid server address: {0}")]
49 InvalidAddress(String),
50 /// TLS configuration error.
51 #[error("TLS config error: {0}")]
52 TlsConfig(String),
53 /// Data stream used out of order: an object before its header, or an
54 /// Object ID that does not advance on the last one written.
55 #[error("data stream state error: {0}")]
56 DataStreamState(&'static str),
57}
58
59impl From<crate::transport::DialError> for ConnectionError {
60 /// Preserves the variants this error had when the dial was inlined here,
61 /// so a caller matching on `InvalidAddress` or `TlsConfig` sees no change.
62 fn from(e: crate::transport::DialError) -> Self {
63 match e {
64 crate::transport::DialError::InvalidAddress(s) => ConnectionError::InvalidAddress(s),
65 crate::transport::DialError::TlsConfig(s) => ConnectionError::TlsConfig(s),
66 crate::transport::DialError::Transport(e) => ConnectionError::Transport(e),
67 }
68 }
69}
70
71/// Transport type for the connection.
72#[derive(Debug, Clone)]
73pub enum TransportType {
74 /// Raw QUIC via quinn. The `addr` field should be `host:port`.
75 Quic,
76 /// WebTransport via wtransport. The `url` field is the WebTransport URL.
77 WebTransport {
78 /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
79 url: String,
80 },
81}
82
83/// Configuration for a draft-07 MoQT client connection.
84pub struct ClientConfig {
85 /// Additional draft versions to offer in CLIENT_SETUP (draft-07 is always
86 /// offered first).
87 pub additional_versions: Vec<DraftVersion>,
88 /// The transport type (QUIC or WebTransport).
89 pub transport: TransportType,
90 /// Whether to skip TLS certificate verification (for testing).
91 pub skip_cert_verification: bool,
92 /// Custom CA certificates to trust (DER-encoded).
93 pub ca_certs: Vec<Vec<u8>>,
94 /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
95 pub setup_parameters: Vec<moqtap_codec::kvp::KeyValuePair>,
96}
97
98impl ClientConfig {
99 /// Returns the MoQT version varints for the CLIENT_SETUP message.
100 /// Draft-07 first, then any additional versions.
101 pub fn supported_versions(&self) -> Vec<VarInt> {
102 let mut versions = vec![DraftVersion::Draft07.version_varint()];
103 for v in &self.additional_versions {
104 let varint = v.version_varint();
105 if !versions.contains(&varint) {
106 versions.push(varint);
107 }
108 }
109 versions
110 }
111
112 /// Returns the ALPN protocol identifiers for the transport.
113 pub fn alpn(&self) -> Vec<Vec<u8>> {
114 match &self.transport {
115 TransportType::Quic => vec![DraftVersion::Draft07.quic_alpn().to_vec()],
116 TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
117 }
118 }
119}
120
121/// A framed writer for a send stream. Handles MoQT length-prefixed framing.
122pub struct FramedSendStream {
123 inner: SendStream,
124 /// The last Object ID written on this subgroup stream, once one has been.
125 ///
126 /// `None` before the first object; the outer `Option` is `None` until a
127 /// subgroup header has been written, which is what makes an object sent
128 /// before its header answerable rather than unframed bytes.
129 subgroup_objects: Option<Option<u64>>,
130}
131
132impl FramedSendStream {
133 /// Create a new framed send stream.
134 pub fn new(inner: SendStream) -> Self {
135 Self { inner, subgroup_objects: None }
136 }
137
138 /// Get the transport-level stream ID.
139 pub fn stream_id(&self) -> u64 {
140 self.inner.stream_id()
141 }
142
143 /// Write a control message to the stream with type+length framing.
144 /// Returns the raw bytes that were written (for event capture).
145 pub async fn write_control(
146 &mut self,
147 msg: &AnyControlMessage,
148 ) -> Result<Vec<u8>, ConnectionError> {
149 let mut buf = Vec::new();
150 msg.encode(&mut buf)?;
151 self.inner.write_all(&buf).await?;
152 Ok(buf)
153 }
154
155 /// Write a subgroup stream header. Also opens the Object ID bookkeeping
156 /// [`FramedSendStream::write_subgroup_object`] holds the stream to.
157 ///
158 /// Written through the checked encoder, which on this draft refuses
159 /// nothing: SUBGROUP_HEADER has one shape here, every field goes out every
160 /// time, and no type byte selects between them. The drafts that gained a
161 /// header type table need the refusal, and one call site for all thirteen
162 /// is what keeps this from being the draft where it was forgotten.
163 pub async fn write_subgroup_header(
164 &mut self,
165 header: &AnySubgroupHeader,
166 ) -> Result<(), ConnectionError> {
167 let mut buf = Vec::new();
168 header.encode_stream_checked(&mut buf)?;
169 self.inner.write_all(&buf).await?;
170 self.subgroup_objects = Some(None);
171 Ok(())
172 }
173
174 /// Write a fetch response header.
175 pub async fn write_fetch_header(
176 &mut self,
177 header: &AnyFetchHeader,
178 ) -> Result<(), ConnectionError> {
179 let mut buf = Vec::new();
180 header.encode_stream(&mut buf);
181 self.inner.write_all(&buf).await?;
182 Ok(())
183 }
184
185 /// Append a draft-07 subgroup object (header + payload) to the stream.
186 ///
187 /// Section 7.3.1: "A publisher MUST NOT send an Object on a stream if its
188 /// Object ID is less than a previously sent Object ID within a given group
189 /// in that stream." A subgroup stream carries one group, so the Object IDs
190 /// written here are exactly the ones that sentence compares, and the
191 /// comparison needs the object before - which no per-header check can see.
192 /// The state advances only once the object has been written, so declining to
193 /// write an object leaves the next one measured against the last one kept.
194 ///
195 /// An equal Object ID is refused as well as a smaller one. The draft's own
196 /// sentence forbids only "less than", but an Object ID names an Object
197 /// within a Group: writing one twice on a stream describes the same Object
198 /// with two different payloads, and a reader has no way to choose. The
199 /// dispatch-level writer in the codec draws the line in the same place, and
200 /// two writers that disagreed about it would be worse than either answer.
201 ///
202 /// # Errors
203 ///
204 /// [`ConnectionError::DataStreamState`] if no subgroup header has been
205 /// written yet, or if `object` does not advance past the last one written.
206 pub async fn write_subgroup_object(
207 &mut self,
208 object: &SubgroupObject,
209 ) -> Result<(), ConnectionError> {
210 let previous = self
211 .subgroup_objects
212 .as_mut()
213 .ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
214 let object_id = object.header.object_id.into_inner();
215 if matches!(*previous, Some(prev) if object_id <= prev) {
216 return Err(ConnectionError::DataStreamState(
217 "object id does not advance on the last one written to this stream",
218 ));
219 }
220 // The declared length comes from the payload rather than from the
221 // caller's field: a header that disagrees with the bytes beside it
222 // desynchronises every object after it on the stream, and nothing
223 // downstream can recover.
224 let mut header = object.header.clone();
225 header.payload_length = VarInt::from_usize(object.payload.len());
226 let mut buf = Vec::new();
227 header.encode_checked(&mut buf)?;
228 buf.extend_from_slice(&object.payload);
229 self.inner.write_all(&buf).await?;
230 *previous = Some(object_id);
231 Ok(())
232 }
233
234 /// Append a draft-07 fetch object (header + payload) to the stream.
235 pub async fn write_fetch_object(
236 &mut self,
237 object: &FetchObject,
238 ) -> Result<(), ConnectionError> {
239 // The declared length comes from the payload rather than from the
240 // caller's field: a header that disagrees with the bytes beside it
241 // desynchronises every object after it on the stream, and nothing
242 // downstream can recover.
243 let mut header = object.header.clone();
244 header.payload_length = VarInt::from_usize(object.payload.len());
245 let mut buf = Vec::new();
246 header.encode_checked(&mut buf)?;
247 buf.extend_from_slice(&object.payload);
248 self.inner.write_all(&buf).await?;
249 Ok(())
250 }
251
252 /// Finish the stream (send FIN).
253 pub async fn finish(&mut self) -> Result<(), ConnectionError> {
254 self.inner.finish()?;
255 Ok(())
256 }
257}
258
259/// A framed reader for a recv stream. Handles MoQT varint-length decoding.
260pub struct FramedRecvStream {
261 inner: RecvStream,
262 buf: BytesMut,
263}
264
265impl FramedRecvStream {
266 /// Create a new framed receive stream.
267 pub fn new(inner: RecvStream) -> Self {
268 Self { inner, buf: BytesMut::with_capacity(4096) }
269 }
270
271 /// Get the transport-level stream ID.
272 pub fn stream_id(&self) -> u64 {
273 self.inner.stream_id()
274 }
275
276 /// Read more data from the stream into the internal buffer.
277 async fn fill(&mut self) -> Result<bool, ConnectionError> {
278 let mut tmp = [0u8; 4096];
279 match self.inner.read(&mut tmp).await {
280 Ok(Some(n)) => {
281 self.buf.extend_from_slice(&tmp[..n]);
282 Ok(true)
283 }
284 Ok(None) => Ok(false),
285 Err(e) => Err(ConnectionError::Transport(e)),
286 }
287 }
288
289 /// Ensure at least `n` bytes are available in the buffer.
290 async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
291 while self.buf.len() < n {
292 if !self.fill().await? {
293 return Err(ConnectionError::UnexpectedEnd);
294 }
295 }
296 Ok(())
297 }
298
299 /// Read a control message from the stream.
300 ///
301 /// When `capture_raw` is true, the returned tuple includes a clone of the
302 /// framed wire bytes (for observer emission). When false, the second
303 /// element is `None` and the payload clone is skipped.
304 pub async fn read_control(
305 &mut self,
306 capture_raw: bool,
307 ) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
308 // Read type ID varint
309 self.ensure(1).await?;
310 let type_len = varint_len(self.buf[0]);
311 self.ensure(type_len).await?;
312
313 let mut cursor = &self.buf[..type_len];
314 let _type_id = VarInt::decode(&mut cursor)?;
315
316 // Draft-07 uses varint length framing.
317 self.ensure(type_len + 1).await?;
318 let payload_len_start = type_len;
319 let payload_len_varint_len = varint_len(self.buf[payload_len_start]);
320 self.ensure(type_len + payload_len_varint_len).await?;
321 let mut cursor = &self.buf[payload_len_start..type_len + payload_len_varint_len];
322 let payload_len = VarInt::decode(&mut cursor)?.into_inner() as usize;
323 let len_field_size = payload_len_varint_len;
324
325 // Read full payload
326 let total = type_len + len_field_size + payload_len;
327 self.ensure(total).await?;
328
329 // Capture raw bytes only if requested (observer attached).
330 let raw = capture_raw.then(|| self.buf[..total].to_vec());
331
332 // Now decode the whole message using the draft-07 dispatcher
333 let mut frame = &self.buf[..total];
334 let msg = AnyControlMessage::decode(DraftVersion::Draft07, &mut frame)?;
335 self.buf.advance(total);
336 Ok((msg, raw))
337 }
338
339 /// Read a subgroup stream header.
340 pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
341 self.ensure(1).await?;
342 loop {
343 let mut cursor = &self.buf[..];
344 match AnySubgroupHeader::decode_stream(DraftVersion::Draft07, &mut cursor) {
345 Ok(header) => {
346 let consumed = self.buf.len() - cursor.remaining();
347 self.buf.advance(consumed);
348 return Ok(header);
349 }
350 Err(CodecError::UnexpectedEnd) => {
351 if !self.fill().await? {
352 return Err(ConnectionError::UnexpectedEnd);
353 }
354 }
355 Err(e) => return Err(ConnectionError::Codec(e)),
356 }
357 }
358 }
359
360 /// Read a fetch response header.
361 pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
362 self.ensure(1).await?;
363 loop {
364 let mut cursor = &self.buf[..];
365 match AnyFetchHeader::decode_stream(DraftVersion::Draft07, &mut cursor) {
366 Ok(header) => {
367 let consumed = self.buf.len() - cursor.remaining();
368 self.buf.advance(consumed);
369 return Ok(header);
370 }
371 Err(CodecError::UnexpectedEnd) => {
372 if !self.fill().await? {
373 return Err(ConnectionError::UnexpectedEnd);
374 }
375 }
376 Err(e) => return Err(ConnectionError::Codec(e)),
377 }
378 }
379 }
380
381 /// Read the next draft-07 subgroup object (header + payload). Since
382 /// draft-07 subgroup objects are stateless, this does not require any
383 /// prior header-decoding state.
384 pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
385 loop {
386 let mut cursor = &self.buf[..];
387 match ObjectHeader::decode(&mut cursor) {
388 Ok(header) => {
389 let header_consumed = self.buf.len() - cursor.remaining();
390 let payload_len = header.payload_length.into_inner() as usize;
391 let total = header_consumed + payload_len;
392 if self.buf.len() < total {
393 if !self.fill().await? {
394 return Err(ConnectionError::UnexpectedEnd);
395 }
396 continue;
397 }
398 let payload = self.buf[header_consumed..total].to_vec();
399 self.buf.advance(total);
400 return Ok(SubgroupObject { header, payload });
401 }
402 Err(CodecError::UnexpectedEnd) => {
403 if !self.fill().await? {
404 return Err(ConnectionError::UnexpectedEnd);
405 }
406 }
407 Err(e) => return Err(ConnectionError::Codec(e)),
408 }
409 }
410 }
411
412 /// Read the next draft-07 fetch object (header + payload).
413 pub async fn read_fetch_object(&mut self) -> Result<FetchObject, ConnectionError> {
414 loop {
415 let mut cursor = &self.buf[..];
416 match FetchObjectHeader::decode(&mut cursor) {
417 Ok(header) => {
418 let header_consumed = self.buf.len() - cursor.remaining();
419 let payload_len = header.payload_length.into_inner() as usize;
420 let total = header_consumed + payload_len;
421 if self.buf.len() < total {
422 if !self.fill().await? {
423 return Err(ConnectionError::UnexpectedEnd);
424 }
425 continue;
426 }
427 let payload = self.buf[header_consumed..total].to_vec();
428 self.buf.advance(total);
429 return Ok(FetchObject { header, payload });
430 }
431 Err(CodecError::UnexpectedEnd) => {
432 if !self.fill().await? {
433 return Err(ConnectionError::UnexpectedEnd);
434 }
435 }
436 Err(e) => return Err(ConnectionError::Codec(e)),
437 }
438 }
439 }
440}
441
442/// A live draft-07 MoQT connection over QUIC or WebTransport.
443pub struct Connection {
444 transport: Transport,
445 endpoint: Endpoint,
446 control_send: Option<FramedSendStream>,
447 control_recv: Option<FramedRecvStream>,
448 observer: Option<Box<dyn ConnectionObserver>>,
449 /// Setup events buffered during `connect()` and replayed when an
450 /// observer attaches via `set_observer` — without this, an observer
451 /// attached after `connect` returns would never see the handshake.
452 pending_events: Vec<ClientEvent>,
453}
454
455impl Connection {
456 /// Connect to a draft-07 MoQT server as a client.
457 pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
458 // PATH is for native QUIC only, and the transport is known here and
459 // nowhere further in. Refusing before dialling means a session that
460 // the server would close on sight is never opened.
461 setup::validate_client_path_transport(
462 &config.setup_parameters,
463 matches!(config.transport, TransportType::WebTransport { .. }),
464 )
465 .map_err(EndpointError::from)?;
466
467 let transport = match &config.transport {
468 TransportType::Quic => Self::connect_quic(addr, &config).await?,
469 TransportType::WebTransport { url } => {
470 let url = url.clone();
471 Self::connect_webtransport(&url, &config).await?
472 }
473 };
474
475 Self::adopt(transport, config).await
476 }
477
478 /// Run the MoQT setup handshake over a transport somebody else established.
479 ///
480 /// For choosing the draft from what the server selected: dial once through
481 /// [`crate::transport::dial_quic`] offering every ALPN, then bring the
482 /// connection to the module its answer names. [`Self::connect`] cannot do
483 /// this — it derives its single ALPN from the draft it was given.
484 ///
485 /// `config.draft` must match this module. The transport is adopted as
486 /// given; nothing here re-checks the ALPN it was negotiated with.
487 pub async fn adopt(
488 transport: Transport,
489 config: ClientConfig,
490 ) -> Result<Self, ConnectionError> {
491 // PATH is for native QUIC only, and the transport is known here and
492 // nowhere further in. Refusing before dialling means a session that
493 // the server would close on sight is never opened.
494 setup::validate_client_path_transport(
495 &config.setup_parameters,
496 matches!(config.transport, TransportType::WebTransport { .. }),
497 )
498 .map_err(EndpointError::from)?;
499
500 // Open bidirectional control stream
501 let (send, recv) = transport.open_bi().await?;
502 let mut control_send = FramedSendStream::new(send);
503 let mut control_recv = FramedRecvStream::new(recv);
504
505 // Perform setup handshake
506 let mut endpoint = Endpoint::new(Role::Client);
507 endpoint.connect()?;
508 let setup_msg = endpoint
509 .send_client_setup(config.supported_versions(), config.setup_parameters.clone())?;
510 let any_setup = AnyControlMessage::Draft07(setup_msg);
511 let raw_setup = control_send.write_control(&any_setup).await?;
512
513 let (server_setup, raw_server_setup) = control_recv.read_control(true).await?;
514 match &server_setup {
515 AnyControlMessage::Draft07(ControlMessage::ServerSetup(ref ss)) => {
516 endpoint.receive_server_setup(ss)?;
517 }
518 _ => {
519 return Err(ConnectionError::Endpoint(EndpointError::NotActive));
520 }
521 }
522
523 let mut pending_events = Vec::with_capacity(3);
524 pending_events.push(ClientEvent::ControlMessage {
525 direction: Direction::Send,
526 message: any_setup,
527 raw: Some(raw_setup),
528 });
529 pending_events.push(ClientEvent::ControlMessage {
530 direction: Direction::Receive,
531 message: server_setup,
532 raw: raw_server_setup,
533 });
534 if let Some(v) = endpoint.negotiated_version() {
535 pending_events.push(ClientEvent::SetupComplete { negotiated_version: v.into_inner() });
536 }
537
538 Ok(Self {
539 transport,
540 endpoint,
541 control_send: Some(control_send),
542 control_recv: Some(control_recv),
543 observer: None,
544 pending_events,
545 })
546 }
547
548 /// Establish a raw QUIC connection.
549 ///
550 /// Offers this draft's ALPN alone; [`crate::transport::dial_quic`] holds the
551 /// TLS and endpoint setup.
552 async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
553 let (transport, _negotiated) = crate::transport::dial_quic(
554 addr,
555 &crate::transport::QuicDialOptions {
556 skip_cert_verification: config.skip_cert_verification,
557 ca_certs: config.ca_certs.clone(),
558 alpn: config.alpn(),
559 },
560 )
561 .await?;
562 Ok(transport)
563 }
564
565 /// Establish a WebTransport connection.
566 #[cfg(feature = "webtransport")]
567 async fn connect_webtransport(
568 url: &str,
569 config: &ClientConfig,
570 ) -> Result<Transport, ConnectionError> {
571 use crate::transport::webtransport::WebTransportTransport;
572
573 let wt_config = if config.skip_cert_verification {
574 wtransport::ClientConfig::builder()
575 .with_bind_default()
576 .with_no_cert_validation()
577 .build()
578 } else {
579 wtransport::ClientConfig::builder().with_bind_default().with_native_certs().build()
580 };
581
582 let endpoint = wtransport::Endpoint::client(wt_config)
583 .map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
584
585 let connection = endpoint
586 .connect(url)
587 .await
588 .map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
589
590 Ok(Transport::WebTransport(WebTransportTransport::new(connection)))
591 }
592
593 /// Stub for when the webtransport feature is not enabled.
594 #[cfg(not(feature = "webtransport"))]
595 async fn connect_webtransport(
596 _url: &str,
597 _config: &ClientConfig,
598 ) -> Result<Transport, ConnectionError> {
599 Err(ConnectionError::Transport(TransportError::Connect(
600 "webtransport feature not enabled".into(),
601 )))
602 }
603
604 // ── Observer ───────────────────────────────────────────────
605
606 /// Attach an observer. Buffered handshake events from `connect()` are
607 /// flushed in arrival order before this returns.
608 pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
609 self.observer = Some(observer);
610 for event in self.pending_events.drain(..) {
611 if let Some(ref obs) = self.observer {
612 obs.on_event_owned(event);
613 }
614 }
615 }
616
617 /// Remove the observer.
618 pub fn clear_observer(&mut self) {
619 self.observer = None;
620 }
621
622 /// Emit an event to the observer, if one is attached.
623 fn emit(&self, event: ClientEvent) {
624 if let Some(ref obs) = self.observer {
625 obs.on_event_owned(event);
626 }
627 }
628
629 // ── Control message I/O ─────────────────────────────────
630
631 /// Send a control message on the control stream.
632 pub async fn send_control(&mut self, msg: &ControlMessage) -> Result<(), ConnectionError> {
633 let any = AnyControlMessage::Draft07(msg.clone());
634 let send = self.control_send.as_mut().ok_or(ConnectionError::NoControlStream)?;
635 let raw = send.write_control(&any).await?;
636 self.emit(ClientEvent::ControlMessage {
637 direction: Direction::Send,
638 message: any,
639 raw: Some(raw),
640 });
641 Ok(())
642 }
643
644 /// Read the next control message from the control stream.
645 pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
646 let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
647 let capture_raw = self.observer.is_some();
648 let (any, raw) = match recv.read_control(capture_raw).await {
649 Ok(v) => v,
650 Err(e) => return Err(self.close_for_codec(e)),
651 };
652 if capture_raw {
653 self.emit(ClientEvent::ControlMessage {
654 direction: Direction::Receive,
655 message: any.clone(),
656 raw,
657 });
658 }
659 match any {
660 AnyControlMessage::Draft07(msg) => Ok(msg),
661 // `AnyControlMessage` carries one variant per enabled draft feature. With draft 07 the
662 // only one enabled the arm above is exhaustive and this rejection arm unreachable.
663 // Compiled in every configuration with the lint allowed, rather than gated on a `cfg`
664 // naming the other thirteen drafts: that list had to be edited in every draft module
665 // whenever a draft was added, and a copy that omitted one left this match
666 // non-exhaustive.
667 #[allow(unreachable_patterns)]
668 _ => Err(ConnectionError::Codec(CodecError::UnknownMessageType(0))),
669 }
670 }
671
672 /// Read and dispatch the next incoming control message through the endpoint
673 /// state machine. Returns the decoded message for inspection.
674 pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
675 let msg = self.recv_control().await?;
676 self.endpoint.receive_message(msg.clone()).map_err(|e| self.close_if_session_fatal(e))?;
677
678 if let ControlMessage::GoAway(ref ga) = msg {
679 self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
680 }
681
682 Ok(msg)
683 }
684
685 // ── Subscribe flow ──────────────────────────────────────
686
687 /// Send a SUBSCRIBE and return the allocated subscribe ID.
688 #[allow(clippy::too_many_arguments)]
689 pub async fn subscribe(
690 &mut self,
691 track_alias: VarInt,
692 track_namespace: TrackNamespace,
693 track_name: Vec<u8>,
694 subscriber_priority: u8,
695 group_order: GroupOrder,
696 filter_type: FilterType,
697 ) -> Result<VarInt, ConnectionError> {
698 let (sub_id, msg) = self.endpoint.subscribe(
699 track_alias,
700 track_namespace,
701 track_name,
702 subscriber_priority,
703 group_order,
704 filter_type,
705 )?;
706 self.send_control(&msg).await?;
707 Ok(sub_id)
708 }
709
710 /// Send a SUBSCRIBE for a range of the track and return the allocated ID.
711 ///
712 /// The Filter Type comes from the arguments, so the message cannot name a
713 /// filter whose fields it does not carry.
714 #[allow(clippy::too_many_arguments)]
715 pub async fn subscribe_range(
716 &mut self,
717 track_alias: VarInt,
718 track_namespace: TrackNamespace,
719 track_name: Vec<u8>,
720 subscriber_priority: u8,
721 group_order: GroupOrder,
722 start_location: Location,
723 end_location: Option<Location>,
724 ) -> Result<VarInt, ConnectionError> {
725 let (sub_id, msg) = self.endpoint.subscribe_range(
726 track_alias,
727 track_namespace,
728 track_name,
729 subscriber_priority,
730 group_order,
731 start_location,
732 end_location,
733 )?;
734 self.send_control(&msg).await?;
735 Ok(sub_id)
736 }
737
738 /// Send an UNSUBSCRIBE for the given subscribe ID.
739 pub async fn unsubscribe(&mut self, subscribe_id: VarInt) -> Result<(), ConnectionError> {
740 let msg = self.endpoint.unsubscribe(subscribe_id)?;
741 self.send_control(&msg).await
742 }
743
744 /// Accept a subscription the peer opened, sending SUBSCRIBE_OK.
745 pub async fn subscribe_ok(
746 &mut self,
747 subscribe_id: VarInt,
748 expires: VarInt,
749 group_order: GroupOrder,
750 parameters: Vec<KeyValuePair>,
751 ) -> Result<(), ConnectionError> {
752 let msg =
753 self.endpoint.send_subscribe_ok(subscribe_id, expires, group_order, parameters)?;
754 self.send_control(&msg).await
755 }
756
757 /// Reject a subscription the peer opened, sending SUBSCRIBE_ERROR.
758 ///
759 /// The Track Alias travels back with the refusal: under the 'Retry Track
760 /// Alias' code it is the alias the peer should try again with, and under
761 /// any other code it is ignored.
762 pub async fn subscribe_error(
763 &mut self,
764 subscribe_id: VarInt,
765 error_code: VarInt,
766 reason_phrase: Vec<u8>,
767 track_alias: VarInt,
768 ) -> Result<(), ConnectionError> {
769 let msg = self.endpoint.send_subscribe_error(
770 subscribe_id,
771 error_code,
772 reason_phrase,
773 track_alias,
774 )?;
775 self.send_control(&msg).await
776 }
777
778 /// End a subscription this endpoint accepted, sending SUBSCRIBE_DONE.
779 pub async fn subscribe_done(
780 &mut self,
781 subscribe_id: VarInt,
782 status_code: VarInt,
783 reason_phrase: Vec<u8>,
784 ) -> Result<(), ConnectionError> {
785 let msg = self.endpoint.send_subscribe_done(subscribe_id, status_code, reason_phrase)?;
786 self.send_control(&msg).await
787 }
788
789 // ── Fetch flow ──────────────────────────────────────────
790
791 /// Send a FETCH and return the allocated subscribe ID.
792 #[allow(clippy::too_many_arguments)]
793 pub async fn fetch(
794 &mut self,
795 track_namespace: TrackNamespace,
796 track_name: Vec<u8>,
797 subscriber_priority: u8,
798 group_order: GroupOrder,
799 start_group: VarInt,
800 start_object: VarInt,
801 end_group: VarInt,
802 end_object: VarInt,
803 ) -> Result<VarInt, ConnectionError> {
804 let (sub_id, msg) = self.endpoint.fetch(
805 track_namespace,
806 track_name,
807 subscriber_priority,
808 group_order,
809 start_group,
810 start_object,
811 end_group,
812 end_object,
813 )?;
814 self.send_control(&msg).await?;
815 Ok(sub_id)
816 }
817
818 /// Send a FETCH_CANCEL for the given subscribe ID.
819 pub async fn fetch_cancel(&mut self, subscribe_id: VarInt) -> Result<(), ConnectionError> {
820 let msg = self.endpoint.fetch_cancel(subscribe_id)?;
821 self.send_control(&msg).await
822 }
823
824 /// Accept a fetch the peer opened, sending FETCH_OK.
825 ///
826 /// The endpoint refuses a Joining Fetch naming a subscription this session
827 /// cannot join and refuses a second answer to one FETCH, so nothing is
828 /// written on the wire when it does either.
829 pub async fn fetch_ok(
830 &mut self,
831 subscribe_id: VarInt,
832 group_order: GroupOrder,
833 end_of_track: u8,
834 largest_group_id: Option<VarInt>,
835 largest_object_id: Option<VarInt>,
836 parameters: Vec<KeyValuePair>,
837 ) -> Result<(), ConnectionError> {
838 let msg = self.endpoint.send_fetch_ok(
839 subscribe_id,
840 group_order,
841 end_of_track,
842 largest_group_id,
843 largest_object_id,
844 parameters,
845 )?;
846 self.send_control(&msg).await
847 }
848
849 /// Refuse a fetch the peer opened, sending FETCH_ERROR.
850 ///
851 /// The endpoint refuses a second answer to one FETCH, so nothing is
852 /// written on the wire when it does.
853 pub async fn fetch_error(
854 &mut self,
855 subscribe_id: VarInt,
856 error_code: VarInt,
857 reason_phrase: Vec<u8>,
858 ) -> Result<(), ConnectionError> {
859 let msg = self.endpoint.send_fetch_error(subscribe_id, error_code, reason_phrase)?;
860 self.send_control(&msg).await
861 }
862
863 // ── Namespace flows ─────────────────────────────────────
864
865 /// Send a SUBSCRIBE_ANNOUNCES.
866 pub async fn subscribe_announces(
867 &mut self,
868 track_namespace_prefix: TrackNamespace,
869 ) -> Result<(), ConnectionError> {
870 let msg = self.endpoint.subscribe_announces(track_namespace_prefix)?;
871 self.send_control(&msg).await
872 }
873
874 /// Accept a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_OK.
875 ///
876 /// The endpoint refuses a second answer to one SUBSCRIBE_ANNOUNCES, so nothing is
877 /// written on the wire when it does.
878 pub async fn subscribe_announces_ok(
879 &mut self,
880 track_namespace_prefix: TrackNamespace,
881 ) -> Result<(), ConnectionError> {
882 let msg = self.endpoint.send_subscribe_announces_ok(track_namespace_prefix)?;
883 self.send_control(&msg).await
884 }
885
886 /// Refuse a namespace subscription the peer made, sending SUBSCRIBE_ANNOUNCES_ERROR.
887 ///
888 /// The other half of the same sentence: one answer, and this is the other
889 /// one it can be.
890 pub async fn subscribe_announces_error(
891 &mut self,
892 track_namespace_prefix: TrackNamespace,
893 error_code: VarInt,
894 reason_phrase: Vec<u8>,
895 ) -> Result<(), ConnectionError> {
896 let msg = self.endpoint.send_subscribe_announces_error(
897 track_namespace_prefix,
898 error_code,
899 reason_phrase,
900 )?;
901 self.send_control(&msg).await
902 }
903
904 /// Send an ANNOUNCE.
905 pub async fn announce(
906 &mut self,
907 track_namespace: TrackNamespace,
908 ) -> Result<(), ConnectionError> {
909 let msg = self.endpoint.announce(track_namespace)?;
910 self.send_control(&msg).await
911 }
912
913 /// Send an UNANNOUNCE.
914 pub async fn unannounce(
915 &mut self,
916 track_namespace: TrackNamespace,
917 ) -> Result<(), ConnectionError> {
918 let msg = self.endpoint.unannounce(track_namespace)?;
919 self.send_control(&msg).await
920 }
921
922 /// Accept an announcement the peer made, sending ANNOUNCE_OK.
923 ///
924 /// The endpoint refuses a second answer to one ANNOUNCE, so nothing is
925 /// written on the wire when it does.
926 pub async fn announce_ok(
927 &mut self,
928 track_namespace: TrackNamespace,
929 ) -> Result<(), ConnectionError> {
930 let msg = self.endpoint.send_announce_ok(track_namespace)?;
931 self.send_control(&msg).await
932 }
933
934 /// Refuse an announcement the peer made, sending ANNOUNCE_ERROR.
935 ///
936 /// The other half of the same sentence: one answer, and this is the other
937 /// one it can be.
938 pub async fn announce_error(
939 &mut self,
940 track_namespace: TrackNamespace,
941 error_code: VarInt,
942 reason_phrase: Vec<u8>,
943 ) -> Result<(), ConnectionError> {
944 let msg = self.endpoint.send_announce_error(track_namespace, error_code, reason_phrase)?;
945 self.send_control(&msg).await
946 }
947
948 /// Revoke an acceptance, sending ANNOUNCE_CANCEL.
949 ///
950 /// The endpoint refuses one for an announcement it never accepted, so
951 /// nothing is written on the wire when it does.
952 pub async fn announce_cancel(
953 &mut self,
954 track_namespace: TrackNamespace,
955 error_code: VarInt,
956 reason_phrase: Vec<u8>,
957 ) -> Result<(), ConnectionError> {
958 let msg = self.endpoint.announce_cancel(track_namespace, error_code, reason_phrase)?;
959 self.send_control(&msg).await
960 }
961 // ── Track Status flow ────────────────────────────────────
962
963 /// Send a TRACK_STATUS_REQUEST.
964 pub async fn track_status_request(
965 &mut self,
966 track_namespace: TrackNamespace,
967 track_name: Vec<u8>,
968 ) -> Result<(), ConnectionError> {
969 let msg = self.endpoint.track_status_request(track_namespace, track_name)?;
970 self.send_control(&msg).await
971 }
972
973 /// Answer a TRACK_STATUS_REQUEST the peer sent, sending TRACK_STATUS.
974 ///
975 /// The endpoint refuses a second answer to one request, so nothing is
976 /// written on the wire when it does.
977 pub async fn track_status(
978 &mut self,
979 track_namespace: TrackNamespace,
980 track_name: Vec<u8>,
981 status_code: VarInt,
982 last_group_id: VarInt,
983 last_object_id: VarInt,
984 ) -> Result<(), ConnectionError> {
985 let msg = self.endpoint.send_track_status(
986 track_namespace,
987 track_name,
988 status_code,
989 last_group_id,
990 last_object_id,
991 )?;
992 self.send_control(&msg).await
993 }
994
995 // ── Data streams ────────────────────────────────────────
996
997 /// Open a new unidirectional stream for sending subgroup data.
998 pub async fn open_subgroup_stream(
999 &self,
1000 header: &AnySubgroupHeader,
1001 ) -> Result<FramedSendStream, ConnectionError> {
1002 // Before the stream is opened: the Original Publisher is who the rule
1003 // binds, so a header that would mix this track's framing is refused
1004 // here rather than written and answered by the peer.
1005 self.endpoint.note_object_forwarding_preference(
1006 header.track_alias(),
1007 ObjectForwardingPreference::Subgroup,
1008 )?;
1009 let send = self.transport.open_uni().await?;
1010 let mut framed = FramedSendStream::new(send);
1011 let sid = framed.stream_id();
1012 framed.write_subgroup_header(header).await?;
1013 self.emit(ClientEvent::StreamOpened {
1014 direction: Direction::Send,
1015 stream_kind: StreamKind::Subgroup,
1016 stream_id: sid,
1017 });
1018 self.emit(ClientEvent::DataStreamHeader {
1019 stream_id: sid,
1020 direction: Direction::Send,
1021 header: header.clone(),
1022 });
1023 Ok(framed)
1024 }
1025
1026 /// Open a new unidirectional stream for sending a FETCH's objects.
1027 ///
1028 /// The objects answering a FETCH do not go on the request's own stream:
1029 /// they go on a unidirectional stream of their own, which opens with a
1030 /// FETCH_HEADER naming the request they belong to. This writes that header
1031 /// and hands back the stream, the same way
1032 /// [`open_subgroup_stream`](Self::open_subgroup_stream) does for a
1033 /// subgroup.
1034 ///
1035 /// The caller owns the stream that comes back. Nothing here remembers
1036 /// which request it belongs to, so an endpoint serving several fetches at
1037 /// once keeps its own map from Request ID to stream.
1038 pub async fn open_fetch_stream(
1039 &self,
1040 header: &AnyFetchHeader,
1041 ) -> Result<FramedSendStream, ConnectionError> {
1042 let send = self.transport.open_uni().await?;
1043 let mut framed = FramedSendStream::new(send);
1044 let sid = framed.stream_id();
1045 framed.write_fetch_header(header).await?;
1046 self.emit(ClientEvent::StreamOpened {
1047 direction: Direction::Send,
1048 stream_kind: StreamKind::Fetch,
1049 stream_id: sid,
1050 });
1051 Ok(framed)
1052 }
1053
1054 /// Accept the next unidirectional stream and read its fetch header.
1055 ///
1056 /// [`accept_subgroup_stream`](Self::accept_subgroup_stream)'s twin. The two
1057 /// are separate because the header decides how every object after it is
1058 /// framed, so a caller has to know which it is expecting before the first
1059 /// byte is read.
1060 ///
1061 /// Objects come off the returned stream with
1062 /// [`FramedRecvStream::read_fetch_object`].
1063 pub async fn accept_fetch_stream(
1064 &self,
1065 ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError> {
1066 let recv = self.transport.accept_uni().await?;
1067 let mut framed = FramedRecvStream::new(recv);
1068 let sid = framed.stream_id();
1069 let header = framed.read_fetch_header().await?;
1070 self.emit(ClientEvent::StreamOpened {
1071 direction: Direction::Receive,
1072 stream_kind: StreamKind::Fetch,
1073 stream_id: sid,
1074 });
1075 self.emit(ClientEvent::FetchStreamHeader {
1076 stream_id: sid,
1077 direction: Direction::Receive,
1078 header: header.clone(),
1079 });
1080 // A fetch header goes out as `FetchStreamHeader`; `DataStreamHeader`
1081 // carries an `AnySubgroupHeader` and cannot express one. What
1082 // `accept_subgroup_stream` does beyond this - the forwarding-preference
1083 // note, the object measurement - is about a subgroup and has no
1084 // counterpart on a fetch stream.
1085 Ok((header, framed))
1086 }
1087
1088 /// Accept an incoming unidirectional data stream and read its subgroup
1089 /// header.
1090 pub async fn accept_subgroup_stream(
1091 &self,
1092 ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
1093 let recv = self.transport.accept_uni().await?;
1094 let mut framed = FramedRecvStream::new(recv);
1095 let sid = framed.stream_id();
1096 let header = framed.read_subgroup_header().await?;
1097 self.emit(ClientEvent::StreamOpened {
1098 direction: Direction::Receive,
1099 stream_kind: StreamKind::Subgroup,
1100 stream_id: sid,
1101 });
1102 self.emit(ClientEvent::DataStreamHeader {
1103 stream_id: sid,
1104 direction: Direction::Receive,
1105 header: header.clone(),
1106 });
1107 // Every object on a subgroup stream has the Subgroup preference, so
1108 // the header settles the track's framing before a single object is
1109 // read.
1110 self.endpoint.note_object_forwarding_preference(
1111 header.track_alias(),
1112 ObjectForwardingPreference::Subgroup,
1113 )?;
1114 Ok((header, framed))
1115 }
1116
1117 /// Send an object via datagram.
1118 ///
1119 /// The header goes through `AnyDatagramHeader::encode`, which refuses a
1120 /// header whose Object Status the framing it names cannot carry. Such a
1121 /// header errors here and nothing is sent, rather than going out as an
1122 /// ordinary payload datagram with the status quietly dropped.
1123 pub fn send_datagram(
1124 &self,
1125 header: &AnyDatagramHeader,
1126 payload: &[u8],
1127 ) -> Result<(), ConnectionError> {
1128 // Before anything is encoded, for the reason `open_subgroup_stream`
1129 // gives.
1130 self.endpoint.note_object_forwarding_preference(
1131 header.meta().track_alias,
1132 ObjectForwardingPreference::Datagram,
1133 )?;
1134 let mut buf = Vec::new();
1135 header.encode(&mut buf)?;
1136 buf.extend_from_slice(payload);
1137 self.emit(ClientEvent::DatagramReceived {
1138 direction: Direction::Send,
1139 header: header.clone(),
1140 payload_len: payload.len(),
1141 });
1142 self.transport.send_datagram(bytes::Bytes::from(buf))?;
1143 Ok(())
1144 }
1145
1146 /// Receive a datagram and decode its header.
1147 pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
1148 let data = self.transport.recv_datagram().await?;
1149 let mut cursor = &data[..];
1150 let header = AnyDatagramHeader::decode(DraftVersion::Draft07, &mut cursor)?;
1151 let consumed = data.len() - cursor.len();
1152 let payload = data.slice(consumed..);
1153 self.emit(ClientEvent::DatagramReceived {
1154 direction: Direction::Receive,
1155 header: header.clone(),
1156 payload_len: payload.len(),
1157 });
1158 // A datagram is the other framing, and it settles the track's just as a
1159 // subgroup header does.
1160 self.endpoint.note_object_forwarding_preference(
1161 header.meta().track_alias,
1162 ObjectForwardingPreference::Datagram,
1163 )?;
1164 Ok((header, payload))
1165 }
1166
1167 // ── Accessors ───────────────────────────────────────────
1168
1169 /// Access the underlying endpoint state machine.
1170 pub fn endpoint(&self) -> &Endpoint {
1171 &self.endpoint
1172 }
1173
1174 /// Mutable access to the endpoint state machine.
1175 pub fn endpoint_mut(&mut self) -> &mut Endpoint {
1176 &mut self.endpoint
1177 }
1178
1179 /// Get the negotiated MoQT version.
1180 pub fn negotiated_version(&self) -> Option<VarInt> {
1181 self.endpoint.negotiated_version()
1182 }
1183
1184 /// The code to close the session with when a message could not be decoded
1185 /// because the peer broke a rule draft-07 answers with a close.
1186 ///
1187 /// Every variant listed here comes from a sentence in this draft that names
1188 /// the consequence, and the list is per draft: answering a bound this draft
1189 /// does not state would close a session over traffic a conforming peer may
1190 /// send. This draft's list is the shortest of the thirteen and shares only
1191 /// two entries with draft-08's, which sits next to it.
1192 ///
1193 /// - Duplicate parameters, a SHOULD rather than a MUST: "Receivers SHOULD
1194 /// check that there are no duplicate parameters and close the session as
1195 /// a 'Protocol Violation' if found." Unqualified here, as on drafts 08
1196 /// through 10: there is no carve-out for repeats a message authorizes
1197 /// and none for duplicates of unknown parameters, both of which arrive
1198 /// at draft-11 and make the rule asymmetric there.
1199 /// - Unknown control message type: "An endpoint that receives an unknown
1200 /// message type MUST close the session." The sentence names no code, so
1201 /// Protocol Violation is what carries it, as on every other draft.
1202 /// - A parameter whose value does not match the length its type implies —
1203 /// the one rule here **not** answered with a Protocol Violation: "If a
1204 /// receiver understands a parameter type, and the parameter length
1205 /// implied by that type does not match the Parameter Length field, the
1206 /// receiver MUST terminate the session with error code 'Parameter Length
1207 /// Mismatch'." Drafts 08, 09 and 10 carry the same sentence; drafts 11
1208 /// and later drop it along with the Parameter framing it describes.
1209 ///
1210 /// **Not** the Track Namespace tuple size, and this is where draft-07 parts
1211 /// company with every draft above it. Section 2.3 states the range — "an
1212 /// ordered N-tuple of bytes where N can be between 1 and 32" — and stops
1213 /// there. It is draft-08 Section 2.4.1 that adds the consequence: "If an
1214 /// endpoint receives a Track Namespace tuple with an N of 0 or more than
1215 /// 32, it MUST close the session with a Protocol Violation." A tuple of 40
1216 /// fields is malformed on this draft and the session survives it, so the
1217 /// codec does not raise the variant here and this table would have nothing
1218 /// to answer if it did.
1219 ///
1220 /// **Not** the Reason Phrase maximum, the GOAWAY New Session URI maximum,
1221 /// the Full Track Name maximum or the parameter value maximum. Those enter
1222 /// the specification at draft-11 and this draft states none of them.
1223 ///
1224 /// **Not** the end-of-track Object ID rule either, which drafts 08, 09 and
1225 /// 10 do state: this draft assigns no Object Status 0x5 for it to be about.
1226 ///
1227 /// **Not** [`CodecError::UnexpectedEnd`], which reports no rule at all: the
1228 /// reader raises it whenever a message is still arriving, and
1229 /// `read_control` loops on it. Closing over it would end a session on an
1230 /// ordinary short read.
1231 ///
1232 /// **Not** the key-value pair serialization rule. Drafts 11 and later
1233 /// require a close with KEY_VALUE_FORMATTING_ERROR when a value does not
1234 /// match the serialization its Type defines; this draft has no Key-Value
1235 /// Pair at all. What it states instead is the Parameter Length Mismatch
1236 /// rule above, over the Parameter framing it has in its place.
1237 ///
1238 /// **Not** the unknown Message Parameter rule, which enters at draft-16 and
1239 /// requires a close for a Message Parameter type the negotiated version does
1240 /// not define. This draft states nothing of the kind, and its parameters are
1241 /// not Key-Value-Pairs at all.
1242 ///
1243 /// `None` for everything else, including [`CodecError::InvalidField`]. That
1244 /// variant is shared by a dozen unrelated malformations, only some of which
1245 /// the draft answers with a close, so a session cannot be ended on it
1246 /// without ending sessions the draft does not ask to be ended. Two of this
1247 /// draft's own rules are stuck behind it — an unknown data stream type, and
1248 /// a ContentExists field holding anything but 0 or 1, both of which this
1249 /// draft calls a protocol error. Splitting it is the way to bring them
1250 /// under this function; widening the match is not.
1251 fn codec_session_error_code(
1252 err: &CodecError,
1253 ) -> Option<moqtap_codec::draft07::error_codes::SessionErrorCode> {
1254 use moqtap_codec::draft07::error_codes::SessionErrorCode;
1255 use moqtap_codec::kvp::KvpError;
1256 match err {
1257 // The declared Length disagreeing with the fields, which every
1258 // draft answers with a close. Drafts 07 through 10 name no code for
1259 // it, so it takes the one their other unnamed rules take.
1260 CodecError::ControlMessageLengthMismatch { .. } => {
1261 Some(SessionErrorCode::ProtocolViolation)
1262 }
1263 // A Content Exists field that is neither zero nor one, Sections
1264 // 6.15 and 6.19: "Any other value is a protocol error and MUST
1265 // terminate the session with a Protocol Violation". Drafts 08
1266 // through 14 carry the field and the same sentence; draft-15 is
1267 // where it goes.
1268 CodecError::InvalidContentExists(_) => Some(SessionErrorCode::ProtocolViolation),
1269 CodecError::DuplicateParameter(_) | CodecError::UnknownMessageType(_) => {
1270 Some(SessionErrorCode::ProtocolViolation)
1271 }
1272 CodecError::ParameterLengthMismatch(_) => {
1273 Some(SessionErrorCode::ParameterLengthMismatch)
1274 }
1275 // An unknown data-plane type, Section 7: "An endpoint that
1276 // receives an unknown stream type MUST close the session." Draft-07
1277 // numbers its streams and its datagrams in one table, so the stream
1278 // variant carries both and the datagram variant never arrives here.
1279 CodecError::UnknownStreamType(_) | CodecError::UnknownDatagramType(_) => {
1280 Some(SessionErrorCode::ProtocolViolation)
1281 }
1282 // Everything this draft does not answer, named rather than swept up
1283 // by a wildcard. The arm is exhaustive deliberately: a new
1284 // `CodecError` variant will not compile until it has been placed on
1285 // one side or the other, on this draft, which is the decision a `_`
1286 // arm makes silently and invisibly on all thirteen at once.
1287 //
1288 // Adding one variant to `CodecError` was tried, and produces
1289 // thirteen `E0004`s, one per draft, each naming the variant that has
1290 // nowhere to go. That is the whole mechanism.
1291 //
1292 // The nesting stops at `VarInt`, whose variants report how the bytes
1293 // ran out rather than a rule an endpoint states, so there is nothing
1294 // in it for a draft to answer. `Kvp` is spelled out because it does
1295 // carry one.
1296 // Not `InvalidForward`: draft-07 has no Forward field. Not
1297 // `ParameterValueOutOfRange`: no parameter this draft defines
1298 // restricts its value's range. Both arrive with drafts above.
1299 CodecError::InvalidForward(_)
1300 | CodecError::ParameterValueOutOfRange { .. }
1301 | CodecError::UnexpectedEnd
1302 | CodecError::MessageTooLong(_)
1303 | CodecError::VarInt(_)
1304 | CodecError::InvalidField
1305 | CodecError::InvalidNamespaceTupleSize(_)
1306 | CodecError::EmptyNamespaceField
1307 | CodecError::TrackNameTooLong
1308 | CodecError::InvalidRange(..)
1309 | CodecError::EndOfTrackObjectId(_)
1310 | CodecError::KeyDeltaOverflow(..)
1311 // Not `TrackPropertyValueOutOfRange`: this draft has neither
1312 // namespace the variant is about. Draft-16 opens an extension header
1313 // registry with value rules of its own, and draft-17 renames it to
1314 // the Track Property registry. Before that, everything with a
1315 // restricted range is either a message field or a Message Parameter.
1316 | CodecError::TrackPropertyValueOutOfRange { .. }
1317 | CodecError::ParametersOutOfOrder(..)
1318 | CodecError::ObjectIdOverflow(..)
1319 | CodecError::ExtensionsOnNonExistentObject(_)
1320 | CodecError::InvalidRequiredRequestIdDelta(..)
1321 | CodecError::InvalidTypeValue { .. }
1322 | CodecError::ReasonPhraseTooLong
1323 | CodecError::GoAwayUriTooLong
1324 | CodecError::KeyValueFormatting { .. }
1325 | CodecError::UnknownMessageParameter(_)
1326 // Not `ParameterOutOfScope`: this draft states the scope rule and
1327 // answers it the other way. Section 6.1.1 Version Specific Parameters: "Each
1328 // version-specific parameter definition indicates the message types in which it can
1329 // appear. If it appears in some other type of message, it MUST be
1330 // ignored." The codec carries such a parameter on this draft and never
1331 // raises the variant, so this arm records a rule this draft has and
1332 // does not close over, not one it is missing. Draft-17 is where the
1333 // second sentence becomes a close.
1334 | CodecError::ParameterOutOfScope { .. }
1335 // A Filter Type outside the set this draft assigns. Section 6.4
1336 // states the rule and stops there: "A filter type other than the
1337 // above MUST be treated as error." No code, no close, and no
1338 // sentence elsewhere in the draft that turns an error into one — so
1339 // the message is refused and the session stays open.
1340 // Draft-14 Section 9.7 is where the same sentence gained "MUST be
1341 // close the session with PROTOCOL_VIOLATION", and it is answered
1342 // there.
1343 //
1344 // The assigned set is not the same on every draft either: 07 and 08
1345 // assign 0x1 as Latest Group, 09 and 10 withdraw it, and 11 and
1346 // later reinstate it as Next Group Start. The decoder holds each
1347 // draft to its own list; this arm only decides what a refusal does
1348 // to the session.
1349 //
1350 // The two rules below belong to the parameter form of the filter,
1351 // which arrives at draft-15. This draft carries the Filter Type as a
1352 // field of SUBSCRIBE, so there is no parameter for either to be
1353 // about.
1354 | CodecError::InvalidFilterType(_)
1355 | CodecError::SubscriptionFilterMalformed { .. }
1356 | CodecError::FilterEndGroupOverflow { .. }
1357 // Draft-07's FETCH (Section 6.7) carries no Fetch Type field at all:
1358 // every fetch on this draft is the standalone form, with a start and
1359 // an end and nothing to select between them. Draft-08 introduced the
1360 // field and the rule, and drafts 14 and later answer it with a close.
1361 | CodecError::InvalidFetchType(_)
1362 // The object payload rule, Section 7.1.1.1: "Any object with a status
1363 // code other than zero MUST have an empty payload." A MUST on the
1364 // sender with no receiver action named anywhere — the "SHOULD be
1365 // treated as a protocol error" in the same paragraph belongs to the
1366 // sentence before it, which is about a status value this draft does
1367 // not assign — so an object carrying a payload it may not is refused
1368 // and the session stays open.
1369 //
1370 // That was already the answer. The bytes used to arrive as
1371 // `InvalidField`, which is on this side too; naming the rule changes
1372 // nothing a peer can observe and makes the decision legible.
1373 | CodecError::PayloadNotPermitted { .. }
1374 | CodecError::UnsupportedDraft(_)
1375 | CodecError::Kvp(
1376 KvpError::ValueTooLong(_)
1377 | KvpError::MissingLength
1378 | KvpError::UnexpectedEnd
1379 | KvpError::VarInt(_),
1380 ) => None,
1381 }
1382 }
1383
1384 /// Close the session on the wire when a decode failure is one draft-07
1385 /// answers with a close, and hand the error back unchanged.
1386 /// Without it every bound the decoder enforces would stop at *this endpoint
1387 /// refused the frame* while the peer, which is the one that broke the rule,
1388 /// saw a session that was still open and went on sending. "MUST close the
1389 /// session" is a statement about the wire.
1390 fn close_for_codec(&self, err: ConnectionError) -> ConnectionError {
1391 if let ConnectionError::Codec(inner) = &err {
1392 if let Some(code) = Self::codec_session_error_code(inner) {
1393 // QUIC application error codes are 62-bit; every code in this
1394 // registry is far below `u32::MAX`, and saturating rather than
1395 // truncating means a future code that is not could never be
1396 // reported as a different, assigned one.
1397 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1398 self.close(wire_code, inner.to_string().as_bytes());
1399 }
1400 }
1401 err
1402 }
1403
1404 /// Close the session on the wire when the endpoint says a violation is
1405 /// fatal to it, and hand the error back unchanged.
1406 ///
1407 /// [`EndpointError::session_error_code`] answers `Some` for exactly the
1408 /// errors this draft ends the session over, and the endpoint has already
1409 /// moved its own state machine to Closed by the time this runs. Without
1410 /// this step that move is purely internal: the local endpoint refuses to
1411 /// start anything new while the peer, which is the one that broke the
1412 /// rule, sees a session that is still open and goes on sending. A rule
1413 /// that names a session termination code is a statement about the wire,
1414 /// so it takes a CONNECTION_CLOSE to satisfy it.
1415 ///
1416 /// The reason phrase is the error's own `Display` text, which names the
1417 /// rule rather than repeating the numeric code the close already carries.
1418 ///
1419 /// Errors that answer `None` are recoverable and nothing is sent.
1420 fn close_for(&self, err: &EndpointError) {
1421 if let Some(code) = err.session_error_code() {
1422 // QUIC application error codes are 62-bit; every code in this
1423 // registry is far below `u32::MAX`, and saturating rather than
1424 // truncating means a future code that is not could never be
1425 // reported as a different, assigned one.
1426 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1427 self.close(wire_code, err.to_string().as_bytes());
1428 }
1429 }
1430
1431 /// [`close_for`](Self::close_for), then the error unchanged, for the
1432 /// common case where the endpoint's error is also what the caller returns.
1433 fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError {
1434 self.close_for(&err);
1435 ConnectionError::Endpoint(err)
1436 }
1437
1438 /// Close the session over a rule broken on a data stream, reporting whether
1439 /// it did.
1440 ///
1441 /// A data stream cannot close for itself the way `recv_control` does:
1442 /// [`Connection::accept_subgroup_stream`] hands the caller a
1443 /// [`FramedRecvStream`] holding no connection, so the reader that finds the
1444 /// violation is not the object that can act on it. Keeping it a separate
1445 /// call is deliberate as well — a permissive caller, one reproducing a
1446 /// capture, can read a violating stream and report it without tearing the
1447 /// session down.
1448 ///
1449 /// The rule this draft answers here is the unknown stream type, Section 7,
1450 /// which arrives as the very first varint on a unidirectional stream and
1451 /// nowhere else. Every draft from 07 to 19 states it, in one of two
1452 /// phrasings — this one names streams alone because draft-07 numbers its
1453 /// datagrams in the same table, and drafts 08 through 16,
1454 /// draft-08 Section 8 among them, say "an unknown stream or datagram type"
1455 /// for the two tables they split it into. It shares
1456 /// `codec_session_error_code` with the control path, so a rule is answered
1457 /// with one code whichever stream carried it.
1458 ///
1459 /// Not every rule that reaches here is the decoder's. A track whose objects
1460 /// mix forwarding preferences is the endpoint's to notice — it takes the
1461 /// alias table to know which track an object belongs to — and it arrives on
1462 /// exactly these streams. Both kinds are asked for a code the same way, and
1463 /// a rule with no code is declined rather than guessed at.
1464 pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool {
1465 match err {
1466 ConnectionError::Codec(inner) => {
1467 let Some(code) = Self::codec_session_error_code(inner) else { return false };
1468 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1469 self.close(wire_code, inner.to_string().as_bytes());
1470 true
1471 }
1472 // A rule the endpoint raises rather than the decoder. The two
1473 // reach their codes through different tables and mean the same
1474 // thing here: `Some` is a rule this draft ends the session over.
1475 ConnectionError::Endpoint(inner) => {
1476 let Some(code) = inner.session_error_code() else { return false };
1477 let wire_code = u32::try_from(code.as_u64()).unwrap_or(u32::MAX);
1478 self.close(wire_code, inner.to_string().as_bytes());
1479 true
1480 }
1481 _ => false,
1482 }
1483 }
1484
1485 /// Close the connection.
1486 pub fn close(&self, code: u32, reason: &[u8]) {
1487 self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
1488 self.transport.close(code, reason);
1489 }
1490}
1491
1492/// Determine the encoded length of a varint from its first byte.
1493fn varint_len(first_byte: u8) -> usize {
1494 1 << (first_byte >> 6)
1495}
1496
1497#[cfg(test)]
1498mod tests {
1499 use super::*;
1500
1501 #[test]
1502 fn client_config_supported_versions_default() {
1503 let config = ClientConfig {
1504 additional_versions: Vec::new(),
1505 transport: TransportType::Quic,
1506 skip_cert_verification: false,
1507 ca_certs: Vec::new(),
1508 setup_parameters: Vec::new(),
1509 };
1510 let versions = config.supported_versions();
1511 assert_eq!(versions.len(), 1);
1512 assert_eq!(versions[0].into_inner(), 0xff000000 + 7);
1513 }
1514
1515 #[test]
1516 fn client_config_alpn_quic() {
1517 let config = ClientConfig {
1518 additional_versions: Vec::new(),
1519 transport: TransportType::Quic,
1520 skip_cert_verification: false,
1521 ca_certs: Vec::new(),
1522 setup_parameters: Vec::new(),
1523 };
1524 assert_eq!(config.alpn(), vec![DraftVersion::Draft07.quic_alpn().to_vec()]);
1525 }
1526
1527 #[test]
1528 fn moqt_alpn_value() {
1529 assert_eq!(MOQT_ALPN, b"moq-00");
1530 }
1531}