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