moqtap_client/dispatch.rs
1//! Unified multi-draft entry-point types.
2//!
3//! This module is the facade downstream consumers use to hold a MoQT
4//! connection without caring which draft was negotiated.
5//! It mirrors [`moqtap_codec::dispatch`]: one enum variant per enabled draft,
6//! gated on its feature flag.
7//!
8//! Four types live here:
9//!
10//! - `AnyConnection` — wraps a draft-specific `Connection`.
11//! - `AnyClientEvent` — wraps a draft-specific `ClientEvent`.
12//! - `AnyConnectionObserver` — a trait that receives `AnyClientEvent`s.
13//! Attached to an `AnyConnection` via `AnyConnection::set_observer`,
14//! which installs a per-draft adapter on the inner connection.
15//! - `AnyRequest` — what a request made through `AnyConnection` leaves the
16//! caller holding. Drafts 07-16 put every request on the one control
17//! stream and hand back only a request ID; from draft-17 on each request
18//! owns a bidirectional stream, and the handle owns that stream.
19//!
20//! `AnyConnection` carries only the handful of protocol methods whose
21//! arguments can be reconciled across every draft that has the message
22//! (`subscribe`, `fetch`, `track_status`, `subscribe_namespace`,
23//! `publish_namespace`, `publish`, `subscribe_tracks`). The rest differ too
24//! much in signature — match on the variant to reach them.
25
26use std::sync::Arc;
27
28use moqtap_codec::kvp::KeyValuePair;
29use moqtap_codec::version::DraftVersion;
30
31/// Generates the `AnyConnection` and `AnyClientEvent` enums plus the per-draft
32/// observer adapter, with one variant per enabled draft feature.
33macro_rules! dispatch_all {
34 (
35 $(
36 #[cfg(feature = $feat:literal)]
37 $variant:ident => $module:ident,
38 )+
39 ) => {
40 /// A MoQT client connection of any enabled draft version.
41 ///
42 /// Wraps the draft-specific `Connection` type. Methods common to all
43 /// drafts are forwarded; for draft-specific protocol calls, match on
44 /// the variant.
45 pub enum AnyConnection {
46 $(
47 #[cfg(feature = $feat)]
48 #[doc = concat!("A draft-", $feat, " connection.")]
49 $variant(crate::$module::connection::Connection),
50 )+
51 }
52
53 impl AnyConnection {
54 /// Returns the draft version this connection is using.
55 #[allow(unreachable_code)]
56 pub fn draft(&self) -> DraftVersion {
57 match self {
58 $(
59 #[cfg(feature = $feat)]
60 Self::$variant(_) => DraftVersion::$variant,
61 )+
62 #[allow(unreachable_patterns)]
63 _ => unreachable!("AnyConnection has no enabled variants"),
64 }
65 }
66
67 /// The SETUP message the server answered the handshake with.
68 ///
69 /// `SERVER_SETUP` through draft-16, the server's half of the
70 /// unified `SETUP` from draft-17.
71 /// [`fields`](moqtap_codec::dispatch::AnyControlMessage::fields)
72 /// renders its parameters under the negotiated draft's own names,
73 /// in the order they arrived — which is what makes one relay's
74 /// setup response comparable to another's.
75 #[allow(unreachable_code)]
76 pub fn server_setup(&self) -> &moqtap_codec::dispatch::AnyControlMessage {
77 match self {
78 $(
79 #[cfg(feature = $feat)]
80 Self::$variant(c) => c.server_setup(),
81 )+
82 #[allow(unreachable_patterns)]
83 _ => unreachable!("AnyConnection has no enabled variants"),
84 }
85 }
86
87 /// The framed wire bytes of [`Self::server_setup`], as they
88 /// arrived.
89 pub fn server_setup_raw(&self) -> Option<&[u8]> {
90 match self {
91 $(
92 #[cfg(feature = $feat)]
93 Self::$variant(c) => c.server_setup_raw(),
94 )+
95 #[allow(unreachable_patterns)]
96 _ => None,
97 }
98 }
99
100 /// Attach an observer. The observer is adapted into the
101 /// draft-specific observer trait and installed on the inner
102 /// connection; events are forwarded as [`AnyClientEvent`].
103 ///
104 /// Replaces any previously attached observer.
105 #[allow(unused_variables)]
106 pub fn set_observer(&mut self, observer: Arc<dyn AnyConnectionObserver>) {
107 match self {
108 $(
109 #[cfg(feature = $feat)]
110 Self::$variant(c) => {
111 c.set_observer(Box::new($variant::Adapter(observer)));
112 }
113 )+
114 #[allow(unreachable_patterns)]
115 _ => {}
116 }
117 }
118
119 /// Remove any attached observer.
120 pub fn clear_observer(&mut self) {
121 match self {
122 $(
123 #[cfg(feature = $feat)]
124 Self::$variant(c) => c.clear_observer(),
125 )+
126 #[allow(unreachable_patterns)]
127 _ => {}
128 }
129 }
130
131 /// How the QUIC connection was closed, once it has been. See
132 /// [`TransportClose`](crate::transport::TransportClose).
133 pub fn transport_close(&self) -> Option<crate::transport::TransportClose> {
134 match self {
135 $(
136 #[cfg(feature = $feat)]
137 Self::$variant(c) => c.transport_close(),
138 )+
139 #[allow(unreachable_patterns)]
140 _ => None,
141 }
142 }
143
144 /// Close the connection with the given application error code
145 /// and reason.
146 #[allow(unused_variables)]
147 pub fn close(&self, code: u32, reason: &[u8]) {
148 match self {
149 $(
150 #[cfg(feature = $feat)]
151 Self::$variant(c) => c.close(code, reason),
152 )+
153 #[allow(unreachable_patterns)]
154 _ => {}
155 }
156 }
157 }
158
159 /// An event from a MoQT connection of any enabled draft version.
160 ///
161 /// Event shapes differ across drafts (e.g. draft-17's
162 /// `SubgroupObjectReceived` carries header types, while earlier
163 /// drafts carry decoded objects). Match on the variant to inspect
164 /// the draft-specific event.
165 #[non_exhaustive]
166 #[derive(Debug, Clone)]
167 pub enum AnyClientEvent {
168 $(
169 #[cfg(feature = $feat)]
170 #[doc = concat!("A draft-", $feat, " event.")]
171 $variant(crate::$module::event::ClientEvent),
172 )+
173 }
174
175 impl AnyClientEvent {
176 /// Returns the draft version this event belongs to.
177 #[allow(unreachable_code)]
178 pub fn draft(&self) -> DraftVersion {
179 match self {
180 $(
181 #[cfg(feature = $feat)]
182 Self::$variant(_) => DraftVersion::$variant,
183 )+
184 #[allow(unreachable_patterns)]
185 _ => unreachable!("AnyClientEvent has no enabled variants"),
186 }
187 }
188
189 /// This event as a [`ControlFrame`], or `None` if it is not a
190 /// control message.
191 ///
192 /// The wildcard arm is over the *other* event variants — a stream
193 /// opening, an object arriving — and not over drafts, so it stays
194 /// reachable in every build and says nothing about which drafts are
195 /// enabled.
196 ///
197 /// `stream_id` is deliberately not carried. Drafts 07 through 15
198 /// have no such field on this event, request streams arriving with
199 /// draft-16, so one arm cannot read it from every draft — and a
200 /// second accessor split across two draft lists is a cost to pay
201 /// when something needs the correlation, not before.
202 pub fn control_frame(&self) -> Option<ControlFrame<'_>> {
203 match self {
204 $(
205 #[cfg(feature = $feat)]
206 Self::$variant(crate::$module::event::ClientEvent::ControlMessage {
207 direction,
208 message,
209 raw,
210 ..
211 }) => Some(ControlFrame {
212 draft: DraftVersion::$variant,
213 outbound: matches!(
214 direction,
215 crate::$module::event::Direction::Send
216 ),
217 message,
218 raw: raw.as_deref(),
219 }),
220 )+
221 _ => None,
222 }
223 }
224
225 /// This event as a [`RefusedFrame`], or `None` if it is not a
226 /// refused control frame.
227 ///
228 /// A frame the connection refused: the decoder refused it, or it
229 /// decoded and broke a rule this endpoint holds the peer to. The
230 /// call that read it returns its error after the event.
231 pub fn refused_frame(&self) -> Option<RefusedFrame<'_>> {
232 match self {
233 $(
234 #[cfg(feature = $feat)]
235 Self::$variant(event) => event.refused().map(|(stream_id, raw)| {
236 RefusedFrame { draft: DraftVersion::$variant, stream_id, raw }
237 }),
238 )+
239 #[allow(unreachable_patterns)]
240 _ => None,
241 }
242 }
243 }
244
245 // The classification, written once and instantiated per draft.
246 //
247 // Per draft rather than generic because the two facts worth keeping are
248 // both the draft's: `ConnectionError` is a distinct type on each, and
249 // `close` comes from `codec_session_error_code`, which is the draft's
250 // own reading of its own text. A blanket impl could reach neither.
251 $(
252 #[cfg(feature = $feat)]
253 impl From<crate::$module::connection::ConnectionError> for AnyConnectionError {
254 fn from(err: crate::$module::connection::ConnectionError) -> Self {
255 use crate::above_codec_rules::{DraftSpecificCause, EndpointFault};
256 use crate::$module::connection::{Connection, ConnectionError};
257 use crate::transport::TransportError;
258
259 let message = err.to_string();
260 // Read before the match below moves `err`. Answers `None`
261 // for every variant that match names, so the two tables
262 // partition this draft's error type between them.
263 let own = Connection::draft_specific_cause(&err);
264 let cause = match err {
265 ConnectionError::Codec(error) => ErrorCause::Codec {
266 close: Connection::codec_session_error_code(&error)
267 .map(|code| code.as_u64()),
268 error,
269 },
270 // A varint that would not decode is a decode failure
271 // like any other, and every draft's `CodecError` has a
272 // variant that says so — so it is reported as the codec
273 // error it is rather than as a class of its own.
274 ConnectionError::VarInt(e) => {
275 let error = moqtap_codec::error::CodecError::VarInt(e);
276 ErrorCause::Codec {
277 close: Connection::codec_session_error_code(&error)
278 .map(|code| code.as_u64()),
279 error,
280 }
281 }
282 // The endpoint's error type holds both findings at
283 // once. `fault` says which end,
284 // `session_error_code` says what the draft requires be
285 // done about it, and the pair is read here rather than
286 // guessed at from either half alone.
287 ConnectionError::Endpoint(e) => match e.fault() {
288 EndpointFault::Peer(rule) => ErrorCause::PeerViolation {
289 rule,
290 close: e.session_error_code().map(|code| code.as_u64()),
291 },
292 EndpointFault::ThisEndpoint | EndpointFault::EitherEnd => {
293 ErrorCause::Endpoint
294 }
295 },
296 ConnectionError::Transport(TransportError::StreamReset(code)) => {
297 ErrorCause::StreamReset(code)
298 }
299 ConnectionError::Transport(TransportError::Stopped(code)) => {
300 ErrorCause::Stopped(code)
301 }
302 ConnectionError::Transport(TransportError::SessionClosed {
303 code, ..
304 }) => ErrorCause::SessionClosed(code),
305 ConnectionError::Transport(TransportError::StreamClosed) => {
306 ErrorCause::StreamEnded
307 }
308 ConnectionError::Transport(_) => ErrorCause::Transport,
309 ConnectionError::UnexpectedEnd | ConnectionError::StreamFinished => {
310 ErrorCause::StreamEnded
311 }
312 // Nothing was written: no control stream to write on, an
313 // address that is not one — or a socket this machine
314 // would not open, which `From<DialError>` folds in here
315 // for exactly this reason — a TLS config this build will
316 // not build, an object asked for before the header it is
317 // framed against. `DataStreamState` is the caller
318 // reaching for a data stream out of order, which is this
319 // side's mistake.
320 ConnectionError::NoControlStream
321 | ConnectionError::InvalidAddress(_)
322 | ConnectionError::TlsConfig(_)
323 | ConnectionError::DataStreamState(_) => ErrorCause::Facade,
324 // Whatever this draft adds of its own, read by the draft
325 // rather than guessed at here.
326 #[allow(unreachable_patterns)]
327 _ => match own {
328 Some(DraftSpecificCause::LocalRefusal) => ErrorCause::Facade,
329 Some(DraftSpecificCause::PeerViolation { rule, close }) => {
330 ErrorCause::PeerViolation { rule, close }
331 }
332 Some(DraftSpecificCause::Codec { error, close }) => {
333 ErrorCause::Codec { error, close }
334 }
335 None => ErrorCause::Unclassified,
336 },
337 };
338 AnyConnectionError { message, cause, transport_close: None }
339 }
340 }
341 )+
342
343 // Per-draft adapter modules. Each holds an `Adapter` struct that
344 // implements the draft's `ConnectionObserver` trait by forwarding to
345 // an `AnyConnectionObserver`.
346 $(
347 #[cfg(feature = $feat)]
348 #[allow(non_snake_case)]
349 mod $variant {
350 use super::{AnyClientEvent, AnyConnectionObserver};
351 use std::sync::Arc;
352
353 pub(super) struct Adapter(pub(super) Arc<dyn AnyConnectionObserver>);
354
355 impl crate::$module::observer::ConnectionObserver for Adapter {
356 fn on_event(&self, event: &crate::$module::event::ClientEvent) {
357 self.0.on_event(&AnyClientEvent::$variant(event.clone()));
358 }
359
360 fn on_event_owned(&self, event: crate::$module::event::ClientEvent) {
361 self.0.on_event(&AnyClientEvent::$variant(event));
362 }
363 }
364 }
365 )+
366 };
367}
368
369dispatch_all! {
370 #[cfg(feature = "draft07")]
371 Draft07 => draft07,
372 #[cfg(feature = "draft08")]
373 Draft08 => draft08,
374 #[cfg(feature = "draft09")]
375 Draft09 => draft09,
376 #[cfg(feature = "draft10")]
377 Draft10 => draft10,
378 #[cfg(feature = "draft11")]
379 Draft11 => draft11,
380 #[cfg(feature = "draft12")]
381 Draft12 => draft12,
382 #[cfg(feature = "draft13")]
383 Draft13 => draft13,
384 #[cfg(feature = "draft14")]
385 Draft14 => draft14,
386 #[cfg(feature = "draft15")]
387 Draft15 => draft15,
388 #[cfg(feature = "draft16")]
389 Draft16 => draft16,
390 #[cfg(feature = "draft17")]
391 Draft17 => draft17,
392 #[cfg(feature = "draft18")]
393 Draft18 => draft18,
394 #[cfg(feature = "draft19")]
395 Draft19 => draft19,
396 #[cfg(feature = "draft20")]
397 Draft20 => draft20,
398 #[cfg(feature = "draft21")]
399 Draft21 => draft21,
400 #[cfg(feature = "draft22")]
401 Draft22 => draft22,
402 #[cfg(feature = "draft23")]
403 Draft23 => draft23,
404}
405
406/// Draft-agnostic transport choice for [`AnyConnection::connect`].
407#[derive(Debug, Clone)]
408pub enum AnyTransportType {
409 /// Raw QUIC via quinn. The `addr` passed to `connect` should be `host:port`.
410 Quic,
411 /// WebTransport via wtransport. The `url` is the WebTransport endpoint.
412 WebTransport {
413 /// The WebTransport endpoint URL (e.g., `https://host:port/path`).
414 url: String,
415 },
416}
417
418/// Draft-agnostic client configuration. The exact per-draft `ClientConfig`
419/// is constructed internally by [`AnyConnection::connect`] based on `draft`.
420///
421/// Fields that aren't meaningful for the selected draft are ignored:
422/// `additional_versions` is not carried by drafts 15–22 (single-version
423/// setup) and drafts 07–14 always offer their own draft first.
424#[derive(Debug, Clone)]
425pub struct AnyClientConfig {
426 /// Primary draft version for the connection.
427 pub draft: DraftVersion,
428 /// Additional draft versions to offer in CLIENT_SETUP.
429 pub additional_versions: Vec<DraftVersion>,
430 /// Transport type (QUIC or WebTransport).
431 pub transport: AnyTransportType,
432 /// Whether to skip TLS certificate verification (for testing).
433 pub skip_cert_verification: bool,
434 /// Custom CA certificates to trust (DER-encoded).
435 pub ca_certs: Vec<Vec<u8>>,
436 /// Setup parameters to include in CLIENT_SETUP (e.g., auth tokens).
437 pub setup_parameters: Vec<KeyValuePair>,
438}
439
440/// Why a call through this facade stopped, kept as a value rather than as
441/// words.
442///
443/// # What flattening cost, and what it bought
444///
445/// The drafts each state their own `ConnectionError`, and this facade
446/// exists so that a caller never has to branch on which. Rendering every draft
447/// through `Display` bought exactly that — at the price of the *variant*, which
448/// is the half a caller most often needs. Three questions could not be asked of
449/// a sentence:
450///
451/// - **Did the peer reset this stream, or finish it?** A subgroup ends when its
452/// stream ends, so every reader's last read fails; whether it failed because
453/// the peer abandoned the stream or because there was nothing more to send is
454/// the difference between two entirely different findings, and both arrived
455/// spelled as prose.
456/// - **Which rule stopped a decode?** A decoder built for the negotiated draft
457/// refusing a frame is sometimes this build failing to keep up and sometimes
458/// this build doing what the draft *requires*. [`Self::Codec`] carries the
459/// error itself and the code the draft answers it with, so the two are
460/// separable without reading the message.
461/// - **Was it the peer's fault at all?** [`AnyConnectionError::is_local`].
462///
463/// # The ten every draft shares, and the ones only some do
464///
465/// The first ten variants of `ConnectionError` are identical across all
466/// drafts — `Endpoint`, `Codec`, `Transport`, `VarInt`,
467/// `NoControlStream`, `UnexpectedEnd`, `StreamFinished`, `InvalidAddress`,
468/// `TlsConfig`, `DataStreamState` — and those are classified here, once, rather
469/// than once per draft.
470///
471/// Six of the drafts add variants of their own, and those are read by
472/// `Connection::draft_specific_cause` on each draft, beside the doc comment
473/// quoting the sentence it enforces. They divide into this endpoint refusing to
474/// write something ([`Self::Facade`]) and a peer breaking a rule the decoder
475/// could not see ([`Self::PeerViolation`]) — two opposite findings that reached
476/// a caller as prose and read exactly alike. See
477/// [`crate::above_codec_rules`].
478///
479/// # The same split, one layer in
480///
481/// `ConnectionError::Endpoint` wraps one more type per draft — that draft's
482/// `EndpointError`, twenty-six to forty-one variants holding the same two
483/// findings under one name. `EndpointError::fault` divides it the same way and
484/// by the same rule: a variant raised while **reading** what the peer sent is
485/// the peer's, and one raised while **writing**, or refusing to, is this
486/// side's. A handful are raised on both paths and the variant cannot say which;
487/// those stay on this side, where they already were.
488#[derive(Debug, Clone, PartialEq, Eq)]
489pub enum ErrorCause {
490 /// A decoder built for the negotiated draft refused what arrived.
491 ///
492 /// `close` is the session error code **that draft's own text** requires be
493 /// sent for this error, where it names one — which is the whole of what
494 /// separates a relay's defect from this build's shortfall. A decoder
495 /// stopping is not by itself a finding about the peer; a decoder stopping on
496 /// a rule the draft answers with "MUST close the session" is.
497 ///
498 /// `None` covers both a rule the draft states without a close and an error
499 /// the draft says nothing about, and deliberately does not distinguish
500 /// them: neither is grounds to name a relay.
501 Codec {
502 /// The decoder's own error, unflattened.
503 error: moqtap_codec::error::CodecError,
504 /// The code the negotiated draft answers it with, where it names one.
505 close: Option<u64>,
506 },
507 /// A message that did not fit the session's state, and the fault is not the
508 /// peer's.
509 ///
510 /// Two things reach here, and `EndpointError::fault` tells them apart on
511 /// each draft. Most are this endpoint refusing on the way out — a response
512 /// offered for a request the draft says to refuse, an alias it was asked to
513 /// give to a second track, a session that has closed — where nothing
514 /// reached the wire. The rest are variants raised on **both** a receive path
515 /// and a send path, which the variant alone cannot tell apart: the state
516 /// machines, which render as *invalid transition from X on event Y*
517 /// whichever end asked for the transition, and the unknown-request errors.
518 ///
519 /// Those land here because that is the safe direction: a failure that has
520 /// not been told apart is not evidence against a relay. The peer's half
521 /// does not land here at all — it is [`Self::PeerViolation`], which
522 /// [`AnyConnectionError::is_local`] answers false for.
523 ///
524 /// The draft's `EndpointError` is not carried through because it is one of
525 /// one per draft, unrelated, with no shared spine; the message has it.
526 Endpoint,
527 /// The peer abandoned this stream by resetting it, with the application
528 /// error code it named.
529 ///
530 /// Distinct from [`Self::StreamEnded`] and the distinction is the point: a
531 /// reset says the peer stopped on purpose, and several of the drafts' rules
532 /// turn on exactly that. Section 2.2 forbids one Subgroup's Objects on
533 /// different streams "unless one of the streams was reset prematurely" —
534 /// a sentence no caller can apply without being able to see a reset.
535 StreamReset(u64),
536 /// The peer stopped reading this stream (`STOP_SENDING`), with its code.
537 Stopped(u64),
538 /// The peer closed the whole session, with the code it named.
539 ///
540 /// Over MoQT that code is a draft's own session error, which is the
541 /// sharpest thing a refusal says.
542 SessionClosed(u64),
543 /// The stream ended and the peer did not reset it.
544 ///
545 /// Every way a stream can run out short of a reset: a clean FIN with a read
546 /// still wanting bytes, a truncation, a closed stream. They are together
547 /// because most drafts' readers do not tell them apart — their
548 /// `ConnectionError` raises `UnexpectedEnd` for the first two alike — and
549 /// putting a name on a distinction that is not observable would invent it.
550 /// A draft whose reader does tell a truncation apart reports it as a
551 /// [`Self::PeerViolation`].
552 StreamEnded,
553 /// A transport error naming none of the above — a lost connection, a write
554 /// that failed, a datagram that would not send.
555 Transport,
556 /// This facade refused the call itself. Nothing was written and nothing
557 /// reached the wire.
558 ///
559 /// A value that will not fit the field the draft puts it in, a request
560 /// handle from a different draft than the connection, a draft whose feature
561 /// this build was compiled without, an object asked for before the header it
562 /// is framed against, a message handed to the control stream that belongs on
563 /// a request stream of its own. Local by construction, which is why
564 /// [`AnyConnectionError::is_local`] counts it: without a value saying so, a
565 /// facade refusal carries no prefix and reads to a caller exactly like a
566 /// relay hanging up.
567 Facade,
568 /// The peer broke a rule this endpoint enforces **above** its decoder.
569 ///
570 /// The frame read perfectly well and is forbidden anyway, and the fact that
571 /// forbids it is one of two kinds. Some are a comparison inside the frame:
572 /// properties on an Object whose status permits none, a payload after a
573 /// datagram header that permits none, a bidirectional stream opened with a
574 /// message type the draft does not let one open with. The rest are a
575 /// comparison against the session — a second GOAWAY, a Request ID out of
576 /// the peer's own sequence, a Track Alias already naming another track, an
577 /// Object past the one the track ended at.
578 ///
579 /// A decoder can see none of it, so none of it ever reaches [`Self::Codec`]
580 /// and a caller reading only that would find the peer blameless. Without
581 /// this variant the second group arrives as [`Self::Endpoint`], which
582 /// [`AnyConnectionError::is_local`] counts as **this** side's fault, so a
583 /// relay breaking one of these rules is filed against this build's own
584 /// state machine.
585 ///
586 /// `close` has exactly [`Self::Codec`]'s contract — the code **this draft's
587 /// own text** names for the rule, or `None` where it states the rule and
588 /// attaches no consequence. It comes from the draft's own
589 /// `EndpointError::session_error_code` or
590 /// `Connection::codec_session_error_code`, so the rule and its consequence
591 /// are never two readings of one sentence.
592 PeerViolation {
593 /// Which rule, named the same way on every draft that states it.
594 rule: crate::above_codec_rules::AboveCodecRule,
595 /// The code the negotiated draft answers it with, where it names one.
596 close: Option<u64>,
597 },
598 /// A variant neither this facade nor its draft has classified.
599 ///
600 /// Reachable only if the two tables disagree: a variant the match above does
601 /// not name, and that the draft's own `draft_specific_cause` answered `None`
602 /// for. Both are exhaustive today — neither has a wildcard arm — so adding a
603 /// variant to a draft's `ConnectionError` is a compile error in that draft's
604 /// file rather than a silent arrival here.
605 ///
606 /// Kept because the alternative in that arm is a panic, and a facade that
607 /// panics on an error is worse than one that declines to characterise it.
608 /// It answers [`AnyConnectionError::is_local`] false, which is the safe
609 /// direction: an unclassified failure is not evidence about anybody.
610 Unclassified,
611}
612
613/// Error returned by [`AnyConnection::connect`], [`AnyConnection::recv_response`]
614/// and the rest of this facade.
615///
616/// Renders as the draft-specific error it came from, so nothing that read the
617/// message reads anything different. What is new beside it is
618/// [`Self::cause`] — the same failure as a value, so a caller can ask which
619/// kind of failure it was without branching on draft and without parsing the
620/// sentence. See [`ErrorCause`] for why the sentence was not enough.
621#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
622#[error("{message}")]
623pub struct AnyConnectionError {
624 message: String,
625 cause: ErrorCause,
626 transport_close: Option<crate::transport::TransportClose>,
627}
628
629impl AnyConnectionError {
630 /// A refusal by this facade itself: nothing was written, nothing reached
631 /// the wire. [`ErrorCause::Facade`].
632 pub fn facade(message: impl Into<String>) -> Self {
633 Self { message: message.into(), cause: ErrorCause::Facade, transport_close: None }
634 }
635
636 /// The draft-specific error's own words, unedited.
637 pub fn message(&self) -> &str {
638 &self.message
639 }
640
641 /// What stopped the call, as a value.
642 pub fn cause(&self) -> &ErrorCause {
643 &self.cause
644 }
645
646 /// How the QUIC connection was closed, for a handshake that failed over a
647 /// connection that had closed.
648 ///
649 /// Set by [`AnyConnection::adopt`] and the other `adopt` calls, which
650 /// consume the transport and leave the caller nothing else to ask. On a
651 /// live connection, ask [`AnyConnection::transport_close`] instead.
652 pub fn transport_close(&self) -> Option<&crate::transport::TransportClose> {
653 self.transport_close.as_ref()
654 }
655
656 /// Whether the failure was on **this** side rather than the peer's.
657 ///
658 /// True for a decode this build would not complete, a message that did not
659 /// fit the state machine this side drives, and a call this facade refused
660 /// before writing anything. False for every way a peer can end a stream or
661 /// a session — which is what an unimplemented message type or an unwelcome
662 /// parameter looks like from here — and false for
663 /// [`ErrorCause::PeerViolation`], which is the peer's doing by definition.
664 ///
665 /// [`ErrorCause::Endpoint`] answers **true**, and it is a narrow claim: the
666 /// peer's half of the endpoint's error type leaves through
667 /// [`ErrorCause::PeerViolation`] before it gets here. What reaches this
668 /// answer is this endpoint refusing on the way out, plus the variants
669 /// raised on both paths that no table can tell apart. Those answer true
670 /// because that is the safe direction, not because they have been shown to
671 /// be this side's.
672 ///
673 /// [`ErrorCause::Unclassified`] answers false, which is the safe direction:
674 /// a failure that has not been classified is not evidence of anything.
675 pub fn is_local(&self) -> bool {
676 matches!(self.cause, ErrorCause::Codec { .. } | ErrorCause::Endpoint | ErrorCause::Facade)
677 }
678}
679
680/// Where a FETCH's range ends, said once in a way no draft can read two ways.
681///
682/// The drafts do not agree about what a number in an "end object" field means,
683/// and the disagreement is silent: draft-19 Section 10.13 defines `End
684/// Location` as "the last Object, plus 1; or 0 to indicate the entire Group",
685/// while draft-20 Section 5.1.2 and draft-20 Section 10.13 make the
686/// `LOCATION_FILTER` range "inclusive" at both ends and delete both conventions
687/// without a note in the change log. The same `end_object = 10` therefore asks
688/// for objects 0 through 9 on one draft and 0 through 10 on the other, and
689/// nothing on the wire says which was meant.
690///
691/// So this enum, not a number. [`FetchEnd::Object`] is the last Object the
692/// fetch covers and the range **holds** it; [`FetchEnd::EntireGroup`] is
693/// draft-19's `0` spelled out. [`AnyConnection::fetch`] converts to whichever
694/// the negotiated draft writes.
695#[derive(Debug, Clone, Copy, PartialEq, Eq)]
696pub enum FetchEnd {
697 /// Through this Object ID, **inclusive** — it is the last Object the fetch
698 /// covers, and the range holds it.
699 ///
700 /// `Object(0)` is a range ending at Object 0, one object long if it also
701 /// starts there. It is not "the whole group"; that is
702 /// [`FetchEnd::EntireGroup`].
703 Object(u64),
704 /// Through the last Object of the end Group, however many it turns out to
705 /// hold.
706 ///
707 /// Drafts 14-19 write this as `End Location.Object = 0`. Draft-20 writes it
708 /// as a three-field `LOCATION_FILTER`, which Section 5.1.2 defines as
709 /// covering all Objects in the end Group.
710 EntireGroup,
711}
712
713/// The range one [`AnyConnection::fetch`] asks for.
714///
715/// Four numbers on drafts 14 through 19, a `LOCATION_FILTER` parameter on
716/// draft-20, and one meaning here. The end Group is **absolute** on both sides
717/// of that split — draft-20's `EndGroupDelta` is derived from it, not asked for
718/// — and the end Object is [`FetchEnd`], which is the whole point of the type.
719///
720/// # Porting a draft-19 call
721///
722/// A caller that wrote `end_object` as "the last Object plus one" writes
723/// [`FetchEnd::Object`] with the last Object, and one that wrote `0` for a whole
724/// group writes [`FetchEnd::EntireGroup`]. Both send exactly the bytes they sent
725/// before on drafts 14 through 19, and the same request on draft-20.
726#[derive(Debug, Clone, Copy, PartialEq, Eq)]
727pub struct FetchRange {
728 /// The Group the range starts in.
729 pub start_group: u64,
730 /// The Object within `start_group` the range starts at, inclusive.
731 pub start_object: u64,
732 /// The Group the range ends in, absolute. Must not be below
733 /// `start_group`: draft-20 encodes it as an unsigned delta from the start
734 /// and has no way to say "backwards".
735 pub end_group: u64,
736 /// Where the range stops inside `end_group`.
737 pub end: FetchEnd,
738}
739
740impl FetchRange {
741 /// A range ending at `end_object` in `end_group`, **inclusive** — that
742 /// Object is fetched.
743 pub fn through_object(
744 start_group: u64,
745 start_object: u64,
746 end_group: u64,
747 end_object: u64,
748 ) -> Self {
749 Self { start_group, start_object, end_group, end: FetchEnd::Object(end_object) }
750 }
751
752 /// A range covering every Object of `end_group`, however many there are.
753 pub fn through_end_of_group(start_group: u64, start_object: u64, end_group: u64) -> Self {
754 Self { start_group, start_object, end_group, end: FetchEnd::EntireGroup }
755 }
756
757 /// A range holding exactly one Object.
758 ///
759 /// The shape an off-by-one is loudest in: a draft-19 encoder writing
760 /// `end_object = object` rather than `object + 1` asks for nothing at all,
761 /// and one that ports that arithmetic to draft-20 unchanged asks for two.
762 pub fn one_object(group: u64, object: u64) -> Self {
763 Self::through_object(group, object, group, object)
764 }
765
766 /// The `End Location.Object` drafts 14 through 19 carry inline in FETCH.
767 ///
768 /// Their field is "The end Location, plus 1. A Location.Object value of 0
769 /// means the entire group is requested." — draft-19 Section 10.12.1, and
770 /// drafts 14 through 18 in the same words. So [`FetchEnd::Object`] gains
771 /// one here and
772 /// [`FetchEnd::EntireGroup`] is `0`. **This is the only place that `+ 1`
773 /// lives**, which is what keeps it from reaching draft-20.
774 ///
775 /// # Errors
776 ///
777 /// `FetchEnd::Object(u64::MAX)` has no encoding in that field — the plus
778 /// one leaves the number space — and is the one range these drafts cannot
779 /// express that draft-20 can. Refused rather than wrapped to `0`, which
780 /// would silently ask for the whole group.
781 pub fn inline_end_object(&self) -> Result<u64, AnyConnectionError> {
782 match self.end {
783 FetchEnd::EntireGroup => Ok(0),
784 FetchEnd::Object(last) => last.checked_add(1).ok_or_else(|| {
785 AnyConnectionError::facade(format!(
786 "fetch: a range ending at Object {last} cannot be expressed on drafts 14 \
787 through 19, whose End Location.Object is the last Object plus 1"
788 ))
789 }),
790 }
791 }
792
793 /// The draft-20 `LOCATION_FILTER` that carries this range.
794 ///
795 /// Draft-20 Section 10.13 deleted `Start Location` and `End Location` from
796 /// FETCH and moved the range into the parameter, whose ranges Section 5.1.2
797 /// calls inclusive. So [`FetchEnd::Object`] is written **as it stands** —
798 /// nothing here adds one — and [`FetchEnd::EntireGroup`] becomes the
799 /// three-field filter, which Section 5.1.2 defines as covering all Objects
800 /// in the end Group. The end Group travels as `EndGroupDelta`, "delta
801 /// encoded from StartGroup", so it is `end_group - start_group`.
802 ///
803 /// # Errors
804 ///
805 /// [`AnyConnectionError`] when `end_group` is below `start_group`: the
806 /// delta is unsigned and there is no such filter. Drafts 14 through 19 have
807 /// two absolute fields and would put such a range on the wire, where the
808 /// publisher answers it with `INVALID_RANGE`; the difference is where the
809 /// refusal happens, not whether the fetch is legal.
810 /// The name carries the draft because the type does: `LOCATION_FILTER`
811 /// is draft-20's and later's, and each draft's `fill` module declares its
812 /// own `LocationFilter`. An unsuffixed name would be two inherent methods
813 /// of one name the moment a second draft defines the parameter.
814 #[cfg(feature = "draft20")]
815 pub fn location_filter_draft20(
816 &self,
817 ) -> Result<crate::draft20::fill::LocationFilter, AnyConnectionError> {
818 use crate::draft20::fill::LocationFilter;
819
820 let delta = self.end_group.checked_sub(self.start_group).ok_or_else(|| {
821 AnyConnectionError::facade(format!(
822 "fetch: a range from Group {} to Group {} runs backwards, and draft-20 \
823 Section 5.1.2 encodes the end Group as an unsigned delta from the start",
824 self.start_group, self.end_group
825 ))
826 })?;
827 let filter = match self.end {
828 FetchEnd::EntireGroup => {
829 LocationFilter::range(self.start_group, self.start_object, delta)
830 }
831 FetchEnd::Object(last) => {
832 LocationFilter::range_to(self.start_group, self.start_object, delta, last)
833 }
834 };
835 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
836 }
837 /// The draft-21 `LOCATION_FILTER` that carries this range.
838 ///
839 /// Draft-21 Section 9.11 deleted `Start Location` and `End Location` from
840 /// FETCH and moved the range into the parameter, whose ranges Section 3.3.1
841 /// calls inclusive. So [`FetchEnd::Object`] is written **as it stands** —
842 /// nothing here adds one — and [`FetchEnd::EntireGroup`] becomes the
843 /// three-field filter, which Section 9.20.10 defines as covering all Objects
844 /// in the end Group. The end Group travels as `EndGroupDelta`, "delta
845 /// encoded from StartGroup", so it is `end_group - start_group`.
846 ///
847 /// # Errors
848 ///
849 /// [`AnyConnectionError`] when `end_group` is below `start_group`: the
850 /// delta is unsigned and there is no such filter. Drafts 14 through 19 have
851 /// two absolute fields and would put such a range on the wire, where the
852 /// publisher answers it with `INVALID_RANGE`; the difference is where the
853 /// refusal happens, not whether the fetch is legal.
854 /// The name carries the draft because the type does: `LOCATION_FILTER`
855 /// is draft-21's and later's, and each draft's `fill` module declares its
856 /// own `LocationFilter`. An unsuffixed name would be two inherent methods
857 /// of one name the moment a second draft defines the parameter.
858 #[cfg(feature = "draft21")]
859 pub fn location_filter_draft21(
860 &self,
861 ) -> Result<crate::draft21::fill::LocationFilter, AnyConnectionError> {
862 use crate::draft21::fill::LocationFilter;
863
864 let delta = self.end_group.checked_sub(self.start_group).ok_or_else(|| {
865 AnyConnectionError::facade(format!(
866 "fetch: a range from Group {} to Group {} runs backwards, and draft-21 \
867 Section 9.20.10 encodes the end Group as an unsigned delta from the start",
868 self.start_group, self.end_group
869 ))
870 })?;
871 let filter = match self.end {
872 FetchEnd::EntireGroup => {
873 LocationFilter::range(self.start_group, self.start_object, delta)
874 }
875 FetchEnd::Object(last) => {
876 LocationFilter::range_to(self.start_group, self.start_object, delta, last)
877 }
878 };
879 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
880 }
881 /// The draft-22 `LOCATION_FILTER` that carries this range.
882 ///
883 /// Draft-22 Section 9.11 gives FETCH no `Start Location` or `End Location`
884 /// field: the range is the parameter, and draft-22 Section 3.2 has a FETCH
885 /// request Objects "between a Start Location and an End Location,
886 /// inclusive". So [`FetchEnd::Object`] is written **as it stands** —
887 /// Location Filter Type 0x04, Absolute Range — and
888 /// [`FetchEnd::EntireGroup`] is type 0x03, which draft-22 Section 9.20.9
889 /// ends at the "last Object of Group StartGroup + EndGroupDelta". The end
890 /// Group travels as `EndGroupDelta`, "delta encoded from StartGroup", so it
891 /// is `end_group - start_group`.
892 ///
893 /// # Errors
894 ///
895 /// [`AnyConnectionError`] when `end_group` is below `start_group`: the
896 /// delta is unsigned and there is no such filter.
897 /// The name carries the draft because the type does: each draft's `fill`
898 /// module declares its own `LocationFilter`, and draft-22's writes a
899 /// `Location Filter Type` that draft-21's has no field for.
900 #[cfg(feature = "draft22")]
901 pub fn location_filter_draft22(
902 &self,
903 ) -> Result<crate::draft22::fill::LocationFilter, AnyConnectionError> {
904 use crate::draft22::fill::LocationFilter;
905
906 let delta = self.end_group.checked_sub(self.start_group).ok_or_else(|| {
907 AnyConnectionError::facade(format!(
908 "fetch: a range from Group {} to Group {} runs backwards, and draft-22 \
909 Section 9.20.9 encodes the end Group as an unsigned delta from the start",
910 self.start_group, self.end_group
911 ))
912 })?;
913 let filter = match self.end {
914 FetchEnd::EntireGroup => {
915 LocationFilter::range(self.start_group, self.start_object, delta)
916 }
917 FetchEnd::Object(last) => {
918 LocationFilter::range_to(self.start_group, self.start_object, delta, last)
919 }
920 };
921 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
922 }
923 /// The draft-23 `LOCATION_FILTER` that carries this range.
924 ///
925 /// Draft-23 Section 9.11 gives FETCH no `Start Location` or `End Location`
926 /// field: the range is the parameter, and draft-23 Section 3.2 has a FETCH
927 /// request Objects "between a Start Location and an End Location,
928 /// inclusive". So [`FetchEnd::Object`] is written as it stands, Location
929 /// Filter Type 0x04 (Absolute Range), and [`FetchEnd::EntireGroup`] is type
930 /// 0x03, which draft-23 Section 9.19.9 ends at the "last Object of Group
931 /// StartGroup + EndGroupDelta". The end Group travels as `EndGroupDelta`,
932 /// "delta encoded from StartGroup", so it is `end_group - start_group`.
933 /// Both types are absolute, so neither meets draft-23 Section 3.2's "If the
934 /// Location filter is relative (see Section 9.19.9), the publisher MUST
935 /// return REQUEST_ERROR with error code INVALID_RANGE."
936 ///
937 /// # Errors
938 ///
939 /// [`AnyConnectionError`] when `end_group` is below `start_group`: the
940 /// delta is unsigned and there is no such filter.
941 #[cfg(feature = "draft23")]
942 pub fn location_filter_draft23(
943 &self,
944 ) -> Result<crate::draft23::fill::LocationFilter, AnyConnectionError> {
945 use crate::draft23::fill::LocationFilter;
946
947 let delta = self.end_group.checked_sub(self.start_group).ok_or_else(|| {
948 AnyConnectionError::facade(format!(
949 "fetch: a range from Group {} to Group {} runs backwards, and draft-23 \
950 Section 9.19.9 encodes the end Group as an unsigned delta from the start",
951 self.start_group, self.end_group
952 ))
953 })?;
954 let filter = match self.end {
955 FetchEnd::EntireGroup => {
956 LocationFilter::range(self.start_group, self.start_object, delta)
957 }
958 FetchEnd::Object(last) => {
959 LocationFilter::range_to(self.start_group, self.start_object, delta, last)
960 }
961 };
962 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
963 }
964}
965
966/// Where a **Joining** FETCH starts, said once for the two forms the drafts
967/// give it.
968///
969/// A Joining FETCH names no track and no end. Draft-19 Section 10.12.2: "A
970/// Joining Fetch is associated with a Subscribe request by specifying the
971/// Request ID of an active subscription. A publisher receiving a Joining Fetch
972/// uses properties of the associated Subscribe to determine the Track
973/// Namespace, Track Name and End Location such that it is contiguous with the
974/// associated Subscribe." So the only thing a subscriber still has to say is
975/// where the range *begins* — and the field that says it is one varint called
976/// Joining Start, which means two different things depending on a **Fetch
977/// Type** written six bytes earlier.
978///
979/// That is why this is an enum and not a `u64`. Section 10.12.2.1 gives the
980/// publisher two sentences for the same field: for a Relative Joining Fetch it
981/// sets the Start Location to "{Subscribe Largest Location.Group - Joining
982/// Start, 0}", and for an Absolute Joining Fetch it sets the Start Location "to
983/// Joining Start". A caller handing `3` to a single-number entry point would be
984/// asking for three groups of history on one call and for the whole track from
985/// Group 3 on the other, with nothing in the argument to say which was meant.
986///
987/// [`AnyConnection::fetch_joining`] turns the variant into the Fetch Type as
988/// well as the number, so the two cannot come apart.
989#[derive(Debug, Clone, Copy, PartialEq, Eq)]
990pub enum JoiningStart {
991 /// Start this many Groups **before** the joined subscription's Largest
992 /// Location — Fetch Type `0x2`, Relative Joining.
993 ///
994 /// `GroupsBefore(0)` is the subscription's own current Group and nothing
995 /// earlier; `GroupsBefore(1)` adds the Group before it. The publisher
996 /// clamps at zero — "{Subscribe Largest Location.Group - Joining Start,
997 /// 0}" — so an offset larger than the track is long asks for the whole of
998 /// it rather than being an error.
999 ///
1000 /// This is the form a subscriber that has **not** been told a Largest
1001 /// Location can still use, which is most of them: it is the publisher that
1002 /// does the subtraction.
1003 GroupsBefore(u64),
1004 /// Start at this Group, absolutely — Fetch Type `0x3`, Absolute Joining.
1005 ///
1006 /// The Start Location is `{Group, 0}`. Drafts 08 through 10 have no such
1007 /// Fetch Type — their FETCH offers Standalone and one Joining kind, and
1008 /// draft-11 is where the pair arrives — so this variant is refused there
1009 /// rather than sent as the relative one, which would ask a completely
1010 /// different question.
1011 Group(u64),
1012}
1013
1014/// Where a subscription's range stops, said once in a way no draft can read two
1015/// ways.
1016///
1017/// A SUBSCRIBE that names a start location may also name an end, and the
1018/// drafts spell that end three different ways. Draft-07 Section 6.4
1019/// gives the AbsoluteRange filter an End Group **and** an End Object, with
1020/// FETCH's own conventions — "the end Object ID, plus 1. A value of 0 means the
1021/// entire group is requested." Draft-08 deleted the End Object and redefined
1022/// the End Group as "the end Group ID, inclusive", and drafts 09 through 19
1023/// kept it that way. Draft-20 Section 5.1.2 then brought an end Object back, as
1024/// the fourth field of a `LOCATION_FILTER` whose range is **inclusive** at both
1025/// ends with no plus one anywhere.
1026///
1027/// One `end_object: u64` at this boundary would therefore have meant the last
1028/// Object on four drafts, the last Object plus one on one of them, and nothing
1029/// at all on the other twelve — which is [`FetchEnd`]'s problem a second time,
1030/// in a place where it is worse: there, every draft could at least carry the
1031/// field.
1032///
1033/// So [`SubscribeEnd::EndOfGroup`] is the end every draft with a range can
1034/// express, and [`SubscribeEnd::ThroughObject`] is the one only draft-07 and
1035/// drafts 20 through 23 can. The twelve drafts between them **refuse** it rather than
1036/// rounding it up to the whole group, because a subscription that quietly
1037/// covers more than it asked for is one whose extra objects look like a relay
1038/// ignoring the range.
1039#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1040pub enum SubscribeEnd {
1041 /// No end at all — the AbsoluteStart filter. Every draft has it.
1042 ///
1043 /// On drafts 20 and 21 this is not expressible from the start location
1044 /// `{0, 0}`; see [`SubscribeRange::location_filter_draft20`] for the collision and
1045 /// for the two ways round it.
1046 Open,
1047 /// Through the whole of this Group, however many Objects it turns out to
1048 /// hold. The Group is **absolute** on every draft, including the ones that
1049 /// put a delta on the wire.
1050 EndOfGroup(u64),
1051 /// Through this Object of this Group, **inclusive** — it is the last Object
1052 /// the subscription covers, and the range holds it.
1053 ///
1054 /// Expressible on draft-07 and drafts 20 through 23, and on nothing between
1055 /// them. See [`SubscribeEnd`] for why those and not the twelve in the middle, and
1056 /// [`SubscribeRange::inline_end_location`] for the plus one draft-07 needs
1057 /// and draft-20 must not have.
1058 ThroughObject {
1059 /// The Group the range ends in, absolute.
1060 end_group: u64,
1061 /// The last Object of `end_group` the range covers.
1062 end_object: u64,
1063 },
1064}
1065
1066/// The range one [`AnyConnection::subscribe_range`] asks for.
1067///
1068/// The start is a plain Location on all the drafts and needs no type; the
1069/// end is [`SubscribeEnd`], which is the whole point. What the drafts do to the
1070/// **end Group** is handled here rather than by the caller: drafts 08 through 16
1071/// write it out in full, drafts 17 through 20 write it as a delta from the start
1072/// Group, and this type takes the absolute value and derives the delta — the
1073/// same split, and the same direction of travel, as [`FetchRange`].
1074///
1075/// # Which filter this is
1076///
1077/// [`SubscribeEnd::Open`] is the AbsoluteStart filter and everything else is
1078/// AbsoluteRange. The Filter Type is never taken as an argument beside the
1079/// fields, because that is the bug this type exists to make unavailable: a
1080/// SUBSCRIBE naming AbsoluteStart with no Start Location beside it is a frame
1081/// whose declared length is short by the fields its own type promised, and the
1082/// publisher reading it runs off the end of the message with nothing local to
1083/// complain. See `AnyConnection::subscribe`, which refuses both filters for
1084/// exactly that reason and points here.
1085#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1086pub struct SubscribeRange {
1087 /// The Group the range starts in.
1088 pub start_group: u64,
1089 /// The Object within `start_group` the range starts at, inclusive.
1090 pub start_object: u64,
1091 /// Where the range stops.
1092 pub end: SubscribeEnd,
1093}
1094
1095impl SubscribeRange {
1096 /// From this Location onward, with no end: the AbsoluteStart filter.
1097 pub fn starting_at(start_group: u64, start_object: u64) -> Self {
1098 Self { start_group, start_object, end: SubscribeEnd::Open }
1099 }
1100
1101 /// From this Location through the whole of `end_group`.
1102 ///
1103 /// `end_group` is absolute and must not be below `start_group` — drafts 17
1104 /// and later encode it as an unsigned delta from the start and have no way
1105 /// to say "backwards".
1106 pub fn through_end_of_group(start_group: u64, start_object: u64, end_group: u64) -> Self {
1107 Self { start_group, start_object, end: SubscribeEnd::EndOfGroup(end_group) }
1108 }
1109
1110 /// From this Location through `end_object` of `end_group`, **inclusive**.
1111 ///
1112 /// Drafts 07 and 20 through 23 only. See [`SubscribeEnd::ThroughObject`].
1113 pub fn through_object(
1114 start_group: u64,
1115 start_object: u64,
1116 end_group: u64,
1117 end_object: u64,
1118 ) -> Self {
1119 Self {
1120 start_group,
1121 start_object,
1122 end: SubscribeEnd::ThroughObject { end_group, end_object },
1123 }
1124 }
1125
1126 /// The whole of one Group, from its first Object.
1127 ///
1128 /// The shape a caller asking "what has this track carried in group N"
1129 /// wants, and the one that is expressible on all the drafts.
1130 pub fn whole_group(group: u64) -> Self {
1131 Self::through_end_of_group(group, 0, group)
1132 }
1133
1134 /// Which of the two filters this range names.
1135 pub fn filter_type(&self) -> moqtap_codec::types::FilterType {
1136 match self.end {
1137 SubscribeEnd::Open => moqtap_codec::types::FilterType::AbsoluteStart,
1138 _ => moqtap_codec::types::FilterType::AbsoluteRange,
1139 }
1140 }
1141
1142 /// The Start Location every draft carrying this range puts on the wire.
1143 pub fn start_location(&self) -> moqtap_codec::types::Location {
1144 moqtap_codec::types::Location {
1145 group: moqtap_codec::varint::VarInt::from_u64_moqt(self.start_group),
1146 object: moqtap_codec::varint::VarInt::from_u64_moqt(self.start_object),
1147 }
1148 }
1149
1150 /// The end Group in full, or `None` for an open-ended range.
1151 pub fn end_group(&self) -> Option<u64> {
1152 match self.end {
1153 SubscribeEnd::Open => None,
1154 SubscribeEnd::EndOfGroup(group)
1155 | SubscribeEnd::ThroughObject { end_group: group, .. } => Some(group),
1156 }
1157 }
1158
1159 /// The `End Location` draft-07 carries inline: the end Group, and the end
1160 /// Object **plus one**, with `0` meaning the whole Group.
1161 ///
1162 /// Draft-07 Section 6.4 gives its AbsoluteRange filter FETCH's two fields
1163 /// and FETCH's two conventions, in the same words, which is why
1164 /// `moqtap_codec::types::check_location_range` checks both. So this is
1165 /// [`FetchRange::inline_end_object`]'s arithmetic on the one draft where a
1166 /// *subscription* needs it, and it lives here rather than being reached for
1167 /// from there so that the two cannot drift into disagreeing about a
1168 /// sentence they share.
1169 ///
1170 /// # Errors
1171 ///
1172 /// [`SubscribeEnd::ThroughObject`] with `end_object` at `u64::MAX`: the plus
1173 /// one leaves the number space, and wrapping it to `0` would silently widen
1174 /// the range to the whole Group. Draft-20 can express that range and
1175 /// draft-07 cannot.
1176 pub fn inline_end_location(
1177 &self,
1178 ) -> Result<Option<moqtap_codec::types::Location>, AnyConnectionError> {
1179 let raw = match self.end {
1180 SubscribeEnd::Open => return Ok(None),
1181 SubscribeEnd::EndOfGroup(group) => (group, 0),
1182 SubscribeEnd::ThroughObject { end_group, end_object } => {
1183 let plus_one = end_object.checked_add(1).ok_or_else(|| {
1184 AnyConnectionError::facade(format!(
1185 "subscribe_range: a range ending at Object {end_object} cannot be \
1186 expressed on draft-07, whose End Location.Object is the last Object plus 1"
1187 ))
1188 })?;
1189 (end_group, plus_one)
1190 }
1191 };
1192 Ok(Some(moqtap_codec::types::Location {
1193 group: moqtap_codec::varint::VarInt::from_u64_moqt(raw.0),
1194 object: moqtap_codec::varint::VarInt::from_u64_moqt(raw.1),
1195 }))
1196 }
1197
1198 /// The end Group drafts 08 through 19 carry, which is a Group and never a
1199 /// Location.
1200 ///
1201 /// # Errors
1202 ///
1203 /// [`SubscribeEnd::ThroughObject`], which those twelve drafts deleted the
1204 /// field for. Refused rather than widened to the whole Group: see
1205 /// [`SubscribeEnd`].
1206 fn group_only_end(&self, draft: DraftVersion) -> Result<Option<u64>, AnyConnectionError> {
1207 match self.end {
1208 SubscribeEnd::Open => Ok(None),
1209 SubscribeEnd::EndOfGroup(group) => Ok(Some(group)),
1210 SubscribeEnd::ThroughObject { .. } => Err(AnyConnectionError::facade(format!(
1211 "subscribe_range: draft-{:02} carries no End Object on SUBSCRIBE — draft-08 \
1212 deleted the field and draft-20 restored it inside LOCATION_FILTER, so a range \
1213 ending inside a group is expressible on draft-07 and drafts 20 through 23 \
1214 alone. Ask for \
1215 the whole group with SubscribeEnd::EndOfGroup",
1216 draft.number()
1217 ))),
1218 }
1219 }
1220
1221 /// The end Group as drafts 17 and later write it: a delta from the start
1222 /// Group.
1223 ///
1224 /// # Errors
1225 ///
1226 /// An `end_group` below `start_group`. The delta is unsigned and there is no
1227 /// such filter; drafts 08 through 16 have an absolute field and would put
1228 /// the range on the wire, where the publisher refuses it. The difference is
1229 /// where the refusal happens, not whether the range is legal.
1230 pub fn end_group_delta(&self) -> Result<Option<u64>, AnyConnectionError> {
1231 let Some(end_group) = self.end_group() else {
1232 return Ok(None);
1233 };
1234 end_group.checked_sub(self.start_group).map(Some).ok_or_else(|| {
1235 AnyConnectionError::facade(format!(
1236 "subscribe_range: a range from Group {} to Group {end_group} runs backwards, and \
1237 drafts 17 and later encode the end Group as an unsigned delta from the start",
1238 self.start_group
1239 ))
1240 })
1241 }
1242
1243 /// This range as the filter parameter drafts 15 through 19 carry it in.
1244 ///
1245 /// `delta` is what those drafts disagree about among themselves: 15 and 16
1246 /// write the End Group out in full, and 17 introduced the delta. The codec's
1247 /// [`SubscriptionFilter`](moqtap_codec::subscription_filter::SubscriptionFilter)
1248 /// refuses a filter whose end is spelled the other draft's way, so the two
1249 /// cannot be mixed up silently here.
1250 ///
1251 /// # Errors
1252 ///
1253 /// [`SubscribeEnd::ThroughObject`], which these five drafts have no field
1254 /// for — the same wall drafts 08 through 14 hit, one layer along. It is
1255 /// worth saying twice because it is not obvious from the shape of the code:
1256 /// the filter these drafts carry has a Start *Location* and an End *Group*,
1257 /// so a range ending inside a group has nowhere to put its Object and would
1258 /// otherwise be written out as the whole group with nothing to say it had
1259 /// been widened.
1260 ///
1261 /// And an `end_group` below `start_group` on the drafts that write a delta.
1262 pub fn subscription_filter(
1263 &self,
1264 draft: DraftVersion,
1265 delta: bool,
1266 ) -> Result<moqtap_codec::subscription_filter::SubscriptionFilter, AnyConnectionError> {
1267 use moqtap_codec::subscription_filter::{FilterEnd, SubscriptionFilter};
1268
1269 let end_group = match (self.group_only_end(draft)?, delta) {
1270 (None, _) => None,
1271 (Some(group), false) => Some(FilterEnd::Group(group)),
1272 (Some(_), true) => {
1273 Some(FilterEnd::GroupDelta(self.end_group_delta()?.unwrap_or_default()))
1274 }
1275 };
1276 Ok(SubscriptionFilter {
1277 filter_type: self.filter_type(),
1278 start_location: Some(self.start_location()),
1279 end_group,
1280 })
1281 }
1282
1283 /// This range as the draft-20 `LOCATION_FILTER` that carries it.
1284 ///
1285 /// Two fields for an open-ended range, three for one through the end of a
1286 /// Group, four for one ending at an Object — Section 5.1.2 selects the shape
1287 /// by how many fields the value holds, so each of the three is a different
1288 /// constructor rather than the same one with values left out.
1289 ///
1290 /// # `{0, 0}` means the opposite here, and is refused
1291 ///
1292 /// On drafts 07 through 19 and on draft-22 an AbsoluteStart at `{0, 0}` is
1293 /// the beginning of the track. Draft-20 Section 5.1.2 gives the two-field filter `{0, 0}` to
1294 /// **Next Object** — `{Largest Object.Group, Largest Object.Object + 1}`,
1295 /// or `{0,0}` where nothing has been delivered — which is the live edge and
1296 /// not the beginning. The identical call would therefore ask every other draft
1297 /// for everything and draft-20 for nothing that has already happened, and
1298 /// nothing on the wire says which was meant.
1299 ///
1300 /// So it is refused, and the error names both ways round it: a range
1301 /// (`through_end_of_group`, which is three fields and unambiguous) for the
1302 /// beginning of the track, and
1303 /// [`LocationFilter::next_object`](crate::draft20::fill::LocationFilter::next_object)
1304 /// through the draft-20 variant for the live edge. This is the only value, on
1305 /// drafts 20 and 21, where the two readings collide: a `{0, 0}` start with an
1306 /// end beside it is three or four fields and means what it says, and any
1307 /// other start location is unambiguous with or without one.
1308 ///
1309 /// # Errors
1310 ///
1311 /// The `{0, 0}` collision above, and an `end_group` below `start_group`, for
1312 /// which see [`Self::end_group_delta`].
1313 /// The name carries the draft because the type does: `LOCATION_FILTER`
1314 /// is draft-20's and later's, and each draft's `fill` module declares its
1315 /// own `LocationFilter`. An unsuffixed name would be two inherent methods
1316 /// of one name the moment a second draft defines the parameter.
1317 #[cfg(feature = "draft20")]
1318 pub fn location_filter_draft20(
1319 &self,
1320 ) -> Result<crate::draft20::fill::LocationFilter, AnyConnectionError> {
1321 use crate::draft20::fill::LocationFilter;
1322
1323 let filter = match self.end {
1324 SubscribeEnd::Open if self.start_group == 0 && self.start_object == 0 => {
1325 return Err(AnyConnectionError::facade(
1326 "subscribe_range: draft-20 Section 5.1.2 reads a two-field LOCATION_FILTER of \
1327 {0, 0} as Next Object — the live edge — where drafts 07 through 19 read an \
1328 AbsoluteStart at {0, 0} as the beginning of the track. For the beginning, \
1329 give the range an end: SubscribeRange::through_end_of_group. For the live \
1330 edge, LocationFilter::next_object through the Draft20 variant",
1331 ));
1332 }
1333 SubscribeEnd::Open => {
1334 Ok(LocationFilter::absolute_start(self.start_group, self.start_object))
1335 }
1336 SubscribeEnd::EndOfGroup(_) => LocationFilter::range(
1337 self.start_group,
1338 self.start_object,
1339 self.end_group_delta()?.unwrap_or_default(),
1340 ),
1341 SubscribeEnd::ThroughObject { end_object, .. } => LocationFilter::range_to(
1342 self.start_group,
1343 self.start_object,
1344 self.end_group_delta()?.unwrap_or_default(),
1345 end_object,
1346 ),
1347 };
1348 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
1349 }
1350 /// This range as the draft-21 `LOCATION_FILTER` that carries it.
1351 ///
1352 /// Two fields for an open-ended range, three for one through the end of a
1353 /// Group, four for one ending at an Object — Section 9.20.10 selects the shape
1354 /// by how many fields the value holds, so each of the three is a different
1355 /// constructor rather than the same one with values left out.
1356 ///
1357 /// # `{0, 0}` means the opposite here, and is refused
1358 ///
1359 /// On drafts 07 through 19 and on draft-22 an AbsoluteStart at `{0, 0}` is
1360 /// the beginning of the track. Draft-21 Section 9.20.10 gives the two-field filter `{0, 0}` to
1361 /// **Next Object** — `{Largest Object.Group, Largest Object.Object + 1}`,
1362 /// or `{0,0}` where nothing has been delivered — which is the live edge and
1363 /// not the beginning. The identical call would therefore ask every other draft
1364 /// for everything and draft-21 for nothing that has already happened, and
1365 /// nothing on the wire says which was meant.
1366 ///
1367 /// So it is refused, and the error names both ways round it: a range
1368 /// (`through_end_of_group`, which is three fields and unambiguous) for the
1369 /// beginning of the track, and
1370 /// [`LocationFilter::next_object`](crate::draft21::fill::LocationFilter::next_object)
1371 /// through the draft-21 variant for the live edge. This is the only value, on
1372 /// drafts 20 and 21, where the two readings collide: a `{0, 0}` start with an
1373 /// end beside it is three or four fields and means what it says, and any
1374 /// other start location is unambiguous with or without one.
1375 ///
1376 /// # Errors
1377 ///
1378 /// The `{0, 0}` collision above, and an `end_group` below `start_group`, for
1379 /// which see [`Self::end_group_delta`].
1380 /// The name carries the draft because the type does: `LOCATION_FILTER`
1381 /// is draft-21's and later's, and each draft's `fill` module declares its
1382 /// own `LocationFilter`. An unsuffixed name would be two inherent methods
1383 /// of one name the moment a second draft defines the parameter.
1384 #[cfg(feature = "draft21")]
1385 pub fn location_filter_draft21(
1386 &self,
1387 ) -> Result<crate::draft21::fill::LocationFilter, AnyConnectionError> {
1388 use crate::draft21::fill::LocationFilter;
1389
1390 let filter = match self.end {
1391 SubscribeEnd::Open if self.start_group == 0 && self.start_object == 0 => {
1392 return Err(AnyConnectionError::facade(
1393 "subscribe_range: draft-21 Section 9.20.10 reads a two-field LOCATION_FILTER of \
1394 {0, 0} as Next Object — the live edge — where drafts 07 through 19 read an \
1395 AbsoluteStart at {0, 0} as the beginning of the track. For the beginning, \
1396 give the range an end: SubscribeRange::through_end_of_group. For the live \
1397 edge, LocationFilter::next_object through the Draft21 variant",
1398 ));
1399 }
1400 SubscribeEnd::Open => {
1401 Ok(LocationFilter::absolute_start(self.start_group, self.start_object))
1402 }
1403 SubscribeEnd::EndOfGroup(_) => LocationFilter::range(
1404 self.start_group,
1405 self.start_object,
1406 self.end_group_delta()?.unwrap_or_default(),
1407 ),
1408 SubscribeEnd::ThroughObject { end_object, .. } => LocationFilter::range_to(
1409 self.start_group,
1410 self.start_object,
1411 self.end_group_delta()?.unwrap_or_default(),
1412 end_object,
1413 ),
1414 };
1415 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
1416 }
1417 /// This range as the draft-22 `LOCATION_FILTER` that carries it.
1418 ///
1419 /// Location Filter Type 0x02 for an open-ended range, 0x03 for one through
1420 /// the end of a Group, 0x04 for one ending at an Object — Section 9.20.9
1421 /// names the shape by its type, so each is a different constructor.
1422 ///
1423 /// # `{0, 0}` means what it says here
1424 ///
1425 /// An open range from `{0, 0}` is type 0x02 with both fields zero, which
1426 /// draft-22 reads as an absolute start at the beginning of the track, as
1427 /// an AbsoluteStart at `{0, 0}` was read before the field count chose the
1428 /// shape. The live edge is a type of its own, "If Location Filter Type is
1429 /// 0x05, no fields follow and it specifies the Next Object", so nothing on
1430 /// this draft collides with the beginning of the track and nothing is
1431 /// refused for it.
1432 /// [`LocationFilter::next_object`](crate::draft22::fill::LocationFilter::next_object)
1433 /// through the draft-22 variant is the live edge.
1434 ///
1435 /// # Errors
1436 ///
1437 /// An `end_group` below `start_group`, for which see
1438 /// [`Self::end_group_delta`].
1439 /// The name carries the draft because the type does: each draft's `fill`
1440 /// module declares its own `LocationFilter`, and draft-22's writes a
1441 /// `Location Filter Type` that draft-21's has no field for.
1442 #[cfg(feature = "draft22")]
1443 pub fn location_filter_draft22(
1444 &self,
1445 ) -> Result<crate::draft22::fill::LocationFilter, AnyConnectionError> {
1446 use crate::draft22::fill::LocationFilter;
1447
1448 let filter = match self.end {
1449 SubscribeEnd::Open => {
1450 Ok(LocationFilter::absolute_start(self.start_group, self.start_object))
1451 }
1452 SubscribeEnd::EndOfGroup(_) => LocationFilter::range(
1453 self.start_group,
1454 self.start_object,
1455 self.end_group_delta()?.unwrap_or_default(),
1456 ),
1457 SubscribeEnd::ThroughObject { end_object, .. } => LocationFilter::range_to(
1458 self.start_group,
1459 self.start_object,
1460 self.end_group_delta()?.unwrap_or_default(),
1461 end_object,
1462 ),
1463 };
1464 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
1465 }
1466 /// This range as the draft-23 `LOCATION_FILTER` that carries it.
1467 ///
1468 /// Location Filter Type 0x02 for an open-ended range, 0x03 for one through
1469 /// the end of a Group, 0x04 for one ending at an Object (draft-23 Section
1470 /// 9.19.9). An open range from `{0, 0}` is type 0x02 with both fields zero,
1471 /// the beginning of the track; the live edge is type 0x05, "If Location
1472 /// Filter Type is 0x05, no fields follow and it specifies the Next Object",
1473 /// built by
1474 /// [`LocationFilter::next_object`](crate::draft23::fill::LocationFilter::next_object).
1475 ///
1476 /// # Errors
1477 ///
1478 /// An `end_group` below `start_group`, for which see
1479 /// [`Self::end_group_delta`].
1480 #[cfg(feature = "draft23")]
1481 pub fn location_filter_draft23(
1482 &self,
1483 ) -> Result<crate::draft23::fill::LocationFilter, AnyConnectionError> {
1484 use crate::draft23::fill::LocationFilter;
1485
1486 let filter = match self.end {
1487 SubscribeEnd::Open => {
1488 Ok(LocationFilter::absolute_start(self.start_group, self.start_object))
1489 }
1490 SubscribeEnd::EndOfGroup(_) => LocationFilter::range(
1491 self.start_group,
1492 self.start_object,
1493 self.end_group_delta()?.unwrap_or_default(),
1494 ),
1495 SubscribeEnd::ThroughObject { end_object, .. } => LocationFilter::range_to(
1496 self.start_group,
1497 self.start_object,
1498 self.end_group_delta()?.unwrap_or_default(),
1499 end_object,
1500 ),
1501 };
1502 filter.map_err(|e| AnyConnectionError::facade(e.to_string()))
1503 }
1504}
1505
1506/// A request made through [`AnyConnection`], in whichever form the negotiated
1507/// draft carries it.
1508///
1509/// Drafts 07-15 put every request on the single bidirectional control stream,
1510/// so all a requester keeps is the request ID it was allocated. Draft-16 moved
1511/// one request off it: Section 3.3 there names "two uses of bidirectional
1512/// streams, the control stream, which begins with CLIENT_SETUP, and
1513/// SUBSCRIBE_NAMESPACE", so a namespace subscription on that draft owns a
1514/// stream and everything else does not. From draft-17 on every request does,
1515/// and the stream *is* the correlation — responses on those drafts carry no
1516/// request ID at all. The variants reflect that split rather than hiding it.
1517///
1518/// Hold on to this value for as long as the request is live. Dropping a
1519/// per-request variant resets its stream, which the peer reads as a
1520/// cancellation; dropping [`AnyRequest::ControlPlane`] does nothing, because
1521/// there is no stream to reset.
1522///
1523/// The per-request variants are large — a `RequestStream` holds both halves of
1524/// a QUIC stream — and `ControlPlane` is two small fields. That disparity is
1525/// only visible when a single draft from 17 on is the only one enabled; with
1526/// more than one, the largest variants are the same size as each other.
1527/// Boxing them to close it would put an allocation on every request of every
1528/// draft to flatter a build no released configuration uses.
1529#[allow(clippy::large_enum_variant)]
1530#[must_use = "dropping a request stream cancels the request"]
1531pub enum AnyRequest {
1532 /// Drafts 07-16: the request was written on the control stream and is
1533 /// identified only by its request ID.
1534 ControlPlane {
1535 /// The request ID the endpoint allocated.
1536 request_id: moqtap_codec::varint::VarInt,
1537 /// The draft that allocated it.
1538 draft: DraftVersion,
1539 },
1540 /// Draft-16: a namespace subscription, the one request on that draft that
1541 /// owns a bidirectional stream. Every other draft-16 request comes back as
1542 /// [`AnyRequest::ControlPlane`].
1543 #[cfg(feature = "draft16")]
1544 Draft16(crate::draft16::connection::NamespaceStream),
1545 /// Draft-17: the request owns a bidirectional stream.
1546 #[cfg(feature = "draft17")]
1547 Draft17(crate::draft17::connection::RequestStream),
1548 /// Draft-18: the request owns a bidirectional stream.
1549 #[cfg(feature = "draft18")]
1550 Draft18(crate::draft18::connection::RequestStream),
1551 /// Draft-19: the request owns a bidirectional stream.
1552 #[cfg(feature = "draft19")]
1553 Draft19(crate::draft19::connection::RequestStream),
1554 /// Draft-20: the request owns a bidirectional stream.
1555 #[cfg(feature = "draft20")]
1556 Draft20(crate::draft20::connection::RequestStream),
1557 /// Draft-21: the request owns a bidirectional stream.
1558 #[cfg(feature = "draft21")]
1559 Draft21(crate::draft21::connection::RequestStream),
1560 /// Draft-22: the request owns a bidirectional stream.
1561 #[cfg(feature = "draft22")]
1562 Draft22(crate::draft22::connection::RequestStream),
1563 /// Draft-23: the request owns a bidirectional stream.
1564 #[cfg(feature = "draft23")]
1565 Draft23(crate::draft23::connection::RequestStream),
1566}
1567
1568impl AnyRequest {
1569 /// The request ID this request was allocated.
1570 pub fn request_id(&self) -> moqtap_codec::varint::VarInt {
1571 match self {
1572 Self::ControlPlane { request_id, .. } => *request_id,
1573 #[cfg(feature = "draft16")]
1574 Self::Draft16(r) => r.request_id(),
1575 #[cfg(feature = "draft17")]
1576 Self::Draft17(r) => r.request_id(),
1577 #[cfg(feature = "draft18")]
1578 Self::Draft18(r) => r.request_id(),
1579 #[cfg(feature = "draft19")]
1580 Self::Draft19(r) => r.request_id(),
1581 #[cfg(feature = "draft20")]
1582 Self::Draft20(r) => r.request_id(),
1583 #[cfg(feature = "draft21")]
1584 Self::Draft21(r) => r.request_id(),
1585 #[cfg(feature = "draft22")]
1586 Self::Draft22(r) => r.request_id(),
1587 #[cfg(feature = "draft23")]
1588 Self::Draft23(r) => r.request_id(),
1589 }
1590 }
1591
1592 /// The draft that carries this request.
1593 pub fn draft(&self) -> DraftVersion {
1594 match self {
1595 Self::ControlPlane { draft, .. } => *draft,
1596 #[cfg(feature = "draft16")]
1597 Self::Draft16(r) => r.draft(),
1598 #[cfg(feature = "draft17")]
1599 Self::Draft17(r) => r.draft(),
1600 #[cfg(feature = "draft18")]
1601 Self::Draft18(r) => r.draft(),
1602 #[cfg(feature = "draft19")]
1603 Self::Draft19(r) => r.draft(),
1604 #[cfg(feature = "draft20")]
1605 Self::Draft20(r) => r.draft(),
1606 #[cfg(feature = "draft21")]
1607 Self::Draft21(r) => r.draft(),
1608 #[cfg(feature = "draft22")]
1609 Self::Draft22(r) => r.draft(),
1610 #[cfg(feature = "draft23")]
1611 Self::Draft23(r) => r.draft(),
1612 }
1613 }
1614
1615 /// Which stream this request owns, or `None` on a draft that carries
1616 /// requests on the shared control stream.
1617 ///
1618 /// This is the observable difference between the two variants: a caller
1619 /// that needs to correlate a response by stream — which is the only
1620 /// correlation drafts 17-19 offer — gets `Some` exactly when the draft
1621 /// provides one.
1622 ///
1623 /// The number is the QUIC stream ID (RFC 9000 Section 2.1), as on
1624 /// [`AnySubgroupWriter::stream_id`].
1625 pub fn stream_id(&self) -> Option<u64> {
1626 match self {
1627 Self::ControlPlane { .. } => None,
1628 #[cfg(feature = "draft16")]
1629 Self::Draft16(r) => Some(r.stream_id()),
1630 #[cfg(feature = "draft17")]
1631 Self::Draft17(r) => Some(r.stream_id()),
1632 #[cfg(feature = "draft18")]
1633 Self::Draft18(r) => Some(r.stream_id()),
1634 #[cfg(feature = "draft19")]
1635 Self::Draft19(r) => Some(r.stream_id()),
1636 #[cfg(feature = "draft20")]
1637 Self::Draft20(r) => Some(r.stream_id()),
1638 #[cfg(feature = "draft21")]
1639 Self::Draft21(r) => Some(r.stream_id()),
1640 #[cfg(feature = "draft22")]
1641 Self::Draft22(r) => Some(r.stream_id()),
1642 #[cfg(feature = "draft23")]
1643 Self::Draft23(r) => Some(r.stream_id()),
1644 }
1645 }
1646
1647 /// Cancel the request by resetting its stream with `code`.
1648 ///
1649 /// Only a request that owns a stream can do this, which is a draft-16
1650 /// namespace subscription and every request from draft-17 on. Cancelling a
1651 /// request that lives on the control stream means sending a message
1652 /// (UNSUBSCRIBE, FETCH_CANCEL, and so on), which needs the connection and
1653 /// is therefore not reachable from the request handle alone. There this
1654 /// refuses rather than silently doing nothing.
1655 #[allow(unused_variables)]
1656 pub fn cancel(&mut self, code: u64) -> Result<(), AnyConnectionError> {
1657 match self {
1658 Self::ControlPlane { draft, .. } => Err(AnyConnectionError::facade(format!(
1659 "cancel: draft {draft:?} carries this request on the control stream, so the \
1660 request handle has no stream to reset; send the draft's own cancellation \
1661 message instead"
1662 ))),
1663 #[cfg(feature = "draft16")]
1664 Self::Draft16(r) => r.cancel(code).map_err(AnyConnectionError::from),
1665 #[cfg(feature = "draft17")]
1666 Self::Draft17(r) => r.cancel(code).map_err(AnyConnectionError::from),
1667 #[cfg(feature = "draft18")]
1668 Self::Draft18(r) => r.cancel(code).map_err(AnyConnectionError::from),
1669 #[cfg(feature = "draft19")]
1670 Self::Draft19(r) => r.cancel(code).map_err(AnyConnectionError::from),
1671 #[cfg(feature = "draft20")]
1672 Self::Draft20(r) => r.cancel(code).map_err(AnyConnectionError::from),
1673 #[cfg(feature = "draft21")]
1674 Self::Draft21(r) => r.cancel(code).map_err(AnyConnectionError::from),
1675 #[cfg(feature = "draft22")]
1676 Self::Draft22(r) => r.cancel(code).map_err(AnyConnectionError::from),
1677 #[cfg(feature = "draft23")]
1678 Self::Draft23(r) => r.cancel(code).map_err(AnyConnectionError::from),
1679 }
1680 }
1681}
1682
1683/// A request the **peer** made, and the handle this side answers it through.
1684///
1685/// The mirror of [`AnyRequest`], and split the same way and for the same
1686/// reason: drafts 07 through 16 carry every request on one shared control
1687/// stream and identify it by its Request ID, and from draft-17 each request
1688/// owns a bidirectional stream that *is* its identity. What differs is who
1689/// allocated the ID — here the peer did, so it is read off the message rather
1690/// than out of this endpoint's own sequence.
1691///
1692/// There is no `Draft16` stream variant, unlike [`AnyRequest`]. Draft-16 gave a
1693/// stream to namespace *subscriptions* alone, and an inbound SUBSCRIBE on that
1694/// draft still arrives on the control stream.
1695///
1696/// Dropping an unanswered request from draft-17 on resets its stream, which the
1697/// peer reads as a refusal. Dropping a [`AnyInboundRequest::ControlPlane`] does
1698/// nothing at all, and the peer is left waiting — refusing there means sending
1699/// the draft's own error message through the variant.
1700#[allow(clippy::large_enum_variant)]
1701#[must_use = "dropping an unanswered request refuses it on the drafts that can"]
1702pub enum AnyInboundRequest {
1703 /// Drafts 07-16: identified only by the Request ID the peer allocated.
1704 ControlPlane {
1705 /// The Request ID the peer put on the message.
1706 request_id: moqtap_codec::varint::VarInt,
1707 /// The draft that carried it.
1708 draft: DraftVersion,
1709 },
1710 /// Draft-17: the request arrived on a bidirectional stream of its own.
1711 #[cfg(feature = "draft17")]
1712 Draft17(crate::draft17::connection::RequestStream),
1713 /// Draft-18: the request arrived on a bidirectional stream of its own.
1714 #[cfg(feature = "draft18")]
1715 Draft18(crate::draft18::connection::RequestStream),
1716 /// Draft-19: the request arrived on a bidirectional stream of its own.
1717 #[cfg(feature = "draft19")]
1718 Draft19(crate::draft19::connection::RequestStream),
1719 /// Draft-20: the request arrived on a bidirectional stream of its own.
1720 #[cfg(feature = "draft20")]
1721 Draft20(crate::draft20::connection::RequestStream),
1722 /// Draft-21: the request arrived on a bidirectional stream of its own.
1723 #[cfg(feature = "draft21")]
1724 Draft21(crate::draft21::connection::RequestStream),
1725 /// Draft-22: the request arrived on a bidirectional stream of its own.
1726 #[cfg(feature = "draft22")]
1727 Draft22(crate::draft22::connection::RequestStream),
1728 /// Draft-23: the request arrived on a bidirectional stream of its own.
1729 #[cfg(feature = "draft23")]
1730 Draft23(crate::draft23::connection::RequestStream),
1731}
1732
1733impl AnyInboundRequest {
1734 /// The Request ID the peer allocated for this request.
1735 pub fn request_id(&self) -> moqtap_codec::varint::VarInt {
1736 match self {
1737 Self::ControlPlane { request_id, .. } => *request_id,
1738 #[cfg(feature = "draft17")]
1739 Self::Draft17(r) => r.request_id(),
1740 #[cfg(feature = "draft18")]
1741 Self::Draft18(r) => r.request_id(),
1742 #[cfg(feature = "draft19")]
1743 Self::Draft19(r) => r.request_id(),
1744 #[cfg(feature = "draft20")]
1745 Self::Draft20(r) => r.request_id(),
1746 #[cfg(feature = "draft21")]
1747 Self::Draft21(r) => r.request_id(),
1748 #[cfg(feature = "draft22")]
1749 Self::Draft22(r) => r.request_id(),
1750 #[cfg(feature = "draft23")]
1751 Self::Draft23(r) => r.request_id(),
1752 }
1753 }
1754
1755 /// The draft that carries this request.
1756 pub fn draft(&self) -> DraftVersion {
1757 match self {
1758 Self::ControlPlane { draft, .. } => *draft,
1759 #[cfg(feature = "draft17")]
1760 Self::Draft17(r) => r.draft(),
1761 #[cfg(feature = "draft18")]
1762 Self::Draft18(r) => r.draft(),
1763 #[cfg(feature = "draft19")]
1764 Self::Draft19(r) => r.draft(),
1765 #[cfg(feature = "draft20")]
1766 Self::Draft20(r) => r.draft(),
1767 #[cfg(feature = "draft21")]
1768 Self::Draft21(r) => r.draft(),
1769 #[cfg(feature = "draft22")]
1770 Self::Draft22(r) => r.draft(),
1771 #[cfg(feature = "draft23")]
1772 Self::Draft23(r) => r.draft(),
1773 }
1774 }
1775
1776 /// The transport stream this request arrived on, or `None` on a draft that
1777 /// carries inbound requests on the shared control stream.
1778 ///
1779 /// The same observable difference [`AnyRequest::stream_id`] exposes, in the
1780 /// other direction.
1781 pub fn stream_id(&self) -> Option<u64> {
1782 match self {
1783 Self::ControlPlane { .. } => None,
1784 #[cfg(feature = "draft17")]
1785 Self::Draft17(r) => Some(r.stream_id()),
1786 #[cfg(feature = "draft18")]
1787 Self::Draft18(r) => Some(r.stream_id()),
1788 #[cfg(feature = "draft19")]
1789 Self::Draft19(r) => Some(r.stream_id()),
1790 #[cfg(feature = "draft20")]
1791 Self::Draft20(r) => Some(r.stream_id()),
1792 #[cfg(feature = "draft21")]
1793 Self::Draft21(r) => Some(r.stream_id()),
1794 #[cfg(feature = "draft22")]
1795 Self::Draft22(r) => Some(r.stream_id()),
1796 #[cfg(feature = "draft23")]
1797 Self::Draft23(r) => Some(r.stream_id()),
1798 }
1799 }
1800}
1801
1802/// What arrived from the peer, and whether this facade has a handle for it.
1803///
1804/// [`AnyConnection::recv_inbound`] returns one of these per message. Everything
1805/// is returned rather than filtered, because on drafts 07 through 16 the same
1806/// stream carries the peer's requests *and* the answers to this side's own —
1807/// a reader that quietly dropped what it was not looking for would swallow a
1808/// SUBSCRIBE_OK somebody was waiting on.
1809///
1810/// The variants differ in size for the reason [`AnyRequest`]'s do — one of them
1811/// carries a request stream and the other does not — and boxing to close the
1812/// gap would put an allocation on every message of every draft to flatter a
1813/// build no released configuration uses.
1814#[allow(clippy::large_enum_variant)]
1815pub enum AnyArrival {
1816 /// The peer subscribed to a track this session publishes.
1817 ///
1818 /// Answer it with [`AnyConnection::accept_subscribe`]. The message is the
1819 /// SUBSCRIBE as it arrived, which is where the track it names lives — and,
1820 /// on drafts 07 through 11, the Track Alias the subscriber chose.
1821 Subscribe {
1822 /// The SUBSCRIBE, decoded by the negotiated draft.
1823 message: moqtap_codec::dispatch::AnyControlMessage,
1824 /// The handle to answer it through.
1825 request: AnyInboundRequest,
1826 },
1827 /// Anything else the peer sent.
1828 ///
1829 /// On drafts 07 through 16 that is any control message which is not a
1830 /// SUBSCRIBE: a response to one of this side's own requests, or something
1831 /// sent unprompted such as MAX_REQUEST_ID. It has already been dispatched
1832 /// into the endpoint by the time it arrives here, so the session's state is
1833 /// correct whether the caller reads it or not.
1834 ///
1835 /// From draft-17 it is a request of a kind this facade has no entry point
1836 /// for, and **it has already been refused**: REQUEST_ERROR with
1837 /// NOT_SUPPORTED and a retry interval of 0, then FIN. There is no handle in
1838 /// this variant to answer it otherwise. That asymmetry is real: on the
1839 /// older drafts this variant is passive, and on the newer ones producing it
1840 /// refuses something.
1841 Other(moqtap_codec::dispatch::AnyControlMessage),
1842}
1843
1844/// One object on a subgroup stream, in the terms every draft shares.
1845///
1846/// The drafts give a subgroup object five different struct shapes, and
1847/// what varies between them is bookkeeping rather than content: whether the
1848/// extension block is counted or measured, whether the status is a field that
1849/// is always present or an `Option`, whether the declared length is stored
1850/// beside the payload or derived from it. None of that is a choice a caller
1851/// makes. What a caller has is an ID and some bytes.
1852///
1853/// The extension block is absent here for a different reason than the rest. It
1854/// is a property of the **stream**, not of the object — the header settles it
1855/// once, and an object with nothing to put in the block still writes a length
1856/// of zero on a stream that carries one, which is why
1857/// [`moqtap_codec::dispatch::AnySubgroupHeader::carries_extension_block`] is
1858/// asked of the header. [`AnyConnection::open_subgroup`] opens streams that
1859/// carry none wherever a draft has a way to say so — eleven of the drafts;
1860/// [`AnyConnection::accept_subgroup`] reads whichever kind the peer opened, and
1861/// hands the header over so a caller can ask.
1862#[derive(Debug, Clone, PartialEq, Eq)]
1863pub struct AnyObject {
1864 /// The Object ID, resolved.
1865 ///
1866 /// Drafts 14 and later encode it as a delta against the object before it on
1867 /// the same stream. The per-draft reader has already undone that, so this
1868 /// is the absolute ID on every draft and a caller never sees the encoding.
1869 pub object_id: u64,
1870 /// The payload. Empty when `status` is `Some`.
1871 pub payload: Vec<u8>,
1872 /// The Object Status wire code, or `None` when the object carried a payload
1873 /// instead.
1874 ///
1875 /// `None` means the same thing on all the drafts, which is why this is
1876 /// an `Option` even though half of them model the status as a field that is
1877 /// always there. Every draft writes the status **only** when the declared
1878 /// payload length is zero, because the status and the payload occupy the
1879 /// same position on the wire: no sequence of bytes states both. So the
1880 /// question "did this object carry a status?" has one answer per object,
1881 /// and it is the answer the wire gives rather than the one a particular
1882 /// draft's struct happens to hold.
1883 pub status: Option<u64>,
1884}
1885
1886/// A subgroup stream this endpoint opened and is writing objects to.
1887///
1888/// Returned by [`AnyConnection::open_subgroup`]. The header is already on the
1889/// wire by the time this exists — opening the stream and framing it are one
1890/// step, because a unidirectional stream with no header on it is not a subgroup
1891/// stream and there is nothing a caller could do with one.
1892///
1893/// Unlike [`AnyRequest`], there is no era split here and no variant that stands
1894/// for "the draft has no stream for this". Every draft from 07 on carries
1895/// subgroup objects on a unidirectional stream of their own, and this is the
1896/// first part of the facade that spans every draft without a footnote. The
1897/// walls that stop the control plane at draft-11 — no ANNOUNCE, no
1898/// TRACK_STATUS_REQUEST, a request ceiling granted by a message this crate
1899/// cannot send — are all about *requests*, and none of them touches a data
1900/// stream.
1901#[allow(clippy::large_enum_variant)]
1902pub enum AnySubgroupWriter {
1903 /// Draft-07's subgroup stream.
1904 #[cfg(feature = "draft07")]
1905 Draft07(crate::draft07::connection::FramedSendStream),
1906 /// Draft-08's subgroup stream.
1907 #[cfg(feature = "draft08")]
1908 Draft08(crate::draft08::connection::FramedSendStream),
1909 /// Draft-09's subgroup stream.
1910 #[cfg(feature = "draft09")]
1911 Draft09(crate::draft09::connection::FramedSendStream),
1912 /// Draft-10's subgroup stream.
1913 #[cfg(feature = "draft10")]
1914 Draft10(crate::draft10::connection::FramedSendStream),
1915 /// Draft-11's subgroup stream.
1916 #[cfg(feature = "draft11")]
1917 Draft11(crate::draft11::connection::FramedSendStream),
1918 /// Draft-12's subgroup stream.
1919 #[cfg(feature = "draft12")]
1920 Draft12(crate::draft12::connection::FramedSendStream),
1921 /// Draft-13's subgroup stream.
1922 #[cfg(feature = "draft13")]
1923 Draft13(crate::draft13::connection::FramedSendStream),
1924 /// Draft-14's subgroup stream.
1925 #[cfg(feature = "draft14")]
1926 Draft14(crate::draft14::connection::FramedSendStream),
1927 /// Draft-15's subgroup stream.
1928 #[cfg(feature = "draft15")]
1929 Draft15(crate::draft15::connection::FramedSendStream),
1930 /// Draft-16's subgroup stream.
1931 #[cfg(feature = "draft16")]
1932 Draft16(crate::draft16::connection::FramedSendStream),
1933 /// Draft-17's subgroup stream.
1934 #[cfg(feature = "draft17")]
1935 Draft17(crate::draft17::connection::FramedSendStream),
1936 /// Draft-18's subgroup stream.
1937 #[cfg(feature = "draft18")]
1938 Draft18(crate::draft18::connection::FramedSendStream),
1939 /// Draft-19's subgroup stream.
1940 #[cfg(feature = "draft19")]
1941 Draft19(crate::draft19::connection::FramedSendStream),
1942 /// Draft-20's subgroup stream.
1943 #[cfg(feature = "draft20")]
1944 Draft20(crate::draft20::connection::FramedSendStream),
1945 /// Draft-21's subgroup stream.
1946 #[cfg(feature = "draft21")]
1947 Draft21(crate::draft21::connection::FramedSendStream),
1948 /// Draft-22's subgroup stream.
1949 #[cfg(feature = "draft22")]
1950 Draft22(crate::draft22::connection::FramedSendStream),
1951 /// Draft-23's subgroup stream.
1952 #[cfg(feature = "draft23")]
1953 Draft23(crate::draft23::connection::FramedSendStream),
1954}
1955
1956/// Expands one body per group of drafts that share a shape, over
1957/// [`AnySubgroupWriter`], [`AnySubgroupReader`] or [`AnyFetchReader`].
1958///
1959/// Every arm is `#[cfg]`-gated on its own draft feature and a catch-all closes
1960/// the match, so a single-draft build compiles with every other draft's arm removed and a
1961/// no-draft build compiles with all of them removed. The same shape
1962/// `moqtap_codec`'s `subgroup_header_accessor!` generates, and for the same
1963/// reason: the alternative is a hand-written arm per draft per method, of which
1964/// at most five differ.
1965///
1966/// Named for data streams rather than for subgroups because a fetch stream is
1967/// the other kind and reads through the same shape. Every variant it is used
1968/// over therefore holds **exactly one** field — which is why
1969/// [`AnyFetchReader::Draft16`] wraps its stream and its resolver in a
1970/// [`Draft16FetchStream`] instead of being a two-field variant.
1971macro_rules! data_stream_dispatch {
1972 (
1973 $enum:ident, $self:expr,
1974 $( [ $( $variant:ident @ $feat:literal ),+ $(,)? ] => |$s:ident, $draft:ident| $body:expr ),+ $(,)?
1975 ) => {
1976 match $self {
1977 $($(
1978 #[cfg(feature = $feat)]
1979 $enum::$variant($s) => {
1980 #[allow(unused_variables)]
1981 let $draft = DraftVersion::$variant;
1982 $body
1983 }
1984 )+)+
1985 #[allow(unreachable_patterns)]
1986 _ => unreachable!("no draft feature is enabled"),
1987 }
1988 };
1989}
1990
1991impl AnySubgroupWriter {
1992 /// The draft framing this stream.
1993 pub fn draft(&self) -> DraftVersion {
1994 data_stream_dispatch! {
1995 AnySubgroupWriter, self,
1996 [
1997 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
1998 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
1999 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2000 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2001 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2002 Draft22 @ "draft22",
2003 Draft23 @ "draft23",
2004 ] => |_s, draft| draft,
2005 }
2006 }
2007
2008 /// Which stream this subgroup is being written on.
2009 ///
2010 /// Bare rather than an `Option`, unlike [`AnyRequest::stream_id`]: a
2011 /// subgroup stream *is* a stream on every draft, so there is no era for the
2012 /// option to describe.
2013 ///
2014 /// The number is the QUIC stream ID (RFC 9000 Section 2.1): its low two
2015 /// bits say who opened the stream and which way it runs.
2016 pub fn stream_id(&self) -> u64 {
2017 data_stream_dispatch! {
2018 AnySubgroupWriter, self,
2019 [
2020 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2021 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2022 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2023 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2024 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2025 Draft22 @ "draft22",
2026 Draft23 @ "draft23",
2027 ] => |s, _draft| s.stream_id(),
2028 }
2029 }
2030
2031 /// Append one object carrying `payload` under `object_id`.
2032 ///
2033 /// # What this fills in, and why none of it is an argument
2034 ///
2035 /// The declared payload length is `payload.len()`, always. Drafts 15 and
2036 /// later store it as a field of the object and refuse an object whose
2037 /// field disagrees with the bytes beside it — the length is already on the
2038 /// wire ahead of the payload, so a disagreement produces a frame no reader
2039 /// can parse and no writer can repair. Drafts 07 through 14 overwrite the
2040 /// field for the same reason. A caller has no way to want a third answer.
2041 ///
2042 /// The status is `Normal` when the payload is empty and absent when it is
2043 /// not, which is the only pair the wire can express: the status field and
2044 /// the payload occupy the same position. An object with bytes and a
2045 /// non-Normal status is asking for two framings at once, and the per-draft
2046 /// writers refuse it. Sending a real status — END_OF_GROUP, END_OF_TRACK —
2047 /// is a different call with different rules about where on the track it may
2048 /// appear, and is reached through the draft's own `Connection` rather than
2049 /// invented here.
2050 ///
2051 /// The extension block is empty, and whether one is written at all was
2052 /// settled by the header [`AnyConnection::open_subgroup`] put on the
2053 /// stream. That is not this method's to change: an object that guessed
2054 /// differently from its header would misframe every object after it.
2055 // `length` is read by every family but draft-14's, which derives it, and
2056 // `empty` by draft-14's and later alone, where the status is an `Option`.
2057 // A single-draft build keeps one arm and so leaves one of them unread.
2058 #[allow(unused_variables)]
2059 pub async fn write_object(
2060 &mut self,
2061 object_id: u64,
2062 payload: &[u8],
2063 ) -> Result<(), AnyConnectionError> {
2064 use moqtap_codec::varint::VarInt;
2065 let id = VarInt::from_u64(object_id)
2066 .map_err(|e| AnyConnectionError::facade(format!("object id {object_id}: {e}")))?;
2067 let length = VarInt::from_usize(payload.len());
2068 let empty = payload.is_empty();
2069 // Five shapes across the drafts, and each one is written once
2070 // as a macro taking the draft's module. A macro rather than a shared
2071 // arm because the stream in hand is a different concrete type in every
2072 // variant: `FramedSendStream` names one struct per draft, not one, so a
2073 // body written once and matched against several variants type-checks
2074 // against none of them.
2075 #[allow(unused_macros)]
2076 macro_rules! bare {
2077 // Draft-07 is the one draft with no extension block anywhere, so
2078 // its object header has no field for one.
2079 ($s:ident, $m:ident) => {{
2080 let object = crate::$m::event::SubgroupObject {
2081 header: moqtap_codec::$m::data_stream::ObjectHeader {
2082 object_id: id,
2083 payload_length: length,
2084 object_status: moqtap_codec::$m::types::ObjectStatus::Normal,
2085 },
2086 payload: payload.to_vec(),
2087 };
2088 $s.write_subgroup_object(&object).await.map_err(AnyConnectionError::from)
2089 }};
2090 }
2091 #[allow(unused_macros)]
2092 macro_rules! counted {
2093 // Draft-08 counts its extensions rather than measuring them.
2094 ($s:ident, $m:ident) => {{
2095 let object = crate::$m::event::SubgroupObject {
2096 header: moqtap_codec::$m::data_stream::ObjectHeader {
2097 object_id: id,
2098 extension_count: VarInt::from_usize(0),
2099 extensions: Vec::new(),
2100 payload_length: length,
2101 object_status: moqtap_codec::$m::types::ObjectStatus::Normal,
2102 },
2103 payload: payload.to_vec(),
2104 };
2105 $s.write_subgroup_object(&object).await.map_err(AnyConnectionError::from)
2106 }};
2107 }
2108 #[allow(unused_macros)]
2109 macro_rules! measured {
2110 // Draft-09 changed that count to a byte length, and every draft
2111 // through 13 kept it that way.
2112 ($s:ident, $m:ident) => {{
2113 let object = crate::$m::event::SubgroupObject {
2114 header: moqtap_codec::$m::data_stream::ObjectHeader {
2115 object_id: id,
2116 extension_headers_length: VarInt::from_usize(0),
2117 extensions: Vec::new(),
2118 payload_length: length,
2119 object_status: moqtap_codec::$m::types::ObjectStatus::Normal,
2120 },
2121 payload: payload.to_vec(),
2122 };
2123 $s.write_subgroup_object(&object).await.map_err(AnyConnectionError::from)
2124 }};
2125 }
2126 #[allow(unused_macros)]
2127 macro_rules! derived {
2128 // Draft-14 takes the declared length from the payload and so has no
2129 // field for it, and is the one draft to name the status field
2130 // `status` rather than `object_status`.
2131 ($s:ident, $m:ident) => {{
2132 let object = moqtap_codec::$m::data_stream::SubgroupObject {
2133 object_id: id,
2134 extension_headers: Vec::new(),
2135 status: empty.then_some(moqtap_codec::$m::types::ObjectStatus::Normal),
2136 payload: payload.to_vec(),
2137 };
2138 $s.write_subgroup_object(&object).await.map_err(AnyConnectionError::from)
2139 }};
2140 }
2141 #[allow(unused_macros)]
2142 macro_rules! declared {
2143 // Drafts 15 and later put the length back and refuse an object
2144 // whose field disagrees with the bytes beside it.
2145 ($s:ident, $m:ident) => {{
2146 let object = moqtap_codec::$m::data_stream::SubgroupObject {
2147 object_id: id,
2148 extension_headers: Vec::new(),
2149 payload_length: length,
2150 object_status: empty.then_some(moqtap_codec::$m::types::ObjectStatus::Normal),
2151 payload: payload.to_vec(),
2152 };
2153 $s.write_subgroup_object(&object).await.map_err(AnyConnectionError::from)
2154 }};
2155 }
2156
2157 match self {
2158 #[cfg(feature = "draft07")]
2159 Self::Draft07(s) => bare!(s, draft07),
2160 #[cfg(feature = "draft08")]
2161 Self::Draft08(s) => counted!(s, draft08),
2162 #[cfg(feature = "draft09")]
2163 Self::Draft09(s) => measured!(s, draft09),
2164 #[cfg(feature = "draft10")]
2165 Self::Draft10(s) => measured!(s, draft10),
2166 #[cfg(feature = "draft11")]
2167 Self::Draft11(s) => measured!(s, draft11),
2168 #[cfg(feature = "draft12")]
2169 Self::Draft12(s) => measured!(s, draft12),
2170 #[cfg(feature = "draft13")]
2171 Self::Draft13(s) => measured!(s, draft13),
2172 #[cfg(feature = "draft14")]
2173 Self::Draft14(s) => derived!(s, draft14),
2174 #[cfg(feature = "draft15")]
2175 Self::Draft15(s) => declared!(s, draft15),
2176 #[cfg(feature = "draft16")]
2177 Self::Draft16(s) => declared!(s, draft16),
2178 #[cfg(feature = "draft17")]
2179 Self::Draft17(s) => declared!(s, draft17),
2180 #[cfg(feature = "draft18")]
2181 Self::Draft18(s) => declared!(s, draft18),
2182 #[cfg(feature = "draft19")]
2183 Self::Draft19(s) => declared!(s, draft19),
2184 #[cfg(feature = "draft20")]
2185 Self::Draft20(s) => declared!(s, draft20),
2186 #[cfg(feature = "draft21")]
2187 Self::Draft21(s) => declared!(s, draft21),
2188 #[cfg(feature = "draft22")]
2189 Self::Draft22(s) => declared!(s, draft22),
2190 #[cfg(feature = "draft23")]
2191 Self::Draft23(s) => declared!(s, draft23),
2192 #[allow(unreachable_patterns)]
2193 _ => Err(AnyConnectionError::facade("no draft feature is enabled")),
2194 }
2195 }
2196
2197 /// Close the stream, which ends the subgroup.
2198 ///
2199 /// A subgroup has no terminator message on any draft: the stream ending
2200 /// *is* the end of it. So this is not a courtesy — a reader on the far side
2201 /// is waiting for either another object or the end, and cannot tell which
2202 /// is coming until one of them arrives.
2203 pub async fn finish(&mut self) -> Result<(), AnyConnectionError> {
2204 data_stream_dispatch! {
2205 AnySubgroupWriter, self,
2206 [
2207 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2208 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2209 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2210 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2211 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2212 Draft22 @ "draft22",
2213 Draft23 @ "draft23",
2214 ] => |s, _draft| {
2215 s.finish().await.map_err(AnyConnectionError::from)
2216 },
2217 }
2218 }
2219}
2220
2221/// A subgroup stream the **peer** opened, that this endpoint is reading.
2222///
2223/// The mirror of [`AnySubgroupWriter`], returned by
2224/// [`AnyConnection::accept_subgroup`] beside the header the peer framed it
2225/// with.
2226#[allow(clippy::large_enum_variant)]
2227pub enum AnySubgroupReader {
2228 /// Draft-07's subgroup stream.
2229 #[cfg(feature = "draft07")]
2230 Draft07(crate::draft07::connection::FramedRecvStream),
2231 /// Draft-08's subgroup stream.
2232 #[cfg(feature = "draft08")]
2233 Draft08(crate::draft08::connection::FramedRecvStream),
2234 /// Draft-09's subgroup stream.
2235 #[cfg(feature = "draft09")]
2236 Draft09(crate::draft09::connection::FramedRecvStream),
2237 /// Draft-10's subgroup stream.
2238 #[cfg(feature = "draft10")]
2239 Draft10(crate::draft10::connection::FramedRecvStream),
2240 /// Draft-11's subgroup stream.
2241 #[cfg(feature = "draft11")]
2242 Draft11(crate::draft11::connection::FramedRecvStream),
2243 /// Draft-12's subgroup stream.
2244 #[cfg(feature = "draft12")]
2245 Draft12(crate::draft12::connection::FramedRecvStream),
2246 /// Draft-13's subgroup stream.
2247 #[cfg(feature = "draft13")]
2248 Draft13(crate::draft13::connection::FramedRecvStream),
2249 /// Draft-14's subgroup stream.
2250 #[cfg(feature = "draft14")]
2251 Draft14(crate::draft14::connection::FramedRecvStream),
2252 /// Draft-15's subgroup stream.
2253 #[cfg(feature = "draft15")]
2254 Draft15(crate::draft15::connection::FramedRecvStream),
2255 /// Draft-16's subgroup stream.
2256 #[cfg(feature = "draft16")]
2257 Draft16(crate::draft16::connection::FramedRecvStream),
2258 /// Draft-17's subgroup stream.
2259 #[cfg(feature = "draft17")]
2260 Draft17(crate::draft17::connection::FramedRecvStream),
2261 /// Draft-18's subgroup stream.
2262 #[cfg(feature = "draft18")]
2263 Draft18(crate::draft18::connection::FramedRecvStream),
2264 /// Draft-19's subgroup stream.
2265 #[cfg(feature = "draft19")]
2266 Draft19(crate::draft19::connection::FramedRecvStream),
2267 /// Draft-20's subgroup stream.
2268 #[cfg(feature = "draft20")]
2269 Draft20(crate::draft20::connection::FramedRecvStream),
2270 /// Draft-21's subgroup stream.
2271 #[cfg(feature = "draft21")]
2272 Draft21(crate::draft21::connection::FramedRecvStream),
2273 /// Draft-22's subgroup stream.
2274 #[cfg(feature = "draft22")]
2275 Draft22(crate::draft22::connection::FramedRecvStream),
2276 /// Draft-23's subgroup stream.
2277 #[cfg(feature = "draft23")]
2278 Draft23(crate::draft23::connection::FramedRecvStream),
2279}
2280
2281impl AnySubgroupReader {
2282 /// The draft framing this stream.
2283 pub fn draft(&self) -> DraftVersion {
2284 data_stream_dispatch! {
2285 AnySubgroupReader, self,
2286 [
2287 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2288 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2289 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2290 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2291 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2292 Draft22 @ "draft22",
2293 Draft23 @ "draft23",
2294 ] => |_s, draft| draft,
2295 }
2296 }
2297
2298 /// Which stream this subgroup arrived on, on the terms
2299 /// [`AnySubgroupWriter::stream_id`] describes.
2300 pub fn stream_id(&self) -> u64 {
2301 data_stream_dispatch! {
2302 AnySubgroupReader, self,
2303 [
2304 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2305 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2306 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2307 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2308 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2309 Draft22 @ "draft22",
2310 Draft23 @ "draft23",
2311 ] => |s, _draft| s.stream_id(),
2312 }
2313 }
2314
2315 /// Read the next object on this subgroup.
2316 ///
2317 /// # The stream's end arrives as an error, not as `None`
2318 ///
2319 /// A subgroup ends when its stream ends. Most per-draft readers cannot
2320 /// tell that end from a truncation: both leave the reader wanting bytes
2321 /// that never come, and both surface as `ConnectionError::UnexpectedEnd`.
2322 /// Returning `Option` here would have to invent the distinction, and
2323 /// inventing it means reporting a stream the peer cut off mid-object as a
2324 /// subgroup that finished — which is exactly the case a caller most needs
2325 /// to know about. A reader that can tell them apart reports the truncation
2326 /// as an [`ErrorCause::PeerViolation`].
2327 ///
2328 /// So a caller that expects a known number of objects reads that many, and
2329 /// one that does not treats the error as the end and keeps whatever it read
2330 /// before it. Neither has to guess.
2331 pub async fn read_object(&mut self) -> Result<AnyObject, AnyConnectionError> {
2332 data_stream_dispatch! {
2333 AnySubgroupReader, self,
2334 // Drafts 07 through 13 model the status as a field that is always
2335 // present. The wire does not: it carries one only when the declared
2336 // length is zero, and the per-draft decoder fills in `Normal` for
2337 // every object it read a payload for. Reporting that filled-in value
2338 // as though the peer had sent it would make `AnyObject::status` mean
2339 // something different on these seven drafts than on the other seven.
2340 [
2341 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2342 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2343 Draft13 @ "draft13",
2344 ] => |s, _draft| {
2345 let object = s.read_subgroup_object().await
2346 .map_err(AnyConnectionError::from)?;
2347 Ok(AnyObject {
2348 object_id: object.header.object_id.into_inner(),
2349 status: object
2350 .payload
2351 .is_empty()
2352 .then_some(object.header.object_status as u64),
2353 payload: object.payload,
2354 })
2355 },
2356 [Draft14 @ "draft14"] => |s, _draft| {
2357 let object = s.read_subgroup_object().await
2358 .map_err(AnyConnectionError::from)?;
2359 Ok(AnyObject {
2360 object_id: object.object_id.into_inner(),
2361 status: object.status.map(|s| s as u64),
2362 payload: object.payload,
2363 })
2364 },
2365 [
2366 Draft15 @ "draft15", Draft16 @ "draft16", Draft17 @ "draft17",
2367 Draft18 @ "draft18", Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2368 Draft22 @ "draft22",
2369 Draft23 @ "draft23",
2370 ] => |s, _draft| {
2371 let object = s.read_subgroup_object().await
2372 .map_err(AnyConnectionError::from)?;
2373 Ok(AnyObject {
2374 object_id: object.object_id.into_inner(),
2375 status: object.object_status.map(|s| s as u64),
2376 payload: object.payload,
2377 })
2378 },
2379 }
2380 }
2381}
2382
2383/// One object on a **fetch** stream, in the terms every draft shares.
2384///
2385/// [`AnyObject`]'s twin, and it needs three fields that one does not, because a
2386/// fetch stream is not a subgroup stream with a different header on it. A
2387/// subgroup stream's header names the Group and the Subgroup once and every
2388/// object on it belongs to them; a fetch stream carries a whole range, so each
2389/// object states its own Location — and from draft-15 it states it by
2390/// *inheritance*, leaving fields off the wire that the object before it
2391/// supplies. Every value here is resolved: what a caller gets is where the
2392/// object is, never what the wire happened to write down.
2393#[derive(Debug, Clone, PartialEq, Eq)]
2394pub struct AnyFetchObject {
2395 /// The Group this object belongs to, resolved.
2396 pub group_id: u64,
2397 /// The Subgroup, resolved, and `None` where the draft lets an object on a
2398 /// fetch stream have none.
2399 ///
2400 /// Drafts 07 through 15 always carry it. From draft-16 an object whose
2401 /// Forwarding Preference is Datagram omits it — draft-16 Section 10.2.1 —
2402 /// and so does an end-of-range marker, which is a span rather than an
2403 /// object.
2404 pub subgroup_id: Option<u64>,
2405 /// The Object ID, resolved. Drafts 15 and later may encode it as a step
2406 /// from the object before it on the stream, and the reader has undone that.
2407 pub object_id: u64,
2408 /// The payload. Empty when `status` is `Some`, and empty on every
2409 /// end-of-range marker.
2410 pub payload: Vec<u8>,
2411 /// The Object Status wire code, on the same terms as [`AnyObject::status`]:
2412 /// `Some` exactly when the object carried no payload, because the two
2413 /// occupy the same position on the wire.
2414 ///
2415 /// **Always `None` from draft-16 on**, and that is the drafts talking
2416 /// rather than this type giving up: draft-16 deleted the Object Status
2417 /// field from a fetch object and put [`Self::end_of_range`] in its place.
2418 /// The pair covers one question between them — *is this an object, or a
2419 /// statement about objects that are not here* — and which of the two answers
2420 /// it depends on the draft.
2421 pub status: Option<u64>,
2422 /// The end-of-range marker's own wire code, where this "object" is one.
2423 ///
2424 /// Drafts 16 through 23 end a fetch that cannot serve part of its range
2425 /// with a marker rather than with silence, and the marker covers the whole
2426 /// span from the last serialized object to this Location inclusive:
2427 /// `0x8C` for a span known not to exist, `0x10C` for one whose status is
2428 /// unknown, `0x20C` (drafts 20 through 23) for one the publisher abandoned,
2429 /// and `0x40C` (draft-23) for one that is Gone.
2430 ///
2431 /// Draft-23 also has `0x80` (End of Group) and `0x81` (End of Track),
2432 /// which cover no span: their Location is the prior Object's, which
2433 /// draft-23 Section 11.4.2.2 makes "the last Object in its Group, or in
2434 /// the Track, respectively".
2435 ///
2436 /// A number rather than an enum for the reason [`crate::dispatch`]'s
2437 /// neighbours are: the code is the fingerprint, and a later draft
2438 /// assigning a fourth marker should widen a report rather than fail to
2439 /// parse. `None` on drafts 07 through 15, which have no such marker — there
2440 /// the same news arrives, when it arrives at all, as a [`Self::status`].
2441 pub end_of_range: Option<u64>,
2442}
2443
2444/// Draft-16's fetch stream, carried with the resolver its objects need.
2445///
2446/// The one variant of [`AnyFetchReader`] that is not a bare stream, and the
2447/// reason is a gap one draft wide. All but one of the per-draft
2448/// `FramedRecvStream`s resolve a fetch object's elided fields themselves —
2449/// drafts 07 through 14 have nothing to resolve, and 15, 17, 18, 19 and 20 each
2450/// hold a `FetchObjectReader` on the stream. Draft-16's does not, though
2451/// `moqtap_codec::draft16::data_stream::FetchObjectReader` exists and does
2452/// exactly the job.
2453///
2454/// So the state lives here. It has to live *somewhere* per stream: a draft-16
2455/// object may leave out its Group ID, its Subgroup ID, its Object ID and its
2456/// Priority, and Table 5 gives each absent field a meaning drawn from the
2457/// object before it. A reader without the object before it does not get a worse
2458/// answer, it gets no answer at all.
2459#[cfg(feature = "draft16")]
2460pub struct Draft16FetchStream {
2461 stream: crate::draft16::connection::FramedRecvStream,
2462 reader: moqtap_codec::draft16::data_stream::FetchObjectReader,
2463}
2464
2465#[cfg(feature = "draft16")]
2466impl Draft16FetchStream {
2467 /// Which stream this fetch arrived on, on the terms
2468 /// [`AnySubgroupWriter::stream_id`] describes.
2469 pub fn stream_id(&self) -> u64 {
2470 self.stream.stream_id()
2471 }
2472}
2473
2474/// The Group Order a FETCH_OK named, for handing to
2475/// [`AnyConnection::accept_fetch`].
2476///
2477/// # Why this is a function and not a field lookup at the call site
2478///
2479/// Because the field moved twice and the answer is silent when it is wrong.
2480/// Drafts 07 through 14 put Group Order on FETCH_OK outright. Drafts 15 and 16
2481/// deleted it from the message. Drafts 17 through 20 brought it back as
2482/// **Track Property `0x22`**, `DEFAULT PUBLISHER GROUP ORDER`, which is
2483/// optional — so on the three drafts where the value decides how every Group ID
2484/// after the first is resolved, the commonest FETCH_OK does not carry it at all.
2485///
2486/// [`moqtap_codec::types::GroupOrder::Ascending`] is the answer in that case, and
2487/// it is the draft's answer rather than this function's: draft-20 Section 10.2.8
2488/// makes Ascending the default for an omitted property.
2489///
2490/// A message that is not a FETCH_OK answers `Ascending` too, and that is not a
2491/// claim about it — nothing else here has a Group Order to report, and a caller
2492/// holding the wrong message has a different problem than this can name.
2493///
2494/// Written against [`moqtap_codec::dispatch::AnyControlMessage::fields`] rather
2495/// than as an arm per draft, so a draft that moves the field again is one entry
2496/// here rather than a match that still compiles.
2497pub fn fetch_group_order(
2498 message: &moqtap_codec::dispatch::AnyControlMessage,
2499) -> moqtap_codec::types::GroupOrder {
2500 use moqtap_codec::fields::FieldValue;
2501 use moqtap_codec::types::GroupOrder;
2502
2503 let named = |code: u64| match code {
2504 0x2 => GroupOrder::Descending,
2505 0x1 => GroupOrder::Ascending,
2506 // Including `0x0`, Publisher — which says the publisher decides and so
2507 // states no direction. Every consumer of this value needs one.
2508 _ => GroupOrder::Ascending,
2509 };
2510
2511 let fields = message.fields();
2512 // Drafts 07 through 14, where it is a field of the message.
2513 if let Some(FieldValue::Uint(code)) = fields.get("group_order") {
2514 return named(*code);
2515 }
2516 // Drafts 17 through 20, where it is one entry of a property list that need
2517 // not contain it.
2518 if let Some(FieldValue::Array(properties)) = fields.get("track_properties") {
2519 for property in properties {
2520 let FieldValue::Map(entry) = property else { continue };
2521 if entry.get("name") != Some(&FieldValue::Text("default_publisher_group_order".into()))
2522 {
2523 continue;
2524 }
2525 if let Some(FieldValue::Uint(code)) = entry.get("value") {
2526 return named(*code);
2527 }
2528 }
2529 }
2530 GroupOrder::Ascending
2531}
2532
2533/// A fetch stream the **peer** opened, that this endpoint is reading.
2534///
2535/// [`AnySubgroupReader`]'s twin, returned by [`AnyConnection::accept_fetch`]
2536/// beside the FETCH_HEADER the peer framed it with.
2537///
2538/// The two are separate types for the reason the per-draft connections keep
2539/// `accept_fetch_stream` and `accept_subgroup_stream` separate: the header
2540/// decides how every object after it is framed, so a caller has to know which
2541/// kind it is expecting before the first byte is read. Nothing here can be
2542/// handed a subgroup stream and cope.
2543#[allow(clippy::large_enum_variant)]
2544pub enum AnyFetchReader {
2545 /// Draft-07's fetch stream.
2546 #[cfg(feature = "draft07")]
2547 Draft07(crate::draft07::connection::FramedRecvStream),
2548 /// Draft-08's fetch stream.
2549 #[cfg(feature = "draft08")]
2550 Draft08(crate::draft08::connection::FramedRecvStream),
2551 /// Draft-09's fetch stream.
2552 #[cfg(feature = "draft09")]
2553 Draft09(crate::draft09::connection::FramedRecvStream),
2554 /// Draft-10's fetch stream.
2555 #[cfg(feature = "draft10")]
2556 Draft10(crate::draft10::connection::FramedRecvStream),
2557 /// Draft-11's fetch stream.
2558 #[cfg(feature = "draft11")]
2559 Draft11(crate::draft11::connection::FramedRecvStream),
2560 /// Draft-12's fetch stream.
2561 #[cfg(feature = "draft12")]
2562 Draft12(crate::draft12::connection::FramedRecvStream),
2563 /// Draft-13's fetch stream.
2564 #[cfg(feature = "draft13")]
2565 Draft13(crate::draft13::connection::FramedRecvStream),
2566 /// Draft-14's fetch stream.
2567 #[cfg(feature = "draft14")]
2568 Draft14(crate::draft14::connection::FramedRecvStream),
2569 /// Draft-15's fetch stream.
2570 #[cfg(feature = "draft15")]
2571 Draft15(crate::draft15::connection::FramedRecvStream),
2572 /// Draft-16's fetch stream, and the resolver its objects need. See
2573 /// [`Draft16FetchStream`].
2574 #[cfg(feature = "draft16")]
2575 Draft16(Draft16FetchStream),
2576 /// Draft-17's fetch stream.
2577 #[cfg(feature = "draft17")]
2578 Draft17(crate::draft17::connection::FramedRecvStream),
2579 /// Draft-18's fetch stream.
2580 #[cfg(feature = "draft18")]
2581 Draft18(crate::draft18::connection::FramedRecvStream),
2582 /// Draft-19's fetch stream.
2583 #[cfg(feature = "draft19")]
2584 Draft19(crate::draft19::connection::FramedRecvStream),
2585 /// Draft-20's fetch stream.
2586 #[cfg(feature = "draft20")]
2587 Draft20(crate::draft20::connection::FramedRecvStream),
2588 /// Draft-21's fetch stream.
2589 #[cfg(feature = "draft21")]
2590 Draft21(crate::draft21::connection::FramedRecvStream),
2591 /// Draft-22's fetch stream.
2592 #[cfg(feature = "draft22")]
2593 Draft22(crate::draft22::connection::FramedRecvStream),
2594 /// Draft-23's fetch stream.
2595 #[cfg(feature = "draft23")]
2596 Draft23(crate::draft23::connection::FramedRecvStream),
2597}
2598
2599impl AnyFetchReader {
2600 /// The draft framing this stream.
2601 pub fn draft(&self) -> DraftVersion {
2602 data_stream_dispatch! {
2603 AnyFetchReader, self,
2604 [
2605 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2606 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2607 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2608 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2609 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2610 Draft22 @ "draft22",
2611 Draft23 @ "draft23",
2612 ] => |_s, draft| draft,
2613 }
2614 }
2615
2616 /// Which stream this fetch arrived on, on the terms
2617 /// [`AnySubgroupWriter::stream_id`] describes.
2618 pub fn stream_id(&self) -> u64 {
2619 data_stream_dispatch! {
2620 AnyFetchReader, self,
2621 [
2622 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2623 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2624 Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
2625 Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
2626 Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2627 Draft22 @ "draft22",
2628 Draft23 @ "draft23",
2629 ] => |s, _draft| s.stream_id(),
2630 }
2631 }
2632
2633 /// Read the next object on this fetch stream.
2634 ///
2635 /// # The stream's end arrives as an error, not as `None`
2636 ///
2637 /// For the reason [`AnySubgroupReader::read_object`] gives, and one more of
2638 /// its own: a fetch stream ends when the range it was serving runs out, and
2639 /// the only thing that says so is the FIN. Returning `Option` would have to
2640 /// tell that FIN from a peer that stopped mid-object, which no per-draft
2641 /// reader can.
2642 ///
2643 /// # Errors
2644 ///
2645 /// Everything [`AnySubgroupReader::read_object`] can raise, and on drafts 15
2646 /// through 20 one more class of its own: an object that inherits from an
2647 /// object that does not exist. The first object of a stream may not leave
2648 /// out its Group ID or its Object ID, and a stream whose first object does
2649 /// is refused here rather than resolved against zero — draft-16 Section
2650 /// 10.4.4.1 makes it a PROTOCOL_VIOLATION, and there is no value to produce
2651 /// even where it does not.
2652 ///
2653 /// On drafts 11-23, the Prior gap conditions the stream shows, refused with
2654 /// `CodecError::MalformedTrack` as each draft's `FetchGaps` refuses them,
2655 /// and a property block that does not parse, refused with
2656 /// `CodecError::MalformedObjectProperties`, which drafts 14-23 make a
2657 /// Malformed Track.
2658 ///
2659 /// This reader does not withdraw from the track over either. The caller
2660 /// answers them with the draft's own `Connection::withdraw_for_fetch_stream`
2661 /// (drafts 12-16) or `Connection::requests_to_cancel_for_fetch_stream`
2662 /// (drafts 17-23), passing `ConnectionError::Codec` of the error that
2663 /// [`ErrorCause::Codec`] carries.
2664 pub async fn read_object(&mut self) -> Result<AnyFetchObject, AnyConnectionError> {
2665 data_stream_dispatch! {
2666 AnyFetchReader, self,
2667 // Drafts 07 through 13 model the status as a field that is always
2668 // present, exactly as their subgroup objects do, and the same rule
2669 // applies for the same reason: the wire carries a status only where
2670 // the declared length is zero, so a filled-in `Normal` beside a
2671 // payload is the decoder talking and not the peer.
2672 [
2673 Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
2674 Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
2675 Draft13 @ "draft13",
2676 ] => |s, _draft| {
2677 let object = s.read_fetch_object().await
2678 .map_err(AnyConnectionError::from)?;
2679 Ok(AnyFetchObject {
2680 group_id: object.header.group_id.into_inner(),
2681 subgroup_id: Some(object.header.subgroup_id.into_inner()),
2682 object_id: object.header.object_id.into_inner(),
2683 status: object
2684 .payload
2685 .is_empty()
2686 .then_some(object.header.object_status as u64),
2687 payload: object.payload,
2688 end_of_range: None,
2689 })
2690 },
2691 [Draft14 @ "draft14"] => |s, _draft| {
2692 let object = s.read_fetch_object().await
2693 .map_err(AnyConnectionError::from)?;
2694 Ok(AnyFetchObject {
2695 group_id: object.group_id.into_inner(),
2696 subgroup_id: Some(object.subgroup_id.into_inner()),
2697 object_id: object.object_id.into_inner(),
2698 status: object.status.map(|s| s as u64),
2699 payload: object.payload,
2700 end_of_range: None,
2701 })
2702 },
2703 // Draft-15 elides fields too, and its own reader resolves them, so
2704 // what comes back is a header whose every field is a value.
2705 [Draft15 @ "draft15"] => |s, _draft| {
2706 let (header, payload) = s.read_fetch_object().await
2707 .map_err(AnyConnectionError::from)?;
2708 Ok(AnyFetchObject {
2709 group_id: header.group_id.into_inner(),
2710 subgroup_id: Some(header.subgroup_id.into_inner()),
2711 object_id: header.object_id.into_inner(),
2712 status: header.object_status.map(|s| s as u64),
2713 payload,
2714 end_of_range: None,
2715 })
2716 },
2717 // The one arm that resolves rather than reads a resolution. See
2718 // `Draft16FetchStream` for why the state is here and not on the
2719 // stream.
2720 [Draft16 @ "draft16"] => |s, _draft| {
2721 let (header, payload) = s.stream.read_fetch_object().await
2722 .map_err(AnyConnectionError::from)?;
2723 // A Malformed Track is the peer's, and reaches the caller as
2724 // the codec error the draft's withdrawal reads.
2725 let at = s.reader.resolve(&header).map_err(|e| match e {
2726 moqtap_codec::error::CodecError::MalformedTrack { .. }
2727 | moqtap_codec::error::CodecError::MalformedObjectProperties { .. } => {
2728 AnyConnectionError::from(
2729 crate::draft16::connection::ConnectionError::Codec(e),
2730 )
2731 }
2732 e => AnyConnectionError::facade(e.to_string()),
2733 })?;
2734 Ok(AnyFetchObject {
2735 group_id: at.group_id,
2736 subgroup_id: at.subgroup_id,
2737 object_id: at.object_id,
2738 status: None,
2739 payload,
2740 end_of_range: at.end_of_range.map(|marker| marker.as_u64()),
2741 })
2742 },
2743 // Drafts 17 through 20 hand back the resolved Location beside the
2744 // header the wire carried, and the marker is read off the flags
2745 // rather than off the enum: three drafts name two markers and
2746 // draft-20 names three, so a shared body naming them would not
2747 // compile on all four.
2748 [
2749 Draft17 @ "draft17", Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
2750 Draft22 @ "draft22",
2751 ] => |s, _draft| {
2752 let (object, payload) = s.read_fetch_object().await
2753 .map_err(AnyConnectionError::from)?;
2754 let flags = object.header.serialization_flags.into_inner();
2755 Ok(AnyFetchObject {
2756 group_id: object.group_id,
2757 subgroup_id: object.subgroup_id,
2758 object_id: object.object_id,
2759 status: None,
2760 payload,
2761 end_of_range: object.header.end_of_range().map(|_| flags),
2762 })
2763 },
2764 // draft-23 Section 11.4.2.2: "When Serialization Flags is 0x80 (End
2765 // of Group) or 0x81 (End of Track), no other fields are present.
2766 // The prior Object is the last Object in its Group, or in the
2767 // Track, respectively." Those two are indicators as well as the
2768 // End of Range ones, so `is_indicator` rather than `end_of_range`.
2769 [Draft23 @ "draft23"] => |s, _draft| {
2770 let (object, payload) = s.read_fetch_object().await
2771 .map_err(AnyConnectionError::from)?;
2772 let flags = object.header.serialization_flags.into_inner();
2773 Ok(AnyFetchObject {
2774 group_id: object.group_id,
2775 subgroup_id: object.subgroup_id,
2776 object_id: object.object_id,
2777 status: None,
2778 payload,
2779 end_of_range: object.header.is_indicator().then_some(flags),
2780 })
2781 },
2782 // Identical but for the flags field's type, which draft-18 alone
2783 // holds as a bare `u64`.
2784 [Draft18 @ "draft18"] => |s, _draft| {
2785 let (object, payload) = s.read_fetch_object().await
2786 .map_err(AnyConnectionError::from)?;
2787 let flags = object.header.serialization_flags;
2788 Ok(AnyFetchObject {
2789 group_id: object.group_id,
2790 subgroup_id: object.subgroup_id,
2791 object_id: object.object_id,
2792 status: None,
2793 payload,
2794 end_of_range: object.header.end_of_range().map(|_| flags),
2795 })
2796 },
2797 }
2798 }
2799}
2800
2801/// `forward` as the FORWARD parameter, key 0x10, which is where drafts 15
2802/// through 22 carry the field drafts 12 through 14 write inline in a PUBLISH.
2803/// The codec owns the value's encoding on each draft, so this hands it over as
2804/// a plain varint.
2805#[cfg(any(
2806 feature = "draft15",
2807 feature = "draft16",
2808 feature = "draft17",
2809 feature = "draft18",
2810 feature = "draft19",
2811 feature = "draft20",
2812 feature = "draft21",
2813 feature = "draft22",
2814))]
2815fn forward_parameter(forward: moqtap_codec::types::Forward) -> Vec<KeyValuePair> {
2816 use moqtap_codec::kvp::KvpValue;
2817 use moqtap_codec::varint::VarInt;
2818 vec![KeyValuePair {
2819 key: VarInt::from_u64(0x10).expect("0x10 fits in VarInt"),
2820 value: KvpValue::Varint(VarInt::from_u64(forward as u64).expect("0 or 1 fits in VarInt")),
2821 }]
2822}
2823
2824impl AnyConnection {
2825 /// Connect to a MoQT server using the requested draft. Builds the
2826 /// draft-specific `ClientConfig` from the provided [`AnyClientConfig`]
2827 /// and dispatches to the appropriate `Connection::connect`.
2828 pub async fn connect(addr: &str, config: AnyClientConfig) -> Result<Self, AnyConnectionError> {
2829 // Every arm of the match below is `#[cfg(feature = "draftNN")]`. A build
2830 // with no draft feature enabled keeps only the catch-all, which never
2831 // dials anything, so `addr` is genuinely unread in exactly that build.
2832 #[cfg(not(any(
2833 feature = "draft07",
2834 feature = "draft08",
2835 feature = "draft09",
2836 feature = "draft10",
2837 feature = "draft11",
2838 feature = "draft12",
2839 feature = "draft13",
2840 feature = "draft14",
2841 feature = "draft15",
2842 feature = "draft16",
2843 feature = "draft17",
2844 feature = "draft18",
2845 feature = "draft19",
2846 feature = "draft20",
2847 feature = "draft21",
2848 feature = "draft22",
2849 feature = "draft23"
2850 )))]
2851 let _ = addr;
2852 match config.draft {
2853 #[cfg(feature = "draft07")]
2854 DraftVersion::Draft07 => {
2855 use crate::draft07::connection::{ClientConfig, Connection, TransportType};
2856 let transport = match config.transport {
2857 AnyTransportType::Quic => TransportType::Quic,
2858 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2859 };
2860 let inner = ClientConfig {
2861 additional_versions: config.additional_versions,
2862 transport,
2863 skip_cert_verification: config.skip_cert_verification,
2864 ca_certs: config.ca_certs,
2865 setup_parameters: config.setup_parameters,
2866 };
2867 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2868 Ok(AnyConnection::Draft07(c))
2869 }
2870 #[cfg(feature = "draft08")]
2871 DraftVersion::Draft08 => {
2872 use crate::draft08::connection::{ClientConfig, Connection, TransportType};
2873 let transport = match config.transport {
2874 AnyTransportType::Quic => TransportType::Quic,
2875 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2876 };
2877 let inner = ClientConfig {
2878 additional_versions: config.additional_versions,
2879 transport,
2880 skip_cert_verification: config.skip_cert_verification,
2881 ca_certs: config.ca_certs,
2882 setup_parameters: config.setup_parameters,
2883 };
2884 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2885 Ok(AnyConnection::Draft08(c))
2886 }
2887 #[cfg(feature = "draft09")]
2888 DraftVersion::Draft09 => {
2889 use crate::draft09::connection::{ClientConfig, Connection, TransportType};
2890 let transport = match config.transport {
2891 AnyTransportType::Quic => TransportType::Quic,
2892 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2893 };
2894 let inner = ClientConfig {
2895 additional_versions: config.additional_versions,
2896 transport,
2897 skip_cert_verification: config.skip_cert_verification,
2898 ca_certs: config.ca_certs,
2899 setup_parameters: config.setup_parameters,
2900 };
2901 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2902 Ok(AnyConnection::Draft09(c))
2903 }
2904 #[cfg(feature = "draft10")]
2905 DraftVersion::Draft10 => {
2906 use crate::draft10::connection::{ClientConfig, Connection, TransportType};
2907 let transport = match config.transport {
2908 AnyTransportType::Quic => TransportType::Quic,
2909 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2910 };
2911 let inner = ClientConfig {
2912 additional_versions: config.additional_versions,
2913 transport,
2914 skip_cert_verification: config.skip_cert_verification,
2915 ca_certs: config.ca_certs,
2916 setup_parameters: config.setup_parameters,
2917 };
2918 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2919 Ok(AnyConnection::Draft10(c))
2920 }
2921 #[cfg(feature = "draft11")]
2922 DraftVersion::Draft11 => {
2923 use crate::draft11::connection::{ClientConfig, Connection, TransportType};
2924 let transport = match config.transport {
2925 AnyTransportType::Quic => TransportType::Quic,
2926 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2927 };
2928 let inner = ClientConfig {
2929 additional_versions: config.additional_versions,
2930 transport,
2931 skip_cert_verification: config.skip_cert_verification,
2932 ca_certs: config.ca_certs,
2933 setup_parameters: config.setup_parameters,
2934 };
2935 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2936 Ok(AnyConnection::Draft11(c))
2937 }
2938 #[cfg(feature = "draft12")]
2939 DraftVersion::Draft12 => {
2940 use crate::draft12::connection::{ClientConfig, Connection, TransportType};
2941 let transport = match config.transport {
2942 AnyTransportType::Quic => TransportType::Quic,
2943 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2944 };
2945 let inner = ClientConfig {
2946 additional_versions: config.additional_versions,
2947 transport,
2948 skip_cert_verification: config.skip_cert_verification,
2949 ca_certs: config.ca_certs,
2950 setup_parameters: config.setup_parameters,
2951 };
2952 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2953 Ok(AnyConnection::Draft12(c))
2954 }
2955 #[cfg(feature = "draft13")]
2956 DraftVersion::Draft13 => {
2957 use crate::draft13::connection::{ClientConfig, Connection, TransportType};
2958 let transport = match config.transport {
2959 AnyTransportType::Quic => TransportType::Quic,
2960 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2961 };
2962 let inner = ClientConfig {
2963 additional_versions: config.additional_versions,
2964 transport,
2965 skip_cert_verification: config.skip_cert_verification,
2966 ca_certs: config.ca_certs,
2967 setup_parameters: config.setup_parameters,
2968 };
2969 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2970 Ok(AnyConnection::Draft13(c))
2971 }
2972 #[cfg(feature = "draft14")]
2973 DraftVersion::Draft14 => {
2974 use crate::draft14::connection::{ClientConfig, Connection, TransportType};
2975 let transport = match config.transport {
2976 AnyTransportType::Quic => TransportType::Quic,
2977 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2978 };
2979 let inner = ClientConfig {
2980 draft: config.draft,
2981 additional_versions: config.additional_versions,
2982 transport,
2983 skip_cert_verification: config.skip_cert_verification,
2984 ca_certs: config.ca_certs,
2985 setup_parameters: config.setup_parameters,
2986 };
2987 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
2988 Ok(AnyConnection::Draft14(c))
2989 }
2990 #[cfg(feature = "draft15")]
2991 DraftVersion::Draft15 => {
2992 use crate::draft15::connection::{ClientConfig, Connection, TransportType};
2993 let transport = match config.transport {
2994 AnyTransportType::Quic => TransportType::Quic,
2995 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
2996 };
2997 let inner = ClientConfig {
2998 draft: config.draft,
2999 transport,
3000 skip_cert_verification: config.skip_cert_verification,
3001 ca_certs: config.ca_certs,
3002 setup_parameters: config.setup_parameters,
3003 };
3004 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3005 Ok(AnyConnection::Draft15(c))
3006 }
3007 #[cfg(feature = "draft16")]
3008 DraftVersion::Draft16 => {
3009 use crate::draft16::connection::{ClientConfig, Connection, TransportType};
3010 let transport = match config.transport {
3011 AnyTransportType::Quic => TransportType::Quic,
3012 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3013 };
3014 let inner = ClientConfig {
3015 draft: config.draft,
3016 transport,
3017 skip_cert_verification: config.skip_cert_verification,
3018 ca_certs: config.ca_certs,
3019 setup_parameters: config.setup_parameters,
3020 };
3021 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3022 Ok(AnyConnection::Draft16(c))
3023 }
3024 #[cfg(feature = "draft17")]
3025 DraftVersion::Draft17 => {
3026 use crate::draft17::connection::{ClientConfig, Connection, TransportType};
3027 let transport = match config.transport {
3028 AnyTransportType::Quic => TransportType::Quic,
3029 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3030 };
3031 let inner = ClientConfig {
3032 draft: config.draft,
3033 transport,
3034 skip_cert_verification: config.skip_cert_verification,
3035 ca_certs: config.ca_certs,
3036 setup_parameters: config.setup_parameters,
3037 };
3038 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3039 Ok(AnyConnection::Draft17(c))
3040 }
3041 #[cfg(feature = "draft18")]
3042 DraftVersion::Draft18 => {
3043 use crate::draft18::connection::{ClientConfig, Connection, TransportType};
3044 let transport = match config.transport {
3045 AnyTransportType::Quic => TransportType::Quic,
3046 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3047 };
3048 let inner = ClientConfig {
3049 draft: config.draft,
3050 transport,
3051 skip_cert_verification: config.skip_cert_verification,
3052 ca_certs: config.ca_certs,
3053 setup_parameters: config.setup_parameters,
3054 };
3055 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3056 Ok(AnyConnection::Draft18(c))
3057 }
3058 #[cfg(feature = "draft19")]
3059 DraftVersion::Draft19 => {
3060 use crate::draft19::connection::{ClientConfig, Connection, TransportType};
3061 let transport = match config.transport {
3062 AnyTransportType::Quic => TransportType::Quic,
3063 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3064 };
3065 let inner = ClientConfig {
3066 draft: config.draft,
3067 transport,
3068 skip_cert_verification: config.skip_cert_verification,
3069 ca_certs: config.ca_certs,
3070 setup_parameters: config.setup_parameters,
3071 };
3072 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3073 Ok(AnyConnection::Draft19(c))
3074 }
3075 #[cfg(feature = "draft20")]
3076 DraftVersion::Draft20 => {
3077 use crate::draft20::connection::{ClientConfig, Connection, TransportType};
3078 let transport = match config.transport {
3079 AnyTransportType::Quic => TransportType::Quic,
3080 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3081 };
3082 let inner = ClientConfig {
3083 draft: config.draft,
3084 transport,
3085 skip_cert_verification: config.skip_cert_verification,
3086 ca_certs: config.ca_certs,
3087 setup_parameters: config.setup_parameters,
3088 };
3089 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3090 Ok(AnyConnection::Draft20(c))
3091 }
3092 #[cfg(feature = "draft21")]
3093 DraftVersion::Draft21 => {
3094 use crate::draft21::connection::{ClientConfig, Connection, TransportType};
3095 let transport = match config.transport {
3096 AnyTransportType::Quic => TransportType::Quic,
3097 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3098 };
3099 let inner = ClientConfig {
3100 draft: config.draft,
3101 transport,
3102 skip_cert_verification: config.skip_cert_verification,
3103 ca_certs: config.ca_certs,
3104 setup_parameters: config.setup_parameters,
3105 };
3106 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3107 Ok(AnyConnection::Draft21(c))
3108 }
3109 #[cfg(feature = "draft22")]
3110 DraftVersion::Draft22 => {
3111 use crate::draft22::connection::{ClientConfig, Connection, TransportType};
3112 let transport = match config.transport {
3113 AnyTransportType::Quic => TransportType::Quic,
3114 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3115 };
3116 let inner = ClientConfig {
3117 draft: config.draft,
3118 transport,
3119 skip_cert_verification: config.skip_cert_verification,
3120 ca_certs: config.ca_certs,
3121 setup_parameters: config.setup_parameters,
3122 };
3123 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3124 Ok(AnyConnection::Draft22(c))
3125 }
3126 #[cfg(feature = "draft23")]
3127 DraftVersion::Draft23 => {
3128 use crate::draft23::connection::{ClientConfig, Connection, TransportType};
3129 let transport = match config.transport {
3130 AnyTransportType::Quic => TransportType::Quic,
3131 AnyTransportType::WebTransport { url } => TransportType::WebTransport { url },
3132 };
3133 let inner = ClientConfig {
3134 draft: config.draft,
3135 transport,
3136 skip_cert_verification: config.skip_cert_verification,
3137 ca_certs: config.ca_certs,
3138 setup_parameters: config.setup_parameters,
3139 };
3140 let c = Connection::connect(addr, inner).await.map_err(AnyConnectionError::from)?;
3141 Ok(AnyConnection::Draft23(c))
3142 }
3143 #[allow(unreachable_patterns)]
3144 other => Err(AnyConnectionError::facade(format!(
3145 "draft {other:?} not enabled in this build",
3146 ))),
3147 }
3148 }
3149
3150 /// Run the MoQT setup handshake over a transport somebody else established.
3151 ///
3152 /// [`connect`](Self::connect) dials its own socket, which is right for a
3153 /// caller that wants a connection and wrong for one that wants to *measure*
3154 /// how a peer behaves: choosing the address, the SNI, the ALPN offer or the
3155 /// certificate policy all mean dialling first and adopting after. Every
3156 /// draft module has `Connection::adopt` for exactly this, and this wrapper
3157 /// is how a caller reaches it without re-implementing the per-draft
3158 /// match over `AnyConnection` for itself.
3159 ///
3160 /// `config.draft` selects the module. Nothing here re-checks it against the
3161 /// ALPN the transport was negotiated with: adopting a transport under a
3162 /// draft the peer did not agree to is a legitimate probe, and refusing it
3163 /// would remove the ability to ask.
3164 pub async fn adopt(
3165 transport: crate::transport::Transport,
3166 config: AnyClientConfig,
3167 ) -> Result<Self, AnyConnectionError> {
3168 Self::adopt_offering(transport, config, None).await
3169 }
3170
3171 /// [`Self::adopt`], offering exactly `versions` in CLIENT_SETUP.
3172 ///
3173 /// `None` offers what `config` implies. `Some` replaces the list outright,
3174 /// and takes raw varints rather than [`DraftVersion`]s because the reason
3175 /// to reach for this is to offer a version no draft assigns — which an enum
3176 /// of drafts cannot name.
3177 ///
3178 /// Drafts 07-14 only. Drafts 15-23 settle the version by ALPN and put no
3179 /// version list on the wire, so there is nothing there to offer and a
3180 /// `Some` on one of them is refused rather than quietly ignored: silently
3181 /// sending the ordinary handshake would answer a question that was never
3182 /// asked.
3183 pub async fn adopt_offering(
3184 transport: crate::transport::Transport,
3185 config: AnyClientConfig,
3186 versions: Option<Vec<moqtap_codec::varint::VarInt>>,
3187 ) -> Result<Self, AnyConnectionError> {
3188 Self::adopt_with(transport, config, versions, None).await
3189 }
3190
3191 /// [`Self::adopt`] with `observer` attached before the handshake, so it
3192 /// sees the SETUP this side sends as it is sent, and a SETUP from the peer
3193 /// that this side refuses as a
3194 /// [`refused_frame`](AnyClientEvent::refused_frame).
3195 ///
3196 /// [`Self::set_observer`] after `adopt` sees the same handshake events
3197 /// replayed, but only once the handshake has succeeded.
3198 pub async fn adopt_observed(
3199 transport: crate::transport::Transport,
3200 config: AnyClientConfig,
3201 observer: Arc<dyn AnyConnectionObserver>,
3202 ) -> Result<Self, AnyConnectionError> {
3203 Self::adopt_with(transport, config, None, Some(observer)).await
3204 }
3205
3206 /// [`Self::adopt_offering`] with `observer` attached before the handshake,
3207 /// as [`Self::adopt_observed`].
3208 pub async fn adopt_offering_observed(
3209 transport: crate::transport::Transport,
3210 config: AnyClientConfig,
3211 versions: Option<Vec<moqtap_codec::varint::VarInt>>,
3212 observer: Arc<dyn AnyConnectionObserver>,
3213 ) -> Result<Self, AnyConnectionError> {
3214 Self::adopt_with(transport, config, versions, Some(observer)).await
3215 }
3216
3217 // `versions`, `observer` and `quic` are read only by the per-draft arms,
3218 // which the `<zero drafts>` build has none of.
3219 #[allow(unused_variables)]
3220 async fn adopt_with(
3221 transport: crate::transport::Transport,
3222 config: AnyClientConfig,
3223 versions: Option<Vec<moqtap_codec::varint::VarInt>>,
3224 observer: Option<Arc<dyn AnyConnectionObserver>>,
3225 ) -> Result<Self, AnyConnectionError> {
3226 // Kept beside the transport the handshake consumes, so a failure over
3227 // a connection that closed can still say how it closed.
3228 let quic = transport.quinn_connection();
3229 // Three shapes, not one, because `ClientConfig` is not the same struct
3230 // in every era and a single arm cannot spell all three:
3231 //
3232 // 07-13 no `draft` field (the module is the draft), offers a
3233 // version list through `additional_versions`
3234 // 14 both — the last draft that can offer several versions at once
3235 // 15-23 `draft` only. One connection offers exactly one version,
3236 // which is why enumerating these costs a connection each.
3237 macro_rules! adopt_dispatch {
3238 (
3239 legacy: [ $( ($lf:literal, $lv:ident, $lm:ident) ),* $(,)? ],
3240 versioned: [ $( ($vf:literal, $vv:ident, $vm:ident) ),* $(,)? ],
3241 single: [ $( ($sf:literal, $sv:ident, $sm:ident) ),* $(,)? ],
3242 ) => {
3243 match config.draft {
3244 $(
3245 #[cfg(feature = $lf)]
3246 DraftVersion::$lv => {
3247 use crate::$lm::connection::{ClientConfig, Connection, TransportType};
3248 let inner = ClientConfig {
3249 additional_versions: config.additional_versions,
3250 transport: match config.transport {
3251 AnyTransportType::Quic => TransportType::Quic,
3252 AnyTransportType::WebTransport { url } => {
3253 TransportType::WebTransport { url }
3254 }
3255 },
3256 skip_cert_verification: config.skip_cert_verification,
3257 ca_certs: config.ca_certs,
3258 setup_parameters: config.setup_parameters,
3259 };
3260 let observer = observer.map(|o| {
3261 Box::new($lv::Adapter(o))
3262 as Box<dyn crate::$lm::observer::ConnectionObserver>
3263 });
3264 let c = Connection::adopt_with(transport, inner, versions, observer)
3265 .await
3266 .map_err(|e| AnyConnectionError {
3267 transport_close: crate::transport::TransportClose::read(&quic),
3268 ..AnyConnectionError::from(e)
3269 })?;
3270 Ok(AnyConnection::$lv(c))
3271 }
3272 )*
3273 $(
3274 #[cfg(feature = $vf)]
3275 DraftVersion::$vv => {
3276 use crate::$vm::connection::{ClientConfig, Connection, TransportType};
3277 let inner = ClientConfig {
3278 draft: config.draft,
3279 additional_versions: config.additional_versions,
3280 transport: match config.transport {
3281 AnyTransportType::Quic => TransportType::Quic,
3282 AnyTransportType::WebTransport { url } => {
3283 TransportType::WebTransport { url }
3284 }
3285 },
3286 skip_cert_verification: config.skip_cert_verification,
3287 ca_certs: config.ca_certs,
3288 setup_parameters: config.setup_parameters,
3289 };
3290 let observer = observer.map(|o| {
3291 Box::new($vv::Adapter(o))
3292 as Box<dyn crate::$vm::observer::ConnectionObserver>
3293 });
3294 let c = Connection::adopt_with(transport, inner, versions, observer)
3295 .await
3296 .map_err(|e| AnyConnectionError {
3297 transport_close: crate::transport::TransportClose::read(&quic),
3298 ..AnyConnectionError::from(e)
3299 })?;
3300 Ok(AnyConnection::$vv(c))
3301 }
3302 )*
3303 $(
3304 #[cfg(feature = $sf)]
3305 DraftVersion::$sv => {
3306 use crate::$sm::connection::{ClientConfig, Connection, TransportType};
3307 if versions.is_some() {
3308 return Err(AnyConnectionError::facade(format!(
3309 "draft {:?} settles its version by ALPN and puts no version \
3310 list on the wire, so there is nothing to offer",
3311 config.draft
3312 )));
3313 }
3314 let inner = ClientConfig {
3315 draft: config.draft,
3316 transport: match config.transport {
3317 AnyTransportType::Quic => TransportType::Quic,
3318 AnyTransportType::WebTransport { url } => {
3319 TransportType::WebTransport { url }
3320 }
3321 },
3322 skip_cert_verification: config.skip_cert_verification,
3323 ca_certs: config.ca_certs,
3324 setup_parameters: config.setup_parameters,
3325 };
3326 let observer = observer.map(|o| {
3327 Box::new($sv::Adapter(o))
3328 as Box<dyn crate::$sm::observer::ConnectionObserver>
3329 });
3330 let c = Connection::adopt_with(transport, inner, observer)
3331 .await
3332 .map_err(|e| AnyConnectionError {
3333 transport_close: crate::transport::TransportClose::read(&quic),
3334 ..AnyConnectionError::from(e)
3335 })?;
3336 Ok(AnyConnection::$sv(c))
3337 }
3338 )*
3339 #[allow(unreachable_patterns)]
3340 other => Err(AnyConnectionError::facade(format!(
3341 "draft {other:?} not enabled in this build"
3342 ))),
3343 }
3344 };
3345 }
3346
3347 adopt_dispatch! {
3348 legacy: [
3349 ("draft07", Draft07, draft07),
3350 ("draft08", Draft08, draft08),
3351 ("draft09", Draft09, draft09),
3352 ("draft10", Draft10, draft10),
3353 ("draft11", Draft11, draft11),
3354 ("draft12", Draft12, draft12),
3355 ("draft13", Draft13, draft13),
3356 ],
3357 versioned: [
3358 ("draft14", Draft14, draft14),
3359 ],
3360 single: [
3361 ("draft15", Draft15, draft15),
3362 ("draft16", Draft16, draft16),
3363 ("draft17", Draft17, draft17),
3364 ("draft18", Draft18, draft18),
3365 ("draft19", Draft19, draft19),
3366 ("draft20", Draft20, draft20),
3367 ("draft21", Draft21, draft21),
3368 ("draft22", Draft22, draft22),
3369 ("draft23", Draft23, draft23),
3370 ],
3371 }
3372 }
3373
3374 /// The version SERVER_SETUP selected, when the draft has a version list to
3375 /// select from.
3376 ///
3377 /// Not the same question as [`draft`](Self::draft), and the difference is
3378 /// the whole point for a caller enumerating what a relay supports:
3379 /// `draft()` reports the module the connection is running, which is the one
3380 /// the *client* chose, while this reports what the *server* picked out of
3381 /// the versions offered. Offer 11 through 14 and a server that settles on
3382 /// 12 leaves `draft()` saying 14 and this saying 12.
3383 ///
3384 /// `None` for drafts 15-23, which is a fact about those drafts rather than
3385 /// a gap here: they carry no `additional_versions`, so a connection offers
3386 /// exactly one version and there is nothing for a server to choose between.
3387 /// What a peer supports there is discovered by ALPN instead.
3388 pub fn negotiated_version(&self) -> Option<moqtap_codec::varint::VarInt> {
3389 match self {
3390 #[cfg(feature = "draft07")]
3391 Self::Draft07(c) => c.negotiated_version(),
3392 #[cfg(feature = "draft08")]
3393 Self::Draft08(c) => c.negotiated_version(),
3394 #[cfg(feature = "draft09")]
3395 Self::Draft09(c) => c.negotiated_version(),
3396 #[cfg(feature = "draft10")]
3397 Self::Draft10(c) => c.negotiated_version(),
3398 #[cfg(feature = "draft11")]
3399 Self::Draft11(c) => c.negotiated_version(),
3400 #[cfg(feature = "draft12")]
3401 Self::Draft12(c) => c.negotiated_version(),
3402 #[cfg(feature = "draft13")]
3403 Self::Draft13(c) => c.negotiated_version(),
3404 #[cfg(feature = "draft14")]
3405 Self::Draft14(c) => c.negotiated_version(),
3406 #[allow(unreachable_patterns)]
3407 _ => None,
3408 }
3409 }
3410
3411 /// Read and dispatch one control message on the active draft. Draft-specific
3412 /// control-message return values are discarded because event delivery goes
3413 /// through the attached observer; callers only care about success/failure.
3414 pub async fn recv_and_dispatch(&mut self) -> Result<(), AnyConnectionError> {
3415 match self {
3416 #[cfg(feature = "draft07")]
3417 Self::Draft07(c) => {
3418 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3419 }
3420 #[cfg(feature = "draft08")]
3421 Self::Draft08(c) => {
3422 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3423 }
3424 #[cfg(feature = "draft09")]
3425 Self::Draft09(c) => {
3426 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3427 }
3428 #[cfg(feature = "draft10")]
3429 Self::Draft10(c) => {
3430 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3431 }
3432 #[cfg(feature = "draft11")]
3433 Self::Draft11(c) => {
3434 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3435 }
3436 #[cfg(feature = "draft12")]
3437 Self::Draft12(c) => {
3438 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3439 }
3440 #[cfg(feature = "draft13")]
3441 Self::Draft13(c) => {
3442 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3443 }
3444 #[cfg(feature = "draft14")]
3445 Self::Draft14(c) => {
3446 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3447 }
3448 #[cfg(feature = "draft15")]
3449 Self::Draft15(c) => {
3450 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3451 }
3452 #[cfg(feature = "draft16")]
3453 Self::Draft16(c) => {
3454 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3455 }
3456 #[cfg(feature = "draft17")]
3457 Self::Draft17(c) => {
3458 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3459 }
3460 #[cfg(feature = "draft18")]
3461 Self::Draft18(c) => {
3462 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3463 }
3464 #[cfg(feature = "draft19")]
3465 Self::Draft19(c) => {
3466 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3467 }
3468 #[cfg(feature = "draft20")]
3469 Self::Draft20(c) => {
3470 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3471 }
3472 #[cfg(feature = "draft21")]
3473 Self::Draft21(c) => {
3474 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3475 }
3476 #[cfg(feature = "draft22")]
3477 Self::Draft22(c) => {
3478 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3479 }
3480 #[cfg(feature = "draft23")]
3481 Self::Draft23(c) => {
3482 c.recv_and_dispatch().await.map(|_| ()).map_err(AnyConnectionError::from)
3483 }
3484 #[allow(unreachable_patterns)]
3485 _ => Err(AnyConnectionError::facade("AnyConnection has no enabled variants")),
3486 }
3487 }
3488
3489 /// Read the next control message that could answer `request`, wherever the
3490 /// negotiated draft carries it.
3491 ///
3492 /// This is the read half of the split [`AnyRequest`] describes, and without
3493 /// it the facade can send a request on drafts 17-23 and then has no way to
3494 /// hear the answer: those drafts put every response on the request's own
3495 /// bidirectional stream, which [`recv_and_dispatch`](Self::recv_and_dispatch)
3496 /// — a control-stream read — never touches.
3497 ///
3498 /// # What "could answer" means, and why it is not "does answer"
3499 ///
3500 /// Which of the two happens depends on the draft, and the difference is the
3501 /// protocol's, not this method's:
3502 ///
3503 /// - **Drafts 07-16, [`AnyRequest::ControlPlane`].** Every request shares
3504 /// the one control stream, so this returns *the next control message on
3505 /// it*, which is the answer only if nothing else was in flight. A peer is
3506 /// free to send MAX_REQUEST_ID or a PUBLISH_NAMESPACE of its own first.
3507 /// Correlate on the `request_id` the response carries against
3508 /// [`AnyRequest::request_id`], and read again if it is not this one.
3509 /// - **Draft-16 namespace subscriptions and drafts 17-23.** The read is on
3510 /// the request's own stream, so nothing else can arrive on it. Those
3511 /// drafts' responses carry no request id at all — the stream *is* the
3512 /// correlation — which is exactly why the read has to be addressed by the
3513 /// handle rather than by the connection.
3514 ///
3515 /// # `Ok(None)`
3516 ///
3517 /// The peer ended the stream cleanly without a message on it. Reachable
3518 /// today only on a draft-16 namespace stream, whose reader distinguishes a
3519 /// FIN from a message; drafts 17-23 report the same event as an error. A
3520 /// distinct value rather than an error because a peer that answered nothing
3521 /// and closed is a different fact from a read that failed, and a caller
3522 /// that has to tell them apart should not be reading either one out of a
3523 /// message string. Neither phrase carries quotation marks and neither may:
3524 /// they are this crate naming two outcomes, and the marks would hand them
3525 /// to the drafts the line above names.
3526 ///
3527 /// # Errors
3528 ///
3529 /// Whatever the underlying read or dispatch produced, flattened to a
3530 /// string like every other error here — a transport failure, a peer reset,
3531 /// or an endpoint refusing a message that does not fit the request's
3532 /// state. A message the endpoint refuses has still been emitted to any
3533 /// attached observer by the time this returns, so an observer is the way to
3534 /// see *what arrived* when the return value only says that something did
3535 /// not fit.
3536 ///
3537 /// A `request` from a different draft than this connection is refused
3538 /// rather than silently read on the wrong stream.
3539 #[allow(unused_variables)]
3540 pub async fn recv_response(
3541 &mut self,
3542 request: &mut AnyRequest,
3543 ) -> Result<Option<moqtap_codec::dispatch::AnyControlMessage>, AnyConnectionError> {
3544 // Used only by the per-draft arms below, so a build with no draft
3545 // feature enabled — the `<zero drafts>` row of `just draft-matrix`,
3546 // which exists to prove the crate still compiles with nothing
3547 // selected — compiles this import and reaches no arm that reads it.
3548 #[allow(unused_imports)]
3549 use moqtap_codec::dispatch::AnyControlMessage;
3550
3551 match (self, request) {
3552 #[cfg(feature = "draft07")]
3553 (Self::Draft07(c), AnyRequest::ControlPlane { .. }) => c
3554 .recv_and_dispatch()
3555 .await
3556 .map(|m| Some(AnyControlMessage::Draft07(m)))
3557 .map_err(AnyConnectionError::from),
3558 #[cfg(feature = "draft08")]
3559 (Self::Draft08(c), AnyRequest::ControlPlane { .. }) => c
3560 .recv_and_dispatch()
3561 .await
3562 .map(|m| Some(AnyControlMessage::Draft08(m)))
3563 .map_err(AnyConnectionError::from),
3564 #[cfg(feature = "draft09")]
3565 (Self::Draft09(c), AnyRequest::ControlPlane { .. }) => c
3566 .recv_and_dispatch()
3567 .await
3568 .map(|m| Some(AnyControlMessage::Draft09(m)))
3569 .map_err(AnyConnectionError::from),
3570 #[cfg(feature = "draft10")]
3571 (Self::Draft10(c), AnyRequest::ControlPlane { .. }) => c
3572 .recv_and_dispatch()
3573 .await
3574 .map(|m| Some(AnyControlMessage::Draft10(m)))
3575 .map_err(AnyConnectionError::from),
3576 #[cfg(feature = "draft11")]
3577 (Self::Draft11(c), AnyRequest::ControlPlane { .. }) => c
3578 .recv_and_dispatch()
3579 .await
3580 .map(|m| Some(AnyControlMessage::Draft11(m)))
3581 .map_err(AnyConnectionError::from),
3582 #[cfg(feature = "draft12")]
3583 (Self::Draft12(c), AnyRequest::ControlPlane { .. }) => c
3584 .recv_and_dispatch()
3585 .await
3586 .map(|m| Some(AnyControlMessage::Draft12(m)))
3587 .map_err(AnyConnectionError::from),
3588 #[cfg(feature = "draft13")]
3589 (Self::Draft13(c), AnyRequest::ControlPlane { .. }) => c
3590 .recv_and_dispatch()
3591 .await
3592 .map(|m| Some(AnyControlMessage::Draft13(m)))
3593 .map_err(AnyConnectionError::from),
3594 #[cfg(feature = "draft14")]
3595 (Self::Draft14(c), AnyRequest::ControlPlane { .. }) => c
3596 .recv_and_dispatch()
3597 .await
3598 .map(|m| Some(AnyControlMessage::Draft14(m)))
3599 .map_err(AnyConnectionError::from),
3600 #[cfg(feature = "draft15")]
3601 (Self::Draft15(c), AnyRequest::ControlPlane { .. }) => c
3602 .recv_and_dispatch()
3603 .await
3604 .map(|m| Some(AnyControlMessage::Draft15(m)))
3605 .map_err(AnyConnectionError::from),
3606 #[cfg(feature = "draft16")]
3607 (Self::Draft16(c), AnyRequest::ControlPlane { .. }) => c
3608 .recv_and_dispatch()
3609 .await
3610 .map(|m| Some(AnyControlMessage::Draft16(m)))
3611 .map_err(AnyConnectionError::from),
3612 // The one request draft-16 moved off the control stream, and so the
3613 // one place on that draft where this reads a stream instead.
3614 #[cfg(feature = "draft16")]
3615 (Self::Draft16(c), AnyRequest::Draft16(s)) => c
3616 .recv_on_namespace_stream(s)
3617 .await
3618 .map(|m| m.map(AnyControlMessage::Draft16))
3619 .map_err(AnyConnectionError::from),
3620 #[cfg(feature = "draft17")]
3621 (Self::Draft17(c), AnyRequest::Draft17(s)) => c
3622 .recv_on_request_stream(s)
3623 .await
3624 .map(|m| Some(AnyControlMessage::Draft17(m)))
3625 .map_err(AnyConnectionError::from),
3626 #[cfg(feature = "draft18")]
3627 (Self::Draft18(c), AnyRequest::Draft18(s)) => c
3628 .recv_on_request_stream(s)
3629 .await
3630 .map(|m| Some(AnyControlMessage::Draft18(m)))
3631 .map_err(AnyConnectionError::from),
3632 #[cfg(feature = "draft19")]
3633 (Self::Draft19(c), AnyRequest::Draft19(s)) => c
3634 .recv_on_request_stream(s)
3635 .await
3636 .map(|m| Some(AnyControlMessage::Draft19(m)))
3637 .map_err(AnyConnectionError::from),
3638 #[cfg(feature = "draft20")]
3639 (Self::Draft20(c), AnyRequest::Draft20(s)) => c
3640 .recv_on_request_stream(s)
3641 .await
3642 .map(|m| Some(AnyControlMessage::Draft20(m)))
3643 .map_err(AnyConnectionError::from),
3644 #[cfg(feature = "draft21")]
3645 (Self::Draft21(c), AnyRequest::Draft21(s)) => c
3646 .recv_on_request_stream(s)
3647 .await
3648 .map(|m| Some(AnyControlMessage::Draft21(m)))
3649 .map_err(AnyConnectionError::from),
3650 #[cfg(feature = "draft22")]
3651 (Self::Draft22(c), AnyRequest::Draft22(s)) => c
3652 .recv_on_request_stream(s)
3653 .await
3654 .map(|m| Some(AnyControlMessage::Draft22(m)))
3655 .map_err(AnyConnectionError::from),
3656 #[cfg(feature = "draft23")]
3657 (Self::Draft23(c), AnyRequest::Draft23(s)) => c
3658 .recv_on_request_stream(s)
3659 .await
3660 .map(|m| Some(AnyControlMessage::Draft23(m)))
3661 .map_err(AnyConnectionError::from),
3662 // A handle from another draft, or a build with no drafts enabled.
3663 // Refused rather than read on whatever stream happens to be at
3664 // hand: the two kinds of handle address different streams, so
3665 // guessing here would read the control stream for a request that is
3666 // waiting on its own.
3667 #[allow(unreachable_patterns)]
3668 (connection, request) => Err(AnyConnectionError::facade(format!(
3669 "recv_response: a draft {:?} request cannot be read on a draft {:?} connection",
3670 request.draft(),
3671 connection.draft(),
3672 ))),
3673 }
3674 }
3675
3676 // ── Unified control-message helpers ──────────────────────────────────
3677 //
3678 // Draft-agnostic shorthands. Each dispatches to the active variant and
3679 // defaults fields not expressible in the unified shape; drafts that
3680 // lack the operation return an `AnyConnectionError`. Match on the
3681 // variant directly when full per-draft control is needed.
3682
3683 /// Send an UNSUBSCRIBE for the given request ID. Drafts 07 through 16.
3684 ///
3685 /// # Drafts 17 through 23 have no such message
3686 ///
3687 /// Draft-17 deleted UNSUBSCRIBE, and every later draft keeps it deleted:
3688 /// a subscriber ends a subscription by **resetting its request stream**,
3689 /// which is [`AnyRequest::cancel`], or waits for PUBLISH_DONE. So the error
3690 /// those drafts return is not a gap to be filled later — there is nothing to
3691 /// wire — and a caller reaching for it on one of them wants `cancel` on the
3692 /// handle `subscribe` returned.
3693 #[allow(unused_variables)]
3694 pub async fn unsubscribe(
3695 &mut self,
3696 request_id: moqtap_codec::varint::VarInt,
3697 ) -> Result<(), AnyConnectionError> {
3698 match self {
3699 #[cfg(feature = "draft07")]
3700 Self::Draft07(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3701 #[cfg(feature = "draft08")]
3702 Self::Draft08(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3703 #[cfg(feature = "draft09")]
3704 Self::Draft09(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3705 #[cfg(feature = "draft10")]
3706 Self::Draft10(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3707 #[cfg(feature = "draft11")]
3708 Self::Draft11(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3709 #[cfg(feature = "draft12")]
3710 Self::Draft12(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3711 #[cfg(feature = "draft13")]
3712 Self::Draft13(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3713 #[cfg(feature = "draft14")]
3714 Self::Draft14(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3715 #[cfg(feature = "draft15")]
3716 Self::Draft15(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3717 #[cfg(feature = "draft16")]
3718 Self::Draft16(c) => c.unsubscribe(request_id).await.map_err(AnyConnectionError::from),
3719 // The text names the protocol reason and an alternative rather than
3720 // reporting the draft as unimplemented: there is nothing here left
3721 // to wire, and an error that contradicts this method's own docs is
3722 // worse than no error text at all, because the next reader believes
3723 // the error.
3724 #[allow(unreachable_patterns)]
3725 other => Err(AnyConnectionError::facade(format!(
3726 "unsubscribe: draft-{:02} has no UNSUBSCRIBE — draft-17 deleted it and the drafts \
3727 after it keep it deleted. End the subscription with AnyRequest::cancel on the \
3728 handle subscribe returned, or wait for PUBLISH_DONE",
3729 other.draft().number()
3730 ))),
3731 }
3732 }
3733
3734 /// Send a SUBSCRIBE with the given filter, priority, and group order.
3735 /// Supported on **every draft this build carries**. Drafts 15 onward
3736 /// carry priority/order/filter as parameters rather than fields;
3737 /// this helper passes an empty parameter list, so on those drafts all three
3738 /// take the protocol default and the three arguments here are ignored.
3739 ///
3740 /// # The Track Alias, and why it is not an argument
3741 ///
3742 /// Drafts 07 through 11 carry a Track Alias on SUBSCRIBE and make it the
3743 /// **subscriber's** to choose; draft-12 moved the field to SUBSCRIBE_OK and
3744 /// made it the publisher's. An argument here would therefore do nothing on
3745 /// nine of the drafts, and a fixed value would collide the moment
3746 /// a caller subscribed to a second track.
3747 ///
3748 /// So the value is read off the endpoint —
3749 /// `Connection::next_free_track_alias`, the lowest alias no live binding
3750 /// holds — rather than asked of the caller. That table is the same one the
3751 /// endpoint checks before writing, so a caller mixing these calls with a
3752 /// draft's own `Connection::subscribe` and aliases of its own is correct by
3753 /// construction rather than by convention, and a genuine duplicate is still
3754 /// refused with `EndpointError::TrackAliasInUse` before anything reaches
3755 /// the wire.
3756 ///
3757 /// There is no range to get wrong *here*: a subscription with no filter is
3758 /// one that starts where the draft says it starts, and nothing is
3759 /// converted. The two filters that name a Start Location —
3760 /// `AbsoluteStart` and `AbsoluteRange` — are refused by this call on every
3761 /// draft that takes a Filter Type as an argument, because it has no start
3762 /// location to put beside them; [`AnyConnection::subscribe_range`] is the
3763 /// entry point that takes one.
3764 /// A draft-20 caller that wants a Location filter, a fill, or anything else
3765 /// from Section 10.2 reaches
3766 /// [`draft20::connection::Connection::subscribe`](crate::draft20::connection::Connection::subscribe)
3767 /// through the variant with the parameters it wants.
3768 ///
3769 /// The returned [`AnyRequest`] must be held while the request is live: on
3770 /// drafts 17-23 it owns the bidirectional stream the request went out on
3771 /// and dropping it cancels the subscription. See [`AnyRequest`] for how
3772 /// the two kinds of handle differ.
3773 #[allow(unused_variables)]
3774 pub async fn subscribe(
3775 &mut self,
3776 namespace: moqtap_codec::types::TrackNamespace,
3777 track_name: Vec<u8>,
3778 subscriber_priority: u8,
3779 group_order: moqtap_codec::types::GroupOrder,
3780 filter_type: moqtap_codec::types::FilterType,
3781 ) -> Result<AnyRequest, AnyConnectionError> {
3782 // Only the draft-11 and draft-12 arms below convert `filter_type` by
3783 // hand: those two draw that field as a variable-length integer, where
3784 // the drafts on either side of them take the typed value straight
3785 // through. So the import is dead outside those builds.
3786 #[cfg(any(feature = "draft11", feature = "draft12"))]
3787 use moqtap_codec::varint::VarInt;
3788 let draft = self.draft();
3789 match self {
3790 // Drafts 07 through 11 put the Track Alias on SUBSCRIBE and leave
3791 // its choice to the subscriber, and have no parameter block. The
3792 // alias comes from the endpoint's own table of live bindings; see
3793 // this method's docs for why it is not an argument.
3794 #[cfg(feature = "draft07")]
3795 Self::Draft07(c) => {
3796 let alias = c.next_free_track_alias();
3797 c.subscribe(
3798 alias,
3799 namespace,
3800 track_name,
3801 subscriber_priority,
3802 group_order,
3803 filter_type,
3804 )
3805 .await
3806 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3807 .map_err(AnyConnectionError::from)
3808 }
3809 #[cfg(feature = "draft08")]
3810 Self::Draft08(c) => {
3811 let alias = c.next_free_track_alias();
3812 c.subscribe(
3813 alias,
3814 namespace,
3815 track_name,
3816 subscriber_priority,
3817 group_order,
3818 filter_type,
3819 )
3820 .await
3821 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3822 .map_err(AnyConnectionError::from)
3823 }
3824 #[cfg(feature = "draft09")]
3825 Self::Draft09(c) => {
3826 let alias = c.next_free_track_alias();
3827 c.subscribe(
3828 alias,
3829 namespace,
3830 track_name,
3831 subscriber_priority,
3832 group_order,
3833 filter_type,
3834 )
3835 .await
3836 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3837 .map_err(AnyConnectionError::from)
3838 }
3839 #[cfg(feature = "draft10")]
3840 Self::Draft10(c) => {
3841 let alias = c.next_free_track_alias();
3842 c.subscribe(
3843 alias,
3844 namespace,
3845 track_name,
3846 subscriber_priority,
3847 group_order,
3848 filter_type,
3849 )
3850 .await
3851 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3852 .map_err(AnyConnectionError::from)
3853 }
3854 #[cfg(feature = "draft11")]
3855 Self::Draft11(c) => {
3856 let ft = VarInt::from_u64(filter_type as u64)
3857 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
3858 let alias = c.next_free_track_alias();
3859 c.subscribe(alias, namespace, track_name, subscriber_priority, group_order, ft)
3860 .await
3861 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3862 .map_err(AnyConnectionError::from)
3863 }
3864 #[cfg(feature = "draft12")]
3865 Self::Draft12(c) => {
3866 let ft = VarInt::from_u64(filter_type as u64)
3867 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
3868 c.subscribe(namespace, track_name, subscriber_priority, group_order, ft, Vec::new())
3869 .await
3870 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3871 .map_err(AnyConnectionError::from)
3872 }
3873 #[cfg(feature = "draft13")]
3874 Self::Draft13(c) => c
3875 .subscribe(
3876 namespace,
3877 track_name,
3878 subscriber_priority,
3879 group_order,
3880 filter_type,
3881 Vec::new(),
3882 )
3883 .await
3884 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3885 .map_err(AnyConnectionError::from),
3886 #[cfg(feature = "draft14")]
3887 Self::Draft14(c) => c
3888 .subscribe(
3889 namespace,
3890 track_name,
3891 subscriber_priority,
3892 group_order,
3893 filter_type,
3894 Vec::new(),
3895 )
3896 .await
3897 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3898 .map_err(AnyConnectionError::from),
3899 #[cfg(feature = "draft15")]
3900 Self::Draft15(c) => c
3901 .subscribe(namespace, track_name, Vec::new())
3902 .await
3903 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3904 .map_err(AnyConnectionError::from),
3905 #[cfg(feature = "draft16")]
3906 Self::Draft16(c) => c
3907 .subscribe(namespace, track_name, Vec::new())
3908 .await
3909 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
3910 .map_err(AnyConnectionError::from),
3911 #[cfg(feature = "draft17")]
3912 Self::Draft17(c) => c
3913 .subscribe(namespace, track_name, Vec::new())
3914 .await
3915 .map(AnyRequest::Draft17)
3916 .map_err(AnyConnectionError::from),
3917 #[cfg(feature = "draft18")]
3918 Self::Draft18(c) => c
3919 .subscribe(namespace, track_name, Vec::new())
3920 .await
3921 .map(AnyRequest::Draft18)
3922 .map_err(AnyConnectionError::from),
3923 #[cfg(feature = "draft19")]
3924 Self::Draft19(c) => c
3925 .subscribe(namespace, track_name, Vec::new())
3926 .await
3927 .map(AnyRequest::Draft19)
3928 .map_err(AnyConnectionError::from),
3929 #[cfg(feature = "draft20")]
3930 Self::Draft20(c) => c
3931 .subscribe(namespace, track_name, Vec::new())
3932 .await
3933 .map(AnyRequest::Draft20)
3934 .map_err(AnyConnectionError::from),
3935 #[cfg(feature = "draft21")]
3936 Self::Draft21(c) => c
3937 .subscribe(namespace, track_name, Vec::new())
3938 .await
3939 .map(AnyRequest::Draft21)
3940 .map_err(AnyConnectionError::from),
3941 #[cfg(feature = "draft22")]
3942 Self::Draft22(c) => c
3943 .subscribe(namespace, track_name, Vec::new())
3944 .await
3945 .map(AnyRequest::Draft22)
3946 .map_err(AnyConnectionError::from),
3947 #[cfg(feature = "draft23")]
3948 Self::Draft23(c) => c
3949 .subscribe(namespace, track_name, Vec::new())
3950 .await
3951 .map(AnyRequest::Draft23)
3952 .map_err(AnyConnectionError::from),
3953 #[allow(unreachable_patterns)]
3954 other => Err(AnyConnectionError::facade(format!(
3955 "subscribe: not yet wired up for draft {:?} via AnyConnection",
3956 other.draft()
3957 ))),
3958 }
3959 }
3960
3961 /// Send a SUBSCRIBE that names where the subscription starts, and
3962 /// optionally where it stops. Wired on **every draft this build carries**.
3963 ///
3964 /// This is the half of SUBSCRIBE [`AnyConnection::subscribe`] cannot reach.
3965 /// The two filters that ask a relay for anything it has **already carried**
3966 /// — AbsoluteStart and AbsoluteRange — both put a Start Location on the
3967 /// wire, and a call taking the Filter Type beside the other arguments has
3968 /// none to give. So `subscribe` refuses them and this takes a
3969 /// [`SubscribeRange`] instead, from which the Filter Type is **derived**:
3970 /// the message cannot name a filter whose fields it does not carry, because
3971 /// nothing here gets to name one.
3972 ///
3973 /// Without it there is no way to ask whether a relay holds a cache at all,
3974 /// which is a question about relays and not about ranges.
3975 ///
3976 /// # What each draft is handed
3977 ///
3978 /// Five wire shapes for one range, and the conversions are
3979 /// [`SubscribeRange`]'s rather than each arm's:
3980 ///
3981 /// * **draft-07** — a Start Location and an End *Location*, whose Object is
3982 /// the last one plus 1 with `0` for the whole Group, exactly as FETCH
3983 /// words it. [`SubscribeRange::inline_end_location`].
3984 /// * **drafts 08 through 14** — a Start Location and an absolute End
3985 /// *Group*, the End Object having been deleted in draft-08.
3986 /// * **drafts 15 and 16** — the same two, moved into the
3987 /// `SUBSCRIPTION_FILTER` parameter draft-15 introduced.
3988 /// * **drafts 17 through 19** — the same parameter, with the End Group
3989 /// written as a delta from the start.
3990 /// * **drafts 20 and 21** — `LOCATION_FILTER`, whose shape comes from its
3991 /// field count rather than from a Filter Type, and whose ranges are
3992 /// inclusive. [`SubscribeRange::location_filter_draft20`], which is also
3993 /// where the one value this facade refuses on those drafts is documented.
3994 /// * **draft-22** — the same parameter, opening with a `Location Filter
3995 /// Type` that names the shape, so `{0, 0}` is the beginning of the track
3996 /// and nothing is refused for it.
3997 /// [`SubscribeRange::location_filter_draft22`].
3998 ///
3999 /// # What this does not carry
4000 ///
4001 /// An empty parameter list on every draft that has one beside the filter,
4002 /// and the same priority and group order defaults
4003 /// [`AnyConnection::subscribe`] passes: those are fields of SUBSCRIBE on
4004 /// drafts 07 through 14 and parameters on drafts 15 and later, so they are
4005 /// taken here for the drafts that have the fields and ignored by the six
4006 /// that do not — which is the arrangement `subscribe` already documents.
4007 ///
4008 /// # Errors
4009 ///
4010 /// A range the negotiated draft cannot express, before anything is written.
4011 /// There are three, and each is a genuine difference between the drafts
4012 /// rather than a limitation of this call:
4013 /// [`SubscribeEnd::ThroughObject`] on drafts 08 through 19; an `end_group`
4014 /// below `start_group` on drafts 17 through 23; and a `{0, 0}`
4015 /// [`SubscribeEnd::Open`] on drafts 20 and 21, which read it as the live
4016 /// edge, where every other draft reads the beginning of the track.
4017 ///
4018 /// The returned [`AnyRequest`] must be held while the request is live: on
4019 /// drafts 17-23 it owns the bidirectional stream the request went out on
4020 /// and dropping it cancels the subscription.
4021 #[allow(unused_variables)]
4022 pub async fn subscribe_range(
4023 &mut self,
4024 namespace: moqtap_codec::types::TrackNamespace,
4025 track_name: Vec<u8>,
4026 subscriber_priority: u8,
4027 group_order: moqtap_codec::types::GroupOrder,
4028 range: SubscribeRange,
4029 ) -> Result<AnyRequest, AnyConnectionError> {
4030 // The Start Location every arm below 15 puts on the wire directly, and
4031 // every arm from 15 puts inside a parameter. Built once because it is
4032 // the one field none of the drafts disagree about.
4033 let start = range.start_location();
4034 let draft = self.draft();
4035 match self {
4036 // Draft-07 alone carries an End Location rather than an End Group,
4037 // and carries it with FETCH's plus-one convention. The arithmetic
4038 // is `inline_end_location`'s, in one place, so it cannot reach the
4039 // drafts that must not have it.
4040 #[cfg(feature = "draft07")]
4041 Self::Draft07(c) => {
4042 let end = range.inline_end_location()?;
4043 let alias = c.next_free_track_alias();
4044 c.subscribe_range(
4045 alias,
4046 namespace,
4047 track_name,
4048 subscriber_priority,
4049 group_order,
4050 start,
4051 end,
4052 )
4053 .await
4054 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4055 .map_err(AnyConnectionError::from)
4056 }
4057 // Drafts 08 through 11 put the Track Alias on SUBSCRIBE and make it
4058 // the subscriber's to choose, and have no parameter block; see
4059 // `subscribe` for why the alias is read off the endpoint rather
4060 // than taken as an argument.
4061 #[cfg(feature = "draft08")]
4062 Self::Draft08(c) => {
4063 let end =
4064 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4065 let alias = c.next_free_track_alias();
4066 c.subscribe_range(
4067 alias,
4068 namespace,
4069 track_name,
4070 subscriber_priority,
4071 group_order,
4072 start,
4073 end,
4074 )
4075 .await
4076 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4077 .map_err(AnyConnectionError::from)
4078 }
4079 #[cfg(feature = "draft09")]
4080 Self::Draft09(c) => {
4081 let end =
4082 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4083 let alias = c.next_free_track_alias();
4084 c.subscribe_range(
4085 alias,
4086 namespace,
4087 track_name,
4088 subscriber_priority,
4089 group_order,
4090 start,
4091 end,
4092 )
4093 .await
4094 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4095 .map_err(AnyConnectionError::from)
4096 }
4097 #[cfg(feature = "draft10")]
4098 Self::Draft10(c) => {
4099 let end =
4100 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4101 let alias = c.next_free_track_alias();
4102 c.subscribe_range(
4103 alias,
4104 namespace,
4105 track_name,
4106 subscriber_priority,
4107 group_order,
4108 start,
4109 end,
4110 )
4111 .await
4112 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4113 .map_err(AnyConnectionError::from)
4114 }
4115 #[cfg(feature = "draft11")]
4116 Self::Draft11(c) => {
4117 let end =
4118 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4119 let alias = c.next_free_track_alias();
4120 c.subscribe_range(
4121 alias,
4122 namespace,
4123 track_name,
4124 subscriber_priority,
4125 group_order,
4126 start,
4127 end,
4128 )
4129 .await
4130 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4131 .map_err(AnyConnectionError::from)
4132 }
4133 // Drafts 12 through 14: the alias moved to SUBSCRIBE_OK and a
4134 // parameter block arrived, and the range is still two fields on
4135 // SUBSCRIBE itself.
4136 #[cfg(feature = "draft12")]
4137 Self::Draft12(c) => {
4138 let end =
4139 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4140 c.subscribe_range(
4141 namespace,
4142 track_name,
4143 subscriber_priority,
4144 group_order,
4145 start,
4146 end,
4147 Vec::new(),
4148 )
4149 .await
4150 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4151 .map_err(AnyConnectionError::from)
4152 }
4153 #[cfg(feature = "draft13")]
4154 Self::Draft13(c) => {
4155 let end =
4156 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4157 c.subscribe_range(
4158 namespace,
4159 track_name,
4160 subscriber_priority,
4161 group_order,
4162 start,
4163 end,
4164 Vec::new(),
4165 )
4166 .await
4167 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4168 .map_err(AnyConnectionError::from)
4169 }
4170 #[cfg(feature = "draft14")]
4171 Self::Draft14(c) => {
4172 let end =
4173 range.group_only_end(draft)?.map(moqtap_codec::varint::VarInt::from_u64_moqt);
4174 c.subscribe_range(
4175 namespace,
4176 track_name,
4177 subscriber_priority,
4178 group_order,
4179 start,
4180 end,
4181 Vec::new(),
4182 )
4183 .await
4184 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4185 .map_err(AnyConnectionError::from)
4186 }
4187 // From draft-15 the whole filter is a parameter value, so the
4188 // range does not reach the endpoint as arguments at all and there
4189 // is no `subscribe_range` under here to call. `group_only_end`
4190 // still runs, inside `subscription_filter`, because these drafts
4191 // deleted the End Object along with drafts 08 through 14.
4192 #[cfg(feature = "draft15")]
4193 Self::Draft15(c) => {
4194 let filter = range
4195 .subscription_filter(draft, false)?
4196 .parameter()
4197 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4198 c.subscribe(namespace, track_name, vec![filter])
4199 .await
4200 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4201 .map_err(AnyConnectionError::from)
4202 }
4203 #[cfg(feature = "draft16")]
4204 Self::Draft16(c) => {
4205 let filter = range
4206 .subscription_filter(draft, false)?
4207 .parameter()
4208 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4209 c.subscribe(namespace, track_name, vec![filter])
4210 .await
4211 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4212 .map_err(AnyConnectionError::from)
4213 }
4214 // Draft-17 introduced the End Group Delta and its own integer
4215 // encoding, in which the 7-byte length is an invalid code point.
4216 // Draft-18 restored that length, which is why the profile differs
4217 // between this arm and the two below it.
4218 #[cfg(feature = "draft17")]
4219 Self::Draft17(c) => {
4220 let filter = range
4221 .subscription_filter(draft, true)?
4222 .parameter_moqt::<moqtap_codec::varint::Moqt17>()
4223 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4224 c.subscribe(namespace, track_name, vec![filter])
4225 .await
4226 .map(AnyRequest::Draft17)
4227 .map_err(AnyConnectionError::from)
4228 }
4229 #[cfg(feature = "draft18")]
4230 Self::Draft18(c) => {
4231 let filter = range
4232 .subscription_filter(draft, true)?
4233 .parameter_moqt::<moqtap_codec::varint::Moqt18>()
4234 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4235 c.subscribe(namespace, track_name, vec![filter])
4236 .await
4237 .map(AnyRequest::Draft18)
4238 .map_err(AnyConnectionError::from)
4239 }
4240 #[cfg(feature = "draft19")]
4241 Self::Draft19(c) => {
4242 let filter = range
4243 .subscription_filter(draft, true)?
4244 .parameter_moqt::<moqtap_codec::varint::Moqt18>()
4245 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4246 c.subscribe(namespace, track_name, vec![filter])
4247 .await
4248 .map(AnyRequest::Draft19)
4249 .map_err(AnyConnectionError::from)
4250 }
4251 // Draft-20 deleted the Filter Type enum and reads the shape off the
4252 // field count, so this arm builds a different value from the same
4253 // range rather than the same value with a different integer
4254 // encoding.
4255 #[cfg(feature = "draft20")]
4256 Self::Draft20(c) => {
4257 let filter = range
4258 .location_filter_draft20()?
4259 .parameter()
4260 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4261 c.subscribe(namespace, track_name, vec![filter])
4262 .await
4263 .map(AnyRequest::Draft20)
4264 .map_err(AnyConnectionError::from)
4265 }
4266 #[cfg(feature = "draft21")]
4267 Self::Draft21(c) => {
4268 let filter = range
4269 .location_filter_draft21()?
4270 .parameter()
4271 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4272 c.subscribe(namespace, track_name, vec![filter])
4273 .await
4274 .map(AnyRequest::Draft21)
4275 .map_err(AnyConnectionError::from)
4276 }
4277 #[cfg(feature = "draft22")]
4278 Self::Draft22(c) => {
4279 let filter = range
4280 .location_filter_draft22()?
4281 .parameter()
4282 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4283 c.subscribe(namespace, track_name, vec![filter])
4284 .await
4285 .map(AnyRequest::Draft22)
4286 .map_err(AnyConnectionError::from)
4287 }
4288 #[cfg(feature = "draft23")]
4289 Self::Draft23(c) => {
4290 let filter = range
4291 .location_filter_draft23()?
4292 .parameter()
4293 .map_err(|e| AnyConnectionError::facade(e.to_string()))?;
4294 c.subscribe(namespace, track_name, vec![filter])
4295 .await
4296 .map(AnyRequest::Draft23)
4297 .map_err(AnyConnectionError::from)
4298 }
4299 #[allow(unreachable_patterns)]
4300 other => Err(AnyConnectionError::facade(format!(
4301 "subscribe_range: not yet wired up for draft {:?} via AnyConnection",
4302 other.draft()
4303 ))),
4304 }
4305 }
4306
4307 /// Send a standalone FETCH for `range`. Wired on **every draft this build
4308 /// carries**.
4309 ///
4310 /// # What the range means here
4311 ///
4312 /// **[`FetchEnd::Object`] is the last Object the fetch covers, and the
4313 /// range holds it. [`FetchEnd::EntireGroup`] covers the whole end Group.
4314 /// `end_group` is absolute.** That is the whole contract, and it is stated
4315 /// in [`FetchEnd`] as well because it is the one thing about this call a
4316 /// caller can get wrong without being told.
4317 ///
4318 /// The drafts do not agree, which is why the argument is a
4319 /// [`FetchRange`] rather than four numbers. Drafts 07 through 19 carry the
4320 /// start and the end inline in FETCH and all thirteen word the end alike —
4321 /// draft-19 Section 10.12.1: "The end Location, plus 1. A Location.Object
4322 /// value of 0 means the entire group is requested." The section number
4323 /// moves between drafts and the fields are regrouped into a `Location`
4324 /// along the way; the sentence does not change, which is why
4325 /// `moqtap_codec::types::check_location_range` is one shared function
4326 /// rather than thirteen.
4327 /// Draft-20 Section 10.13 deleted both fields and
4328 /// moved the range into the `LOCATION_FILTER` parameter, whose ranges
4329 /// Section 5.1.2 calls **inclusive** — the `+ 1` and the `0`-means-whole-
4330 /// group convention are both gone, and neither deletion is in the draft's
4331 /// own change log. A single `end_object: u64` at this boundary would have
4332 /// meant one of those two things and looked like the other.
4333 ///
4334 /// The conversion is [`FetchRange::inline_end_object`] for the first group
4335 /// and [`FetchRange::location_filter_draft20`] for draft-20. **The `+ 1` exists in
4336 /// exactly one place**, the first of those, so it cannot reach draft-20 by
4337 /// being ported.
4338 ///
4339 /// # What this does not carry
4340 ///
4341 /// An empty parameter list, on every draft that has one — drafts 07 through
4342 /// 11 have no parameter field on FETCH at all. Drafts 07 through 14 carry
4343 /// subscriber priority and group order as fields of FETCH and no later
4344 /// draft does, so those arms send the defaults rather than widening an
4345 /// entry point shared with six drafts that have no such fields. Reach a
4346 /// draft's own `Connection::fetch` through the variant for anything past a
4347 /// plain range.
4348 ///
4349 /// # Errors
4350 ///
4351 /// A range the negotiated draft cannot express, before anything is written:
4352 /// `FetchEnd::Object(u64::MAX)` on drafts 14 through 19, and an `end_group`
4353 /// below `start_group` on draft-20. See the two conversions for why each is
4354 /// inexpressible rather than merely unusual.
4355 ///
4356 /// The returned [`AnyRequest`] must be held while the request is live: on
4357 /// drafts 17-23 it owns the bidirectional stream the request went out on
4358 /// and dropping it cancels the fetch.
4359 #[allow(unused_variables)]
4360 pub async fn fetch(
4361 &mut self,
4362 namespace: moqtap_codec::types::TrackNamespace,
4363 track_name: Vec<u8>,
4364 range: FetchRange,
4365 ) -> Result<AnyRequest, AnyConnectionError> {
4366 // The three fields drafts 14 through 19 carry unchanged, built once for
4367 // whichever arm runs. `from_u64_moqt` rather than `from_u64` because
4368 // MoQT's varint reaches the full 64-bit range (Section 1.4.1) and the
4369 // newtype *is* the value: a `VarInt` a caller could have handed the old
4370 // four-argument form is the same `VarInt` this makes, so every draft
4371 // that was wired before writes the bytes it wrote before. The fourth
4372 // field is the one that differs, and it is built inside each arm from
4373 // `inline_end_object`.
4374 let start_group = moqtap_codec::varint::VarInt::from_u64_moqt(range.start_group);
4375 let start_object = moqtap_codec::varint::VarInt::from_u64_moqt(range.start_object);
4376 let end_group = moqtap_codec::varint::VarInt::from_u64_moqt(range.end_group);
4377 // The fourth field, which is the one that differs: draft-19 Section
4378 // 10.13 makes `End Location.Object` the last Object plus 1, or 0 for
4379 // the whole Group. Built here so the six arms that need it stay
4380 // identical, and lazily so that draft-20 — which can express a range
4381 // those six cannot — is not refused on their behalf.
4382 let inline_end_object = || -> Result<moqtap_codec::varint::VarInt, AnyConnectionError> {
4383 Ok(moqtap_codec::varint::VarInt::from_u64_moqt(range.inline_end_object()?))
4384 };
4385 let draft = self.draft();
4386 match self {
4387 // Drafts 07 through 11 take the four location fields and nothing
4388 // else: FETCH gained its parameter block in draft-12. The
4389 // priority and the order are fields of FETCH on every draft up to
4390 // and including 14 and of no later draft's, so these arms send the
4391 // defaults rather than widening an entry point shared with six
4392 // drafts that have no such fields.
4393 #[cfg(feature = "draft07")]
4394 Self::Draft07(c) => c
4395 .fetch(
4396 namespace,
4397 track_name,
4398 128,
4399 moqtap_codec::types::GroupOrder::Ascending,
4400 start_group,
4401 start_object,
4402 end_group,
4403 inline_end_object()?,
4404 )
4405 .await
4406 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4407 .map_err(AnyConnectionError::from),
4408 #[cfg(feature = "draft08")]
4409 Self::Draft08(c) => c
4410 .fetch(
4411 namespace,
4412 track_name,
4413 128,
4414 moqtap_codec::types::GroupOrder::Ascending,
4415 start_group,
4416 start_object,
4417 end_group,
4418 inline_end_object()?,
4419 )
4420 .await
4421 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4422 .map_err(AnyConnectionError::from),
4423 #[cfg(feature = "draft09")]
4424 Self::Draft09(c) => c
4425 .fetch(
4426 namespace,
4427 track_name,
4428 128,
4429 moqtap_codec::types::GroupOrder::Ascending,
4430 start_group,
4431 start_object,
4432 end_group,
4433 inline_end_object()?,
4434 )
4435 .await
4436 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4437 .map_err(AnyConnectionError::from),
4438 #[cfg(feature = "draft10")]
4439 Self::Draft10(c) => c
4440 .fetch(
4441 namespace,
4442 track_name,
4443 128,
4444 moqtap_codec::types::GroupOrder::Ascending,
4445 start_group,
4446 start_object,
4447 end_group,
4448 inline_end_object()?,
4449 )
4450 .await
4451 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4452 .map_err(AnyConnectionError::from),
4453 #[cfg(feature = "draft11")]
4454 Self::Draft11(c) => c
4455 .fetch(
4456 namespace,
4457 track_name,
4458 128,
4459 moqtap_codec::types::GroupOrder::Ascending,
4460 start_group,
4461 start_object,
4462 end_group,
4463 inline_end_object()?,
4464 )
4465 .await
4466 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4467 .map_err(AnyConnectionError::from),
4468 #[cfg(feature = "draft12")]
4469 Self::Draft12(c) => c
4470 .fetch(
4471 namespace,
4472 track_name,
4473 128,
4474 moqtap_codec::types::GroupOrder::Ascending,
4475 start_group,
4476 start_object,
4477 end_group,
4478 inline_end_object()?,
4479 Vec::new(),
4480 )
4481 .await
4482 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4483 .map_err(AnyConnectionError::from),
4484 #[cfg(feature = "draft13")]
4485 Self::Draft13(c) => c
4486 .fetch(
4487 namespace,
4488 track_name,
4489 128,
4490 moqtap_codec::types::GroupOrder::Ascending,
4491 start_group,
4492 start_object,
4493 end_group,
4494 inline_end_object()?,
4495 Vec::new(),
4496 )
4497 .await
4498 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4499 .map_err(AnyConnectionError::from),
4500 #[cfg(feature = "draft14")]
4501 Self::Draft14(c) => c
4502 .fetch(
4503 namespace,
4504 track_name,
4505 // The last draft that carries these two on FETCH; see the
4506 // comment above the draft-07 arm.
4507 128,
4508 moqtap_codec::types::GroupOrder::Ascending,
4509 start_group,
4510 start_object,
4511 end_group,
4512 inline_end_object()?,
4513 Vec::new(),
4514 )
4515 .await
4516 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4517 .map_err(AnyConnectionError::from),
4518 #[cfg(feature = "draft15")]
4519 Self::Draft15(c) => c
4520 .fetch(
4521 namespace,
4522 track_name,
4523 start_group,
4524 start_object,
4525 end_group,
4526 inline_end_object()?,
4527 Vec::new(),
4528 )
4529 .await
4530 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4531 .map_err(AnyConnectionError::from),
4532 #[cfg(feature = "draft16")]
4533 Self::Draft16(c) => c
4534 .fetch(
4535 namespace,
4536 track_name,
4537 start_group,
4538 start_object,
4539 end_group,
4540 inline_end_object()?,
4541 Vec::new(),
4542 )
4543 .await
4544 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4545 .map_err(AnyConnectionError::from),
4546 #[cfg(feature = "draft17")]
4547 Self::Draft17(c) => c
4548 .fetch(
4549 namespace,
4550 track_name,
4551 start_group,
4552 start_object,
4553 end_group,
4554 inline_end_object()?,
4555 Vec::new(),
4556 )
4557 .await
4558 .map(AnyRequest::Draft17)
4559 .map_err(AnyConnectionError::from),
4560 #[cfg(feature = "draft18")]
4561 Self::Draft18(c) => c
4562 .fetch(
4563 namespace,
4564 track_name,
4565 start_group,
4566 start_object,
4567 end_group,
4568 inline_end_object()?,
4569 Vec::new(),
4570 )
4571 .await
4572 .map(AnyRequest::Draft18)
4573 .map_err(AnyConnectionError::from),
4574 #[cfg(feature = "draft19")]
4575 Self::Draft19(c) => c
4576 .fetch(
4577 namespace,
4578 track_name,
4579 start_group,
4580 start_object,
4581 end_group,
4582 inline_end_object()?,
4583 Vec::new(),
4584 )
4585 .await
4586 .map(AnyRequest::Draft19)
4587 .map_err(AnyConnectionError::from),
4588 #[cfg(feature = "draft20")]
4589 Self::Draft20(c) => {
4590 // Draft-20 Section 10.13 has no location fields to fill in:
4591 // `fetch_range` puts the whole range in the `LOCATION_FILTER`
4592 // parameter, at the position ascending Parameter Type order
4593 // requires. Nothing here adds one to the end —
4594 // draft-20 Section 5.1.2 makes the filter's range inclusive,
4595 // and the `+ 1` the six arms above apply lives in
4596 // `inline_end_object` alone.
4597 let filter = range.location_filter_draft20()?;
4598 c.fetch_range(namespace, track_name, &filter, Vec::new())
4599 .await
4600 .map(AnyRequest::Draft20)
4601 .map_err(AnyConnectionError::from)
4602 }
4603 #[cfg(feature = "draft21")]
4604 Self::Draft21(c) => {
4605 // Draft-21 Section 9.11 has no location fields to fill in:
4606 // `fetch_range` puts the whole range in the `LOCATION_FILTER`
4607 // parameter, at the position ascending Parameter Type order
4608 // requires. Nothing here adds one to the end —
4609 // draft-21 Section 3.3.1 makes the filter's range inclusive,
4610 // and the `+ 1` the six arms above apply lives in
4611 // `inline_end_object` alone.
4612 let filter = range.location_filter_draft21()?;
4613 c.fetch_range(namespace, track_name, &filter, Vec::new())
4614 .await
4615 .map(AnyRequest::Draft21)
4616 .map_err(AnyConnectionError::from)
4617 }
4618 #[cfg(feature = "draft22")]
4619 Self::Draft22(c) => {
4620 // Draft-22 Section 9.11 has no location fields to fill in:
4621 // `fetch_range` puts the whole range in the `LOCATION_FILTER`
4622 // parameter, at the position ascending Parameter Type order
4623 // requires. Nothing here adds one to the end —
4624 // draft-22 Section 3.3.1 makes the filter's range inclusive,
4625 // and the `+ 1` the six arms above apply lives in
4626 // `inline_end_object` alone.
4627 let filter = range.location_filter_draft22()?;
4628 c.fetch_range(namespace, track_name, &filter, Vec::new())
4629 .await
4630 .map(AnyRequest::Draft22)
4631 .map_err(AnyConnectionError::from)
4632 }
4633 #[cfg(feature = "draft23")]
4634 Self::Draft23(c) => {
4635 // Draft-23 Section 9.11 has no location fields to fill in:
4636 // `fetch_range` puts the whole range in the `LOCATION_FILTER`
4637 // parameter, at the position ascending Parameter Type order
4638 // requires. Nothing here adds one to the end —
4639 // draft-23 Section 3.3.1 makes the filter's range inclusive,
4640 // and the `+ 1` the six arms above apply lives in
4641 // `inline_end_object` alone.
4642 let filter = range.location_filter_draft23()?;
4643 c.fetch_range(namespace, track_name, &filter, Vec::new())
4644 .await
4645 .map(AnyRequest::Draft23)
4646 .map_err(AnyConnectionError::from)
4647 }
4648 #[allow(unreachable_patterns)]
4649 other => Err(AnyConnectionError::facade(format!(
4650 "fetch: not yet wired up for draft {:?} via AnyConnection",
4651 other.draft()
4652 ))),
4653 }
4654 }
4655
4656 /// Send a **Joining** FETCH against a subscription this session already
4657 /// holds. Drafts 08 through 19.
4658 ///
4659 /// [`AnyConnection::fetch`]'s other half, and a different question rather
4660 /// than a shorthand for the same one. A standalone FETCH names a track and
4661 /// a range and asks a relay's store for it. A Joining FETCH names **a
4662 /// subscription** and asks for the part of that subscription's track that
4663 /// precedes it, and the publisher fills in the namespace, the name and the
4664 /// end from the subscription itself. So a subscriber that wants "what I am
4665 /// watching, plus the run-up to it" has one request for the pair instead of
4666 /// a subscription and a fetch whose range it had to compute — and, on a
4667 /// live track, could not compute, because the run-up ends wherever the
4668 /// subscription happened to start.
4669 ///
4670 /// `joining_request_id` is the Request ID of that subscription, which is
4671 /// [`AnyRequest::request_id`] on the handle [`AnyConnection::subscribe`]
4672 /// returned. Nothing here checks that it names one: draft-19 Section
4673 /// 10.12.2 puts that check at the publisher — "it MUST respond with a Fetch
4674 /// Error with code Invalid Joining Request ID" — and this side is the
4675 /// subscriber. The subscription must still be live when the FETCH
4676 /// *arrives*, which is a thing about ordering rather than about this call.
4677 ///
4678 /// `start` is [`JoiningStart`], and it carries the Fetch Type as well as
4679 /// the number for the reason that type documents.
4680 ///
4681 /// # The two drafts at the ends, and what each of them deleted
4682 ///
4683 /// **Draft-07 has no Joining Fetch at all.** Its FETCH has no Fetch Type
4684 /// field, so there is no bit in the message that could ask for one; the
4685 /// field and the second Fetch Type both arrive in draft-08.
4686 ///
4687 /// **Draft-20 deleted the whole mechanism** — Section 10.13 removed the
4688 /// Fetch Type field, both payload structures and the Fetch Type registry
4689 /// together, and promoted the namespace and the name to fields of FETCH
4690 /// itself. There is no joining form to fall back to and no parameter that
4691 /// restores one, so this refuses on draft-20 rather than sending something
4692 /// adjacent. **This is the entry point that makes "one suite run per draft"
4693 /// concrete**: a relay speaking both 14 and 20 answers this question on one
4694 /// of them and cannot be asked it on the other, and a probe that tested only
4695 /// the newest draft would never learn that the relay implements it.
4696 ///
4697 /// Drafts 08 through 10 carry only the relative form, so
4698 /// [`JoiningStart::Group`] is refused there — see that variant.
4699 ///
4700 /// # What this does not carry
4701 ///
4702 /// The same three things [`AnyConnection::fetch`] leaves out, for the same
4703 /// reasons: an empty parameter list on every draft that has one (drafts 08
4704 /// through 11 have no parameter field on FETCH), and the subscriber
4705 /// priority and group order as defaults on drafts 08 through 14, which are
4706 /// the only drafts carrying them as fields of FETCH.
4707 ///
4708 /// The returned [`AnyRequest`] must be held while the fetch is live: on
4709 /// drafts 17 through 19 it owns the bidirectional stream the request went
4710 /// out on and dropping it cancels the fetch.
4711 #[allow(unused_variables)]
4712 pub async fn fetch_joining(
4713 &mut self,
4714 joining_request_id: moqtap_codec::varint::VarInt,
4715 start: JoiningStart,
4716 ) -> Result<AnyRequest, AnyConnectionError> {
4717 let draft = self.draft();
4718 let joining_start = moqtap_codec::varint::VarInt::from_u64_moqt(match start {
4719 JoiningStart::GroupsBefore(n) => n,
4720 JoiningStart::Group(g) => g,
4721 });
4722 // Refused once, here, rather than in three arms: drafts 08 through 10
4723 // define Fetch Types 0x1 and 0x2 and nothing else, and sending the
4724 // relative form with an absolute number in it would ask for the last
4725 // `g` Groups of the track when the caller asked for everything from
4726 // Group `g`.
4727 // The return type is spelled out because a single-draft build can
4728 // compile this closure without ever calling it: with only draft-20
4729 // enabled every arm that would have pinned `T` is gone, and inference
4730 // has nothing left to work from. Annotating it keeps `just draft-matrix`
4731 // green on all single-draft rows.
4732 let absolute_unavailable = || -> Result<AnyRequest, AnyConnectionError> {
4733 Err(AnyConnectionError::facade(format!(
4734 "fetch_joining: draft {draft:?} has no Absolute Joining Fetch — its FETCH offers \
4735 Fetch Types 0x1 and 0x2, and 0x3 arrives in draft-11. Ask relatively, or reach \
4736 the draft's own Connection::joining_fetch"
4737 )))
4738 };
4739 // The two fields drafts 08 through 14 carry on FETCH and no later draft
4740 // does. Sent as defaults for `fetch`'s reason: widening an entry point
4741 // shared with five drafts that have no such fields would give a caller
4742 // two arguments that are silently dropped on more than a third of them.
4743 //
4744 // Both are read only by the draft-08 through draft-14 arms, so a build
4745 // that enables none of those seven — `--features draft07,draft22` is
4746 // the pair `just draft-pairs` picks — compiles them and never reaches
4747 // them. Allowing the dead code is preferred to a seven-feature `cfg`
4748 // here: the `cfg` would have to be repeated and kept in step with the
4749 // arms below, and the day a draft joined or left that range the two
4750 // would drift apart silently, where an unused constant costs nothing
4751 // and cannot be wrong.
4752 #[allow(dead_code)]
4753 const PRIORITY: u8 = 128;
4754 #[allow(unused_variables)]
4755 let order = moqtap_codec::types::GroupOrder::Ascending;
4756 match self {
4757 // Draft-07's FETCH has no Fetch Type field, so this is not a gap to
4758 // be filled later — there is nothing in the message to set.
4759 #[cfg(feature = "draft07")]
4760 Self::Draft07(_) => Err(AnyConnectionError::facade(
4761 "fetch_joining: draft-07's FETCH has no Fetch Type field and no Joining Fetch; \
4762 draft-08 is where both arrive",
4763 )),
4764 #[cfg(feature = "draft08")]
4765 Self::Draft08(c) => match start {
4766 JoiningStart::Group(_) => absolute_unavailable(),
4767 JoiningStart::GroupsBefore(_) => c
4768 .joining_fetch(PRIORITY, order, joining_request_id, joining_start)
4769 .await
4770 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4771 .map_err(AnyConnectionError::from),
4772 },
4773 #[cfg(feature = "draft09")]
4774 Self::Draft09(c) => match start {
4775 JoiningStart::Group(_) => absolute_unavailable(),
4776 JoiningStart::GroupsBefore(_) => c
4777 .joining_fetch(PRIORITY, order, joining_request_id, joining_start)
4778 .await
4779 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4780 .map_err(AnyConnectionError::from),
4781 },
4782 #[cfg(feature = "draft10")]
4783 Self::Draft10(c) => match start {
4784 JoiningStart::Group(_) => absolute_unavailable(),
4785 JoiningStart::GroupsBefore(_) => c
4786 .joining_fetch(PRIORITY, order, joining_request_id, joining_start)
4787 .await
4788 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4789 .map_err(AnyConnectionError::from),
4790 },
4791 // Draft-11 splits Joining into the relative and absolute pair and
4792 // has no parameter block on FETCH; draft-12 adds the block.
4793 #[cfg(feature = "draft11")]
4794 Self::Draft11(c) => match start {
4795 JoiningStart::GroupsBefore(_) => {
4796 c.joining_fetch(PRIORITY, order, joining_request_id, joining_start).await
4797 }
4798 JoiningStart::Group(_) => {
4799 c.absolute_joining_fetch(PRIORITY, order, joining_request_id, joining_start)
4800 .await
4801 }
4802 }
4803 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4804 .map_err(AnyConnectionError::from),
4805 #[cfg(feature = "draft12")]
4806 Self::Draft12(c) => match start {
4807 JoiningStart::GroupsBefore(_) => {
4808 c.joining_fetch(PRIORITY, order, joining_request_id, joining_start, Vec::new())
4809 .await
4810 }
4811 JoiningStart::Group(_) => {
4812 c.absolute_joining_fetch(
4813 PRIORITY,
4814 order,
4815 joining_request_id,
4816 joining_start,
4817 Vec::new(),
4818 )
4819 .await
4820 }
4821 }
4822 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4823 .map_err(AnyConnectionError::from),
4824 #[cfg(feature = "draft13")]
4825 Self::Draft13(c) => match start {
4826 JoiningStart::GroupsBefore(_) => {
4827 c.joining_fetch(PRIORITY, order, joining_request_id, joining_start, Vec::new())
4828 .await
4829 }
4830 JoiningStart::Group(_) => {
4831 c.absolute_joining_fetch(
4832 PRIORITY,
4833 order,
4834 joining_request_id,
4835 joining_start,
4836 Vec::new(),
4837 )
4838 .await
4839 }
4840 }
4841 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4842 .map_err(AnyConnectionError::from),
4843 // The last draft carrying the priority and the order on FETCH; see
4844 // the constants above.
4845 #[cfg(feature = "draft14")]
4846 Self::Draft14(c) => match start {
4847 JoiningStart::GroupsBefore(_) => {
4848 c.joining_fetch(PRIORITY, order, joining_request_id, joining_start, Vec::new())
4849 .await
4850 }
4851 JoiningStart::Group(_) => {
4852 c.absolute_joining_fetch(
4853 PRIORITY,
4854 order,
4855 joining_request_id,
4856 joining_start,
4857 Vec::new(),
4858 )
4859 .await
4860 }
4861 }
4862 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4863 .map_err(AnyConnectionError::from),
4864 // From draft-15 the two fields are gone from FETCH and the request
4865 // is three varints and a parameter block.
4866 #[cfg(feature = "draft15")]
4867 Self::Draft15(c) => match start {
4868 JoiningStart::GroupsBefore(_) => {
4869 c.joining_fetch(joining_request_id, joining_start, Vec::new()).await
4870 }
4871 JoiningStart::Group(_) => {
4872 c.absolute_joining_fetch(joining_request_id, joining_start, Vec::new()).await
4873 }
4874 }
4875 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4876 .map_err(AnyConnectionError::from),
4877 #[cfg(feature = "draft16")]
4878 Self::Draft16(c) => match start {
4879 JoiningStart::GroupsBefore(_) => {
4880 c.joining_fetch(joining_request_id, joining_start, Vec::new()).await
4881 }
4882 JoiningStart::Group(_) => {
4883 c.absolute_joining_fetch(joining_request_id, joining_start, Vec::new()).await
4884 }
4885 }
4886 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
4887 .map_err(AnyConnectionError::from),
4888 // From draft-17 the request travels on a bidirectional stream of its
4889 // own, so the handle owns that stream rather than naming an id on a
4890 // shared one.
4891 #[cfg(feature = "draft17")]
4892 Self::Draft17(c) => match start {
4893 JoiningStart::GroupsBefore(_) => {
4894 c.joining_fetch(joining_request_id, joining_start, Vec::new()).await
4895 }
4896 JoiningStart::Group(_) => {
4897 c.absolute_joining_fetch(joining_request_id, joining_start, Vec::new()).await
4898 }
4899 }
4900 .map(AnyRequest::Draft17)
4901 .map_err(AnyConnectionError::from),
4902 #[cfg(feature = "draft18")]
4903 Self::Draft18(c) => match start {
4904 JoiningStart::GroupsBefore(_) => {
4905 c.joining_fetch(joining_request_id, joining_start, Vec::new()).await
4906 }
4907 JoiningStart::Group(_) => {
4908 c.absolute_joining_fetch(joining_request_id, joining_start, Vec::new()).await
4909 }
4910 }
4911 .map(AnyRequest::Draft18)
4912 .map_err(AnyConnectionError::from),
4913 #[cfg(feature = "draft19")]
4914 Self::Draft19(c) => match start {
4915 JoiningStart::GroupsBefore(_) => {
4916 c.joining_fetch(joining_request_id, joining_start, Vec::new()).await
4917 }
4918 JoiningStart::Group(_) => {
4919 c.absolute_joining_fetch(joining_request_id, joining_start, Vec::new()).await
4920 }
4921 }
4922 .map(AnyRequest::Draft19)
4923 .map_err(AnyConnectionError::from),
4924 // Not a gap either. Draft-20 Section 10.13 deleted the Fetch Type
4925 // field, both Fetch payload structures and the Fetch Type registry
4926 // in one rewrite, and its change log does not mention the joining
4927 // mechanism at all.
4928 #[cfg(feature = "draft20")]
4929 Self::Draft20(_) => Err(AnyConnectionError::facade(
4930 "fetch_joining: draft-20 deleted the Fetch Type field and the whole Joining Fetch \
4931 mechanism (Section 10.13); a fetch there names its own track and range, which is \
4932 AnyConnection::fetch",
4933 )),
4934 #[cfg(feature = "draft21")]
4935 Self::Draft21(_) => Err(AnyConnectionError::facade(
4936 "fetch_joining: draft-20 deleted the Fetch Type field and the whole Joining Fetch \
4937 mechanism and draft-21 keeps it gone (Section 9.11); a fetch there names its own \
4938 track and range, which is AnyConnection::fetch",
4939 )),
4940 #[cfg(feature = "draft22")]
4941 Self::Draft22(_) => Err(AnyConnectionError::facade(
4942 "fetch_joining: draft-20 deleted the Fetch Type field and the whole Joining Fetch \
4943 mechanism and draft-22 keeps it gone (Section 9.11); a fetch there names its own \
4944 track and range, which is AnyConnection::fetch",
4945 )),
4946 #[cfg(feature = "draft23")]
4947 Self::Draft23(_) => Err(AnyConnectionError::facade(
4948 "fetch_joining: draft-23 has no Joining Fetch (Section 9.11); a fetch there names \
4949 its own track and range, which is AnyConnection::fetch",
4950 )),
4951 #[allow(unreachable_patterns)]
4952 other => Err(AnyConnectionError::facade(format!(
4953 "fetch_joining: not yet wired up for draft {:?} via AnyConnection",
4954 other.draft()
4955 ))),
4956 }
4957 }
4958
4959 /// Send a TRACK_STATUS query for the given track. Supported on drafts 11
4960 /// through 22. From draft-15 on, passes an empty parameter list.
4961 ///
4962 /// # Which message this sends, and why the name moved
4963 ///
4964 /// Up to draft-12 the query is TRACK_STATUS_REQUEST and `track_status` is
4965 /// the *response* to it; draft-13 renamed the request to TRACK_STATUS and
4966 /// gave the answer its own TRACK_STATUS_OK. So this method reaches
4967 /// `Connection::track_status_request` on drafts 11 and 12 and
4968 /// `Connection::track_status` from draft-13 on — **the same question under
4969 /// two names, not the same name for two things.** Calling the
4970 /// same-named method on a draft-11 `Connection` would send a reply to a
4971 /// question nobody asked, which is why the naming test
4972 /// `an_unsent_request_is_not_named_from_the_drafts_table` exists.
4973 ///
4974 /// # Drafts 07 through 10
4975 ///
4976 /// Not supported here, and not for want of a match arm. Their
4977 /// TRACK_STATUS_REQUEST carries **no Request ID** — `Connection::
4978 /// track_status_request` returns `()` on those drafts, because the answer
4979 /// is matched by track namespace and name rather than by an identifier.
4980 /// [`AnyRequest`] is a handle to a request the endpoint numbered, so there
4981 /// is nothing for this method to return, and fabricating an ID would put a
4982 /// number in [`AnyRequest::request_id`] that was never on the wire. Send
4983 /// the query through the variant's own `Connection` and read the reply with
4984 /// [`AnyConnection::recv_response`] against any other outstanding request,
4985 /// or match it by name off `recv_and_dispatch`.
4986 ///
4987 /// The returned [`AnyRequest`] must be held until the answer arrives: on
4988 /// drafts 17-23 it owns the bidirectional stream the query went out on and
4989 /// dropping it cancels the query.
4990 #[allow(unused_variables)]
4991 pub async fn track_status(
4992 &mut self,
4993 namespace: moqtap_codec::types::TrackNamespace,
4994 track_name: Vec<u8>,
4995 ) -> Result<AnyRequest, AnyConnectionError> {
4996 let draft = self.draft();
4997 match self {
4998 // Drafts 11 and 12: TRACK_STATUS_REQUEST, which is this question's
4999 // name before draft-13 renames it. Both drafts carry a parameter
5000 // block — it arrives with draft-11 — and the two arms differ only
5001 // because this build's draft-11 entry point takes no parameter list
5002 // of its own and sends an empty one.
5003 #[cfg(feature = "draft11")]
5004 Self::Draft11(c) => c
5005 .track_status_request(namespace, track_name)
5006 .await
5007 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5008 .map_err(AnyConnectionError::from),
5009 #[cfg(feature = "draft12")]
5010 Self::Draft12(c) => c
5011 .track_status_request(namespace, track_name, Vec::new())
5012 .await
5013 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5014 .map_err(AnyConnectionError::from),
5015 // Draft-13 is where the request takes the name and the four
5016 // SUBSCRIBE-shaped fields, exactly as draft-14 carries them.
5017 #[cfg(feature = "draft13")]
5018 Self::Draft13(c) => c
5019 .track_status(
5020 namespace,
5021 track_name,
5022 128,
5023 moqtap_codec::types::GroupOrder::Ascending,
5024 moqtap_codec::types::Forward::Forward,
5025 moqtap_codec::types::FilterType::LargestObject,
5026 Vec::new(),
5027 )
5028 .await
5029 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5030 .map_err(AnyConnectionError::from),
5031 #[cfg(feature = "draft14")]
5032 Self::Draft14(c) => c
5033 .track_status(
5034 namespace,
5035 track_name,
5036 // Drafts 13 and 14 word TRACK_STATUS like a SUBSCRIBE and
5037 // no later draft does, so these four are sent as they
5038 // always were rather than widening an entry point shared
5039 // with six drafts that have no such fields.
5040 128,
5041 moqtap_codec::types::GroupOrder::Ascending,
5042 moqtap_codec::types::Forward::Forward,
5043 moqtap_codec::types::FilterType::LargestObject,
5044 Vec::new(),
5045 )
5046 .await
5047 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5048 .map_err(AnyConnectionError::from),
5049 #[cfg(feature = "draft15")]
5050 Self::Draft15(c) => c
5051 .track_status(namespace, track_name, Vec::new())
5052 .await
5053 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5054 .map_err(AnyConnectionError::from),
5055 #[cfg(feature = "draft16")]
5056 Self::Draft16(c) => c
5057 .track_status(namespace, track_name, Vec::new())
5058 .await
5059 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5060 .map_err(AnyConnectionError::from),
5061 #[cfg(feature = "draft17")]
5062 Self::Draft17(c) => c
5063 .track_status(namespace, track_name, Vec::new())
5064 .await
5065 .map(AnyRequest::Draft17)
5066 .map_err(AnyConnectionError::from),
5067 #[cfg(feature = "draft18")]
5068 Self::Draft18(c) => c
5069 .track_status(namespace, track_name, Vec::new())
5070 .await
5071 .map(AnyRequest::Draft18)
5072 .map_err(AnyConnectionError::from),
5073 #[cfg(feature = "draft19")]
5074 Self::Draft19(c) => c
5075 .track_status(namespace, track_name, Vec::new())
5076 .await
5077 .map(AnyRequest::Draft19)
5078 .map_err(AnyConnectionError::from),
5079 #[cfg(feature = "draft20")]
5080 Self::Draft20(c) => c
5081 .track_status(namespace, track_name, Vec::new())
5082 .await
5083 .map(AnyRequest::Draft20)
5084 .map_err(AnyConnectionError::from),
5085 #[cfg(feature = "draft21")]
5086 Self::Draft21(c) => c
5087 .track_status(namespace, track_name, Vec::new())
5088 .await
5089 .map(AnyRequest::Draft21)
5090 .map_err(AnyConnectionError::from),
5091 #[cfg(feature = "draft22")]
5092 Self::Draft22(c) => c
5093 .track_status(namespace, track_name, Vec::new())
5094 .await
5095 .map(AnyRequest::Draft22)
5096 .map_err(AnyConnectionError::from),
5097 #[cfg(feature = "draft23")]
5098 Self::Draft23(c) => c
5099 .track_status(namespace, track_name, Vec::new())
5100 .await
5101 .map(AnyRequest::Draft23)
5102 .map_err(AnyConnectionError::from),
5103 #[allow(unreachable_patterns)]
5104 other => Err(AnyConnectionError::facade(format!(
5105 "track_status: not yet wired up for draft {:?} via AnyConnection",
5106 other.draft()
5107 ))),
5108 }
5109 }
5110
5111 /// Send a SUBSCRIBE_NAMESPACE (or SUBSCRIBE_ANNOUNCES on drafts 11–12).
5112 /// Supported on drafts 11 through 23. Drafts 16 and 17 pass default
5113 /// subscribe options; every draft from 12 on passes an empty parameter
5114 /// list.
5115 ///
5116 /// From draft-18 this is the renumbered SUBSCRIBE_NAMESPACE (0x50), which
5117 /// asks for NAMESPACE and NAMESPACE_DONE only. SUBSCRIBE_TRACKS, the other
5118 /// half of the draft-18 split, is [`AnyConnection::subscribe_tracks`].
5119 ///
5120 /// The returned [`AnyRequest`] must be held while the request is live: on
5121 /// drafts 17-23 it owns the bidirectional stream the request went out on
5122 /// and dropping it cancels the namespace subscription.
5123 #[allow(unused_variables)]
5124 pub async fn subscribe_namespace(
5125 &mut self,
5126 namespace_prefix: moqtap_codec::types::TrackNamespace,
5127 ) -> Result<AnyRequest, AnyConnectionError> {
5128 // Only the draft-16 and draft-17 arms below build a subscribe-options
5129 // varint; the other drafts' wrappers take no such argument, so the
5130 // import is dead outside those two builds.
5131 #[cfg(any(feature = "draft16", feature = "draft17"))]
5132 use moqtap_codec::varint::VarInt;
5133 let draft = self.draft();
5134 match self {
5135 #[cfg(feature = "draft11")]
5136 Self::Draft11(c) => c
5137 .subscribe_announces(namespace_prefix)
5138 .await
5139 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5140 .map_err(AnyConnectionError::from),
5141 #[cfg(feature = "draft12")]
5142 Self::Draft12(c) => c
5143 .subscribe_announces(namespace_prefix, Vec::new())
5144 .await
5145 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5146 .map_err(AnyConnectionError::from),
5147 #[cfg(feature = "draft13")]
5148 Self::Draft13(c) => c
5149 .subscribe_namespace(namespace_prefix, Vec::new())
5150 .await
5151 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5152 .map_err(AnyConnectionError::from),
5153 #[cfg(feature = "draft14")]
5154 Self::Draft14(c) => c
5155 .subscribe_namespace(namespace_prefix, Vec::new())
5156 .await
5157 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5158 .map_err(AnyConnectionError::from),
5159 #[cfg(feature = "draft15")]
5160 Self::Draft15(c) => c
5161 .subscribe_namespace(namespace_prefix, Vec::new())
5162 .await
5163 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5164 .map_err(AnyConnectionError::from),
5165 #[cfg(feature = "draft16")]
5166 Self::Draft16(c) => {
5167 let opts = VarInt::from_u64(0).expect("0 fits in VarInt");
5168 c.subscribe_namespace(namespace_prefix, opts, Vec::new())
5169 .await
5170 .map(AnyRequest::Draft16)
5171 .map_err(AnyConnectionError::from)
5172 }
5173 #[cfg(feature = "draft17")]
5174 Self::Draft17(c) => {
5175 let opts = VarInt::from_u64(0).expect("0 fits in VarInt");
5176 c.subscribe_namespace(namespace_prefix, opts, Vec::new())
5177 .await
5178 .map(AnyRequest::Draft17)
5179 .map_err(AnyConnectionError::from)
5180 }
5181 #[cfg(feature = "draft18")]
5182 Self::Draft18(c) => c
5183 .subscribe_namespace(namespace_prefix, Vec::new())
5184 .await
5185 .map(AnyRequest::Draft18)
5186 .map_err(AnyConnectionError::from),
5187 #[cfg(feature = "draft19")]
5188 Self::Draft19(c) => c
5189 .subscribe_namespace(namespace_prefix, Vec::new())
5190 .await
5191 .map(AnyRequest::Draft19)
5192 .map_err(AnyConnectionError::from),
5193 #[cfg(feature = "draft20")]
5194 Self::Draft20(c) => c
5195 .subscribe_namespace(namespace_prefix, Vec::new())
5196 .await
5197 .map(AnyRequest::Draft20)
5198 .map_err(AnyConnectionError::from),
5199 #[cfg(feature = "draft21")]
5200 Self::Draft21(c) => c
5201 .subscribe_namespace(namespace_prefix, Vec::new())
5202 .await
5203 .map(AnyRequest::Draft21)
5204 .map_err(AnyConnectionError::from),
5205 #[cfg(feature = "draft22")]
5206 Self::Draft22(c) => c
5207 .subscribe_namespace(namespace_prefix, Vec::new())
5208 .await
5209 .map(AnyRequest::Draft22)
5210 .map_err(AnyConnectionError::from),
5211 #[cfg(feature = "draft23")]
5212 Self::Draft23(c) => c
5213 .subscribe_namespace(namespace_prefix, Vec::new())
5214 .await
5215 .map(AnyRequest::Draft23)
5216 .map_err(AnyConnectionError::from),
5217 #[allow(unreachable_patterns)]
5218 other => Err(AnyConnectionError::facade(format!(
5219 "subscribe_namespace: not yet wired up for draft {:?} via AnyConnection",
5220 other.draft()
5221 ))),
5222 }
5223 }
5224
5225 /// Offer a namespace to the peer: PUBLISH_NAMESPACE, or ANNOUNCE on the
5226 /// drafts that called it that. Supported on drafts 11 through 23. From
5227 /// draft-12 on, passes an empty parameter list.
5228 ///
5229 /// This is the first entry point here that asks the peer to **hold state**
5230 /// rather than to answer a question and forget it. A relay that accepts it
5231 /// records this session as the publisher for that namespace and will route
5232 /// matching subscriptions back down this connection, so a caller that
5233 /// announces owes the peer either a withdrawal — see
5234 /// [`AnyConnection::publish_namespace_done`] — or a closed session.
5235 ///
5236 /// # One request under two names
5237 ///
5238 /// Drafts 07 through 13 call it ANNOUNCE; draft-14 renamed it
5239 /// PUBLISH_NAMESPACE and every later draft keeps that name. The rename is
5240 /// the whole of the difference — the same namespace goes out and the same
5241 /// acceptance comes back — so this is one method rather than two, on the
5242 /// same reasoning as [`AnyConnection::track_status`], where the *request*
5243 /// changed names in the other direction.
5244 ///
5245 /// # Drafts 07 through 10
5246 ///
5247 /// Not supported here, and for the reason drafts 07 through 10 have no
5248 /// [`AnyConnection::track_status`] either: their ANNOUNCE carries **no
5249 /// Request ID**. `Connection::announce` returns `()` on those four drafts
5250 /// because ANNOUNCE_OK is matched by track namespace rather than by an
5251 /// identifier, so there is nothing for this method to hand back and a
5252 /// fabricated ID would put a number in [`AnyRequest::request_id`] that was
5253 /// never on the wire. Draft-11 is where the request ID arrives, and where
5254 /// this method starts. Send the announcement through the variant's own
5255 /// `Connection` if an older draft is the target.
5256 ///
5257 /// The returned [`AnyRequest`] must be held while the announcement is live:
5258 /// on drafts 17-23 it owns the bidirectional stream the request went out on
5259 /// and dropping it withdraws the namespace, which on those drafts is the
5260 /// only way to withdraw one.
5261 #[allow(unused_variables)]
5262 pub async fn publish_namespace(
5263 &mut self,
5264 namespace: moqtap_codec::types::TrackNamespace,
5265 ) -> Result<AnyRequest, AnyConnectionError> {
5266 let draft = self.draft();
5267 match self {
5268 // Draft-11 is the first draft to number the request, and the last
5269 // to take no parameter block.
5270 #[cfg(feature = "draft11")]
5271 Self::Draft11(c) => c
5272 .announce(namespace)
5273 .await
5274 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5275 .map_err(AnyConnectionError::from),
5276 #[cfg(feature = "draft12")]
5277 Self::Draft12(c) => c
5278 .announce(namespace, Vec::new())
5279 .await
5280 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5281 .map_err(AnyConnectionError::from),
5282 #[cfg(feature = "draft13")]
5283 Self::Draft13(c) => c
5284 .announce(namespace, Vec::new())
5285 .await
5286 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5287 .map_err(AnyConnectionError::from),
5288 // Draft-14 is the rename. Nothing else about the request moved.
5289 #[cfg(feature = "draft14")]
5290 Self::Draft14(c) => c
5291 .publish_namespace(namespace, Vec::new())
5292 .await
5293 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5294 .map_err(AnyConnectionError::from),
5295 #[cfg(feature = "draft15")]
5296 Self::Draft15(c) => c
5297 .publish_namespace(namespace, Vec::new())
5298 .await
5299 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5300 .map_err(AnyConnectionError::from),
5301 // Draft-16 gave a bidirectional stream to namespace *subscriptions*
5302 // and to nothing else, so this one stays on the control stream —
5303 // which is why the handle is a `ControlPlane` here and a stream one
5304 // line down.
5305 #[cfg(feature = "draft16")]
5306 Self::Draft16(c) => c
5307 .publish_namespace(namespace, Vec::new())
5308 .await
5309 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5310 .map_err(AnyConnectionError::from),
5311 #[cfg(feature = "draft17")]
5312 Self::Draft17(c) => c
5313 .publish_namespace(namespace, Vec::new())
5314 .await
5315 .map(AnyRequest::Draft17)
5316 .map_err(AnyConnectionError::from),
5317 #[cfg(feature = "draft18")]
5318 Self::Draft18(c) => c
5319 .publish_namespace(namespace, Vec::new())
5320 .await
5321 .map(AnyRequest::Draft18)
5322 .map_err(AnyConnectionError::from),
5323 #[cfg(feature = "draft19")]
5324 Self::Draft19(c) => c
5325 .publish_namespace(namespace, Vec::new())
5326 .await
5327 .map(AnyRequest::Draft19)
5328 .map_err(AnyConnectionError::from),
5329 #[cfg(feature = "draft20")]
5330 Self::Draft20(c) => c
5331 .publish_namespace(namespace, Vec::new())
5332 .await
5333 .map(AnyRequest::Draft20)
5334 .map_err(AnyConnectionError::from),
5335 #[cfg(feature = "draft21")]
5336 Self::Draft21(c) => c
5337 .publish_namespace(namespace, Vec::new())
5338 .await
5339 .map(AnyRequest::Draft21)
5340 .map_err(AnyConnectionError::from),
5341 #[cfg(feature = "draft22")]
5342 Self::Draft22(c) => c
5343 .publish_namespace(namespace, Vec::new())
5344 .await
5345 .map(AnyRequest::Draft22)
5346 .map_err(AnyConnectionError::from),
5347 #[cfg(feature = "draft23")]
5348 Self::Draft23(c) => c
5349 .publish_namespace(namespace, Vec::new())
5350 .await
5351 .map(AnyRequest::Draft23)
5352 .map_err(AnyConnectionError::from),
5353 #[allow(unreachable_patterns)]
5354 other => Err(AnyConnectionError::facade(format!(
5355 "publish_namespace: not yet wired up for draft {:?} via AnyConnection",
5356 other.draft()
5357 ))),
5358 }
5359 }
5360
5361 /// Withdraw a namespace this session announced: PUBLISH_NAMESPACE_DONE, or
5362 /// UNANNOUNCE on the drafts that called it that. Drafts 11 through 16.
5363 ///
5364 /// # Why this takes both the handle and the namespace
5365 ///
5366 /// Because the drafts disagree about which of the two identifies the
5367 /// announcement being withdrawn, and they disagree twice:
5368 ///
5369 /// - **Drafts 11 through 15** name the namespace. UNANNOUNCE and, from
5370 /// draft-14, PUBLISH_NAMESPACE_DONE carry the tuple itself.
5371 /// - **Draft-16** names the request ID, having moved the whole message onto
5372 /// the identifier the announcement was allocated.
5373 /// - **Drafts 17 through 23** name neither, because there is no message: the
5374 /// announcement lives exactly as long as its bidirectional stream, so
5375 /// withdrawing one is [`AnyRequest::cancel`] or simply dropping the
5376 /// handle. Those drafts return an error here rather than silently doing
5377 /// nothing, on the same terms as [`AnyConnection::unsubscribe`] — it is
5378 /// not a gap waiting to be wired, there is nothing to send.
5379 ///
5380 /// A signature taking only the namespace would be wrong on draft-16 and one
5381 /// taking only the handle would be wrong on the five drafts before it.
5382 /// Taking both keeps the caller from having to know which era it is in,
5383 /// which is the entire point of this facade.
5384 ///
5385 /// `request` is borrowed rather than consumed: on drafts 11 through 16 it
5386 /// carries no stream, so the caller keeps a handle that is still good for
5387 /// [`AnyRequest::request_id`] afterwards.
5388 #[allow(unused_variables)]
5389 pub async fn publish_namespace_done(
5390 &mut self,
5391 request: &AnyRequest,
5392 namespace: moqtap_codec::types::TrackNamespace,
5393 ) -> Result<(), AnyConnectionError> {
5394 match self {
5395 #[cfg(feature = "draft11")]
5396 Self::Draft11(c) => c.unannounce(namespace).await.map_err(AnyConnectionError::from),
5397 #[cfg(feature = "draft12")]
5398 Self::Draft12(c) => c.unannounce(namespace).await.map_err(AnyConnectionError::from),
5399 #[cfg(feature = "draft13")]
5400 Self::Draft13(c) => c.unannounce(namespace).await.map_err(AnyConnectionError::from),
5401 // Draft-14 renamed UNANNOUNCE to PUBLISH_NAMESPACE_DONE and kept
5402 // the namespace on it; draft-16 swapped the namespace for the
5403 // request ID.
5404 #[cfg(feature = "draft14")]
5405 Self::Draft14(c) => {
5406 c.publish_namespace_done(namespace).await.map_err(AnyConnectionError::from)
5407 }
5408 #[cfg(feature = "draft15")]
5409 Self::Draft15(c) => {
5410 c.publish_namespace_done(namespace).await.map_err(AnyConnectionError::from)
5411 }
5412 #[cfg(feature = "draft16")]
5413 Self::Draft16(c) => c
5414 .publish_namespace_done(request.request_id())
5415 .await
5416 .map_err(AnyConnectionError::from),
5417 #[allow(unreachable_patterns)]
5418 other => Err(AnyConnectionError::facade(format!(
5419 "publish_namespace_done: draft {:?} withdraws a namespace by ending the \
5420 announcement's own stream, so there is no message to send; cancel or drop \
5421 the request handle instead",
5422 other.draft()
5423 ))),
5424 }
5425 }
5426
5427 /// Offer the peer a track: PUBLISH. Drafts 12 through 23.
5428 ///
5429 /// Like [`AnyConnection::publish_namespace`], this asks the peer to hold
5430 /// state: a relay that accepts records this session as the track's
5431 /// publisher and forwards the offer to whoever subscribed to a matching
5432 /// prefix. A caller whose offer was accepted owes the peer
5433 /// [`AnyConnection::publish_done`] or a closed session.
5434 ///
5435 /// # Why `forward` is the one choice offered
5436 ///
5437 /// It is the only property of the offer every draft can express — a field
5438 /// on drafts 12 through 14, the FORWARD parameter (key 0x10) from draft-15
5439 /// on — and it decides whether the peer should expect objects at all.
5440 /// [`Forward::DontForward`](moqtap_codec::types::Forward::DontForward)
5441 /// offers a track without promising any. Everything else is sent as the
5442 /// emptiest form its draft allows: ascending group order and no Largest
5443 /// Location on drafts 12 through 14, and no track extensions or
5444 /// properties after that.
5445 ///
5446 /// # What comes back, and where
5447 ///
5448 /// Drafts 12 through 16 carry the offer on the control stream and the
5449 /// handle is a [`AnyRequest::ControlPlane`]; the answer is PUBLISH_OK or
5450 /// PUBLISH_ERROR on 12 through 14 and PUBLISH_OK or REQUEST_ERROR on 15 and
5451 /// 16, correlated by Request ID. From draft-17 the offer owns a
5452 /// bidirectional stream, and from draft-18 the acceptance on it is
5453 /// REQUEST_OK. Read either with [`AnyConnection::recv_response`].
5454 ///
5455 /// Drafts 07 through 11 have no PUBLISH and are refused.
5456 ///
5457 /// The returned [`AnyRequest`] must be held while the offer is live: on
5458 /// drafts 17-23 dropping it resets the stream, which cancels the offer.
5459 #[allow(unused_variables)]
5460 pub async fn publish(
5461 &mut self,
5462 namespace: moqtap_codec::types::TrackNamespace,
5463 track_name: Vec<u8>,
5464 track_alias: moqtap_codec::varint::VarInt,
5465 forward: moqtap_codec::types::Forward,
5466 ) -> Result<AnyRequest, AnyConnectionError> {
5467 let draft = self.draft();
5468 match self {
5469 // Drafts 12 through 14 carry Forward inline. `None` for the Largest
5470 // Location is what writes Content Exists as 0.
5471 #[cfg(feature = "draft12")]
5472 Self::Draft12(c) => c
5473 .publish(
5474 namespace,
5475 track_name,
5476 track_alias,
5477 moqtap_codec::types::GroupOrder::Ascending,
5478 None,
5479 forward,
5480 Vec::new(),
5481 )
5482 .await
5483 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5484 .map_err(AnyConnectionError::from),
5485 #[cfg(feature = "draft13")]
5486 Self::Draft13(c) => c
5487 .publish(
5488 namespace,
5489 track_name,
5490 track_alias,
5491 moqtap_codec::types::GroupOrder::Ascending,
5492 None,
5493 forward,
5494 Vec::new(),
5495 )
5496 .await
5497 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5498 .map_err(AnyConnectionError::from),
5499 #[cfg(feature = "draft14")]
5500 Self::Draft14(c) => c
5501 .publish(
5502 namespace,
5503 track_name,
5504 track_alias,
5505 moqtap_codec::types::GroupOrder::Ascending,
5506 None,
5507 forward,
5508 Vec::new(),
5509 )
5510 .await
5511 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5512 .map_err(AnyConnectionError::from),
5513 #[cfg(feature = "draft15")]
5514 Self::Draft15(c) => c
5515 .publish(namespace, track_name, track_alias, forward_parameter(forward))
5516 .await
5517 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5518 .map_err(AnyConnectionError::from),
5519 // Draft-16's helper takes the track extensions *before* the
5520 // parameters, the reverse of the order drafts 17 on take their
5521 // properties in.
5522 #[cfg(feature = "draft16")]
5523 Self::Draft16(c) => c
5524 .publish(namespace, track_name, track_alias, Vec::new(), forward_parameter(forward))
5525 .await
5526 .map(|request_id| AnyRequest::ControlPlane { request_id, draft })
5527 .map_err(AnyConnectionError::from),
5528 #[cfg(feature = "draft17")]
5529 Self::Draft17(c) => c
5530 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5531 .await
5532 .map(AnyRequest::Draft17)
5533 .map_err(AnyConnectionError::from),
5534 #[cfg(feature = "draft18")]
5535 Self::Draft18(c) => c
5536 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5537 .await
5538 .map(AnyRequest::Draft18)
5539 .map_err(AnyConnectionError::from),
5540 #[cfg(feature = "draft19")]
5541 Self::Draft19(c) => c
5542 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5543 .await
5544 .map(AnyRequest::Draft19)
5545 .map_err(AnyConnectionError::from),
5546 #[cfg(feature = "draft20")]
5547 Self::Draft20(c) => c
5548 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5549 .await
5550 .map(AnyRequest::Draft20)
5551 .map_err(AnyConnectionError::from),
5552 #[cfg(feature = "draft21")]
5553 Self::Draft21(c) => c
5554 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5555 .await
5556 .map(AnyRequest::Draft21)
5557 .map_err(AnyConnectionError::from),
5558 #[cfg(feature = "draft22")]
5559 Self::Draft22(c) => c
5560 .publish(namespace, track_name, track_alias, forward_parameter(forward), Vec::new())
5561 .await
5562 .map(AnyRequest::Draft22)
5563 .map_err(AnyConnectionError::from),
5564 #[cfg(feature = "draft23")]
5565 Self::Draft23(c) => c
5566 .publish(
5567 namespace,
5568 track_name,
5569 track_alias,
5570 crate::draft23::publish::paused_parameter(forward),
5571 Vec::new(),
5572 )
5573 .await
5574 .map(AnyRequest::Draft23)
5575 .map_err(AnyConnectionError::from),
5576 #[allow(unreachable_patterns)]
5577 other => Err(AnyConnectionError::facade(format!(
5578 "publish: PUBLISH first appears in draft-12; draft {:?} has no such message",
5579 other.draft()
5580 ))),
5581 }
5582 }
5583
5584 /// End a publication this session offered with
5585 /// [`AnyConnection::publish`]: PUBLISH_DONE, or SUBSCRIBE_DONE on drafts
5586 /// 12 and 13, which end a PUBLISH-established subscription with the same
5587 /// message as any other. Drafts 12 through 23.
5588 ///
5589 /// Only after the peer accepted. Every draft's endpoint refuses the done
5590 /// for an offer that was rejected or never answered, because there is no
5591 /// subscription to end: a rejected offer owes nothing, and an unanswered
5592 /// one ends with the session on drafts 12 through 16 and with the handle's
5593 /// reset from draft-17.
5594 ///
5595 /// `stream_count` is the number of data streams the publication opened,
5596 /// which is `0` for an offer that sent no objects. **Draft-14 ignores it**:
5597 /// that draft's endpoint writes 0 itself, which is right for a publication
5598 /// that opened no streams and wrong for one that did.
5599 ///
5600 /// # The FIN after it
5601 ///
5602 /// From draft-17 the done is the last message on the offer's own stream,
5603 /// and draft-18 Section 10.11 asks for the FIN straight after it: "A sender
5604 /// SHOULD send FIN on the subscription's bidi stream immediately after
5605 /// sending PUBLISH_DONE." Drafts 19 through 23 send it inside their own
5606 /// helper. The draft-17 and draft-18 helpers leave the send half open, so
5607 /// this finishes it for them; a finished handle does nothing on drop, so
5608 /// the publication is not then cancelled by the drop that follows.
5609 ///
5610 /// `request` is borrowed: on drafts 12 through 16 the handle carries no
5611 /// stream, and from draft-17 the receive half stays open after the FIN.
5612 #[allow(unused_variables)]
5613 pub async fn publish_done(
5614 &mut self,
5615 request: &mut AnyRequest,
5616 status_code: moqtap_codec::varint::VarInt,
5617 stream_count: moqtap_codec::varint::VarInt,
5618 reason: Vec<u8>,
5619 ) -> Result<(), AnyConnectionError> {
5620 match (self, request) {
5621 #[cfg(feature = "draft12")]
5622 (Self::Draft12(c), AnyRequest::ControlPlane { request_id, .. }) => c
5623 .subscribe_done(*request_id, status_code, stream_count, reason)
5624 .await
5625 .map_err(AnyConnectionError::from),
5626 #[cfg(feature = "draft13")]
5627 (Self::Draft13(c), AnyRequest::ControlPlane { request_id, .. }) => c
5628 .subscribe_done(*request_id, status_code, stream_count, reason)
5629 .await
5630 .map_err(AnyConnectionError::from),
5631 #[cfg(feature = "draft14")]
5632 (Self::Draft14(c), AnyRequest::ControlPlane { request_id, .. }) => c
5633 .publish_done(*request_id, status_code, reason)
5634 .await
5635 .map_err(AnyConnectionError::from),
5636 #[cfg(feature = "draft15")]
5637 (Self::Draft15(c), AnyRequest::ControlPlane { request_id, .. }) => c
5638 .publish_done(*request_id, status_code, stream_count, reason)
5639 .await
5640 .map_err(AnyConnectionError::from),
5641 #[cfg(feature = "draft16")]
5642 (Self::Draft16(c), AnyRequest::ControlPlane { request_id, .. }) => c
5643 .publish_done(*request_id, status_code, stream_count, reason)
5644 .await
5645 .map_err(AnyConnectionError::from),
5646 #[cfg(feature = "draft17")]
5647 (Self::Draft17(c), AnyRequest::Draft17(s)) => {
5648 c.publish_done(s, status_code, stream_count, reason)
5649 .await
5650 .map_err(AnyConnectionError::from)?;
5651 s.finish().await.map_err(AnyConnectionError::from)
5652 }
5653 #[cfg(feature = "draft18")]
5654 (Self::Draft18(c), AnyRequest::Draft18(s)) => {
5655 c.publish_done(s, status_code, stream_count, reason)
5656 .await
5657 .map_err(AnyConnectionError::from)?;
5658 s.finish().await.map_err(AnyConnectionError::from)
5659 }
5660 #[cfg(feature = "draft19")]
5661 (Self::Draft19(c), AnyRequest::Draft19(s)) => c
5662 .publish_done(s, status_code, stream_count, reason)
5663 .await
5664 .map_err(AnyConnectionError::from),
5665 #[cfg(feature = "draft20")]
5666 (Self::Draft20(c), AnyRequest::Draft20(s)) => c
5667 .publish_done(s, status_code, stream_count, reason)
5668 .await
5669 .map_err(AnyConnectionError::from),
5670 #[cfg(feature = "draft21")]
5671 (Self::Draft21(c), AnyRequest::Draft21(s)) => c
5672 .publish_done(s, status_code, stream_count, reason)
5673 .await
5674 .map_err(AnyConnectionError::from),
5675 #[cfg(feature = "draft22")]
5676 (Self::Draft22(c), AnyRequest::Draft22(s)) => c
5677 .publish_done(s, status_code, stream_count, reason)
5678 .await
5679 .map_err(AnyConnectionError::from),
5680 #[cfg(feature = "draft23")]
5681 (Self::Draft23(c), AnyRequest::Draft23(s)) => c
5682 .publish_done(s, status_code, stream_count, reason)
5683 .await
5684 .map_err(AnyConnectionError::from),
5685 // A handle from another draft, one `publish` never returns (a
5686 // draft-16 namespace subscription), a draft with no PUBLISH, or a
5687 // build with no drafts enabled.
5688 #[allow(unreachable_patterns)]
5689 (connection, request) => {
5690 let (on, of) = (connection.draft(), request.draft());
5691 Err(AnyConnectionError::facade(if on == of {
5692 format!(
5693 "publish_done: this draft {on:?} handle is not one `publish` returns; \
5694 PUBLISH first appears in draft-12"
5695 )
5696 } else {
5697 format!(
5698 "publish_done: a draft {of:?} request cannot be ended on a draft {on:?} \
5699 connection"
5700 )
5701 }))
5702 }
5703 }
5704 }
5705
5706 /// Ask the peer to offer every track under a namespace prefix:
5707 /// SUBSCRIBE_TRACKS. Drafts 18 through 23.
5708 ///
5709 /// The other half of the draft-18 split described on
5710 /// [`AnyConnection::subscribe_namespace`]: that one asks for namespaces,
5711 /// this one for the tracks inside them. A peer that accepts answers
5712 /// REQUEST_OK on the request's own stream and then sends a PUBLISH, each on
5713 /// a new bidirectional stream, for every matching track — and this side
5714 /// owes each of those an answer. A caller that wants the answer and not
5715 /// the tracks names a prefix nothing matches.
5716 ///
5717 /// `forward` goes out as the FORWARD parameter, and the PUBLISH messages
5718 /// the subscription brings carry it.
5719 ///
5720 /// # Ending it
5721 ///
5722 /// Hold the handle for as long as the subscription should stand, and drop
5723 /// it or [`AnyRequest::cancel`] it to end the subscription: both reset the
5724 /// stream. What a FIN does depends on the draft. Draft-18 counts it as a
5725 /// cancellation: "A SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS can be
5726 /// cancelled by closing the stream with either a FIN or RESET_STREAM."
5727 /// Drafts 19 through 23 do not — draft-23 Section 6.4.2.2: "A FIN only
5728 /// indicates that an endpoint will send no further messages in that
5729 /// direction; it is not a request cancellation."
5730 ///
5731 /// # Drafts 12 through 17
5732 ///
5733 /// Refused here, though the question exists: those drafts ask it with the
5734 /// namespace subscription [`AnyConnection::subscribe_namespace`] sends. On
5735 /// drafts 12 through 15 every SUBSCRIBE_ANNOUNCES or SUBSCRIBE_NAMESPACE
5736 /// brings the matching PUBLISH messages, with no way to decline them; on
5737 /// drafts 16 and 17 Subscribe Options value 0x00 requests them, and that is
5738 /// the value the facade sends. Drafts before 12 have no PUBLISH.
5739 #[allow(unused_variables)]
5740 pub async fn subscribe_tracks(
5741 &mut self,
5742 namespace_prefix: moqtap_codec::types::TrackNamespace,
5743 forward: moqtap_codec::types::Forward,
5744 ) -> Result<AnyRequest, AnyConnectionError> {
5745 match self {
5746 #[cfg(feature = "draft18")]
5747 Self::Draft18(c) => c
5748 .subscribe_tracks(namespace_prefix, forward_parameter(forward))
5749 .await
5750 .map(AnyRequest::Draft18)
5751 .map_err(AnyConnectionError::from),
5752 #[cfg(feature = "draft19")]
5753 Self::Draft19(c) => c
5754 .subscribe_tracks(namespace_prefix, forward_parameter(forward))
5755 .await
5756 .map(AnyRequest::Draft19)
5757 .map_err(AnyConnectionError::from),
5758 #[cfg(feature = "draft20")]
5759 Self::Draft20(c) => c
5760 .subscribe_tracks(namespace_prefix, forward_parameter(forward))
5761 .await
5762 .map(AnyRequest::Draft20)
5763 .map_err(AnyConnectionError::from),
5764 #[cfg(feature = "draft21")]
5765 Self::Draft21(c) => c
5766 .subscribe_tracks(namespace_prefix, forward_parameter(forward))
5767 .await
5768 .map(AnyRequest::Draft21)
5769 .map_err(AnyConnectionError::from),
5770 #[cfg(feature = "draft22")]
5771 Self::Draft22(c) => c
5772 .subscribe_tracks(namespace_prefix, forward_parameter(forward))
5773 .await
5774 .map(AnyRequest::Draft22)
5775 .map_err(AnyConnectionError::from),
5776 #[cfg(feature = "draft23")]
5777 Self::Draft23(c) => c
5778 .subscribe_tracks(
5779 namespace_prefix,
5780 crate::draft23::publish::paused_parameter(forward),
5781 )
5782 .await
5783 .map(AnyRequest::Draft23)
5784 .map_err(AnyConnectionError::from),
5785 #[allow(unreachable_patterns)]
5786 other => Err(AnyConnectionError::facade(format!(
5787 "subscribe_tracks: SUBSCRIBE_TRACKS first appears in draft-18; draft {:?} has \
5788 no such message (drafts 12 through 17 bring tracks with \
5789 subscribe_namespace: always on 12 to 15, through Subscribe Options on 16 \
5790 and 17)",
5791 other.draft()
5792 ))),
5793 }
5794 }
5795
5796 /// Wait for the peer to send something, and say whether it is a request
5797 /// this facade can answer. Supported on **every draft this build carries**.
5798 ///
5799 /// This is the direction [`AnyConnection::recv_response`] does not cover: a
5800 /// relay that accepted a namespace from [`AnyConnection::publish_namespace`]
5801 /// will send a SUBSCRIBE *down* this connection when somebody asks for a
5802 /// track under it, and that message answers nothing this side asked for.
5803 ///
5804 /// # What "wait" means on either side of draft-17
5805 ///
5806 /// Two different waits for one question. Drafts 07 through 16 read the
5807 /// shared control stream, so every message the peer sends — its requests
5808 /// and its answers to this side's — arrives through here and is returned,
5809 /// tagged. From draft-17 this waits on a **new bidirectional stream**, so
5810 /// only the peer's own requests arrive and an answer to something this side
5811 /// asked is read with [`AnyConnection::recv_response`] instead.
5812 ///
5813 /// That is why [`AnyArrival::Other`] exists rather than a filter. On the
5814 /// older drafts a reader that dropped what it was not looking for would
5815 /// swallow a SUBSCRIBE_OK somebody was waiting on; see that variant for
5816 /// what it costs on the newer ones.
5817 ///
5818 /// Every message is dispatched into the endpoint before it is returned, so
5819 /// the session's state is correct whether the caller inspects it or not.
5820 #[allow(unused_variables)]
5821 pub async fn recv_inbound(&mut self) -> Result<AnyArrival, AnyConnectionError> {
5822 // Used only by the per-draft arms below, so a build with no draft
5823 // feature enabled — the `<zero drafts>` row of `just draft-matrix`,
5824 // which exists to prove the crate still compiles with nothing
5825 // selected — compiles this import and reaches no arm that reads it.
5826 #[allow(unused_imports)]
5827 use moqtap_codec::dispatch::AnyControlMessage;
5828
5829 let draft = self.draft();
5830
5831 // The shared-control-stream drafts, whose only difference here is what
5832 // their SUBSCRIBE calls the field holding the peer's Request ID:
5833 // `subscribe_id` up to draft-10 and `request_id` from draft-11. The id
5834 // is read out before the message is wrapped, because wrapping moves it.
5835 #[allow(unused_macros)]
5836 macro_rules! control_plane {
5837 ($conn:ident, $version:ident, $module:ident, $id_field:ident) => {{
5838 let message = $conn.recv_and_dispatch().await.map_err(AnyConnectionError::from)?;
5839 let request_id = match &message {
5840 moqtap_codec::$module::message::ControlMessage::Subscribe(s) => {
5841 Some(s.$id_field)
5842 }
5843 _ => None,
5844 };
5845 let message = AnyControlMessage::$version(message);
5846 Ok(match request_id {
5847 Some(request_id) => AnyArrival::Subscribe {
5848 message,
5849 request: AnyInboundRequest::ControlPlane { request_id, draft },
5850 },
5851 None => AnyArrival::Other(message),
5852 })
5853 }};
5854 }
5855
5856 // The request-stream drafts. A request this facade cannot answer is
5857 // refused with REQUEST_ERROR NOT_SUPPORTED and its stream finished —
5858 // see [`AnyArrival::Other`]. `$extra` fills the fields REQUEST_ERROR
5859 // gained after draft-17.
5860 #[allow(unused_macros)]
5861 macro_rules! request_stream {
5862 ($conn:ident, $version:ident, $module:ident $(, $extra:ident: $value:expr)*) => {{
5863 let (message, mut stream) =
5864 $conn.accept_request_stream().await.map_err(AnyConnectionError::from)?;
5865 let subscribe =
5866 matches!(message, moqtap_codec::$module::message::ControlMessage::Subscribe(_));
5867 let message = AnyControlMessage::$version(message);
5868 if subscribe {
5869 return Ok(AnyArrival::Subscribe {
5870 message,
5871 request: AnyInboundRequest::$version(stream),
5872 });
5873 }
5874 let refusal = moqtap_codec::$module::message::RequestError {
5875 error_code: moqtap_codec::varint::VarInt::from_u64(
5876 moqtap_codec::$module::error_codes::RequestErrorCode::NotSupported as u64,
5877 )
5878 .expect("a request error code fits a varint"),
5879 retry_interval: moqtap_codec::varint::VarInt::from_u64(0).expect("zero fits a varint"),
5880 reason_phrase: Vec::new(),
5881 $($extra: $value,)*
5882 };
5883 // A failed answer falls back to the reset the handle's drop
5884 // sends, so it is not this call's error.
5885 let _ = $conn.respond_error(&mut stream, refusal).await;
5886 Ok(AnyArrival::Other(message))
5887 }};
5888 }
5889
5890 match self {
5891 #[cfg(feature = "draft07")]
5892 Self::Draft07(c) => control_plane!(c, Draft07, draft07, subscribe_id),
5893 #[cfg(feature = "draft08")]
5894 Self::Draft08(c) => control_plane!(c, Draft08, draft08, subscribe_id),
5895 #[cfg(feature = "draft09")]
5896 Self::Draft09(c) => control_plane!(c, Draft09, draft09, subscribe_id),
5897 #[cfg(feature = "draft10")]
5898 Self::Draft10(c) => control_plane!(c, Draft10, draft10, subscribe_id),
5899 #[cfg(feature = "draft11")]
5900 Self::Draft11(c) => control_plane!(c, Draft11, draft11, request_id),
5901 #[cfg(feature = "draft12")]
5902 Self::Draft12(c) => control_plane!(c, Draft12, draft12, request_id),
5903 #[cfg(feature = "draft13")]
5904 Self::Draft13(c) => control_plane!(c, Draft13, draft13, request_id),
5905 #[cfg(feature = "draft14")]
5906 Self::Draft14(c) => control_plane!(c, Draft14, draft14, request_id),
5907 #[cfg(feature = "draft15")]
5908 Self::Draft15(c) => control_plane!(c, Draft15, draft15, request_id),
5909 // Draft-16 gave a stream to namespace subscriptions and to nothing
5910 // else, so an inbound SUBSCRIBE is still a control-stream message
5911 // here and the handle is still a `ControlPlane`.
5912 #[cfg(feature = "draft16")]
5913 Self::Draft16(c) => control_plane!(c, Draft16, draft16, request_id),
5914 #[cfg(feature = "draft17")]
5915 Self::Draft17(c) => request_stream!(c, Draft17, draft17),
5916 #[cfg(feature = "draft18")]
5917 Self::Draft18(c) => request_stream!(c, Draft18, draft18, redirect: None),
5918 #[cfg(feature = "draft19")]
5919 Self::Draft19(c) => request_stream!(c, Draft19, draft19, redirect: None),
5920 #[cfg(feature = "draft20")]
5921 Self::Draft20(c) => request_stream!(c, Draft20, draft20, redirect: None),
5922 #[cfg(feature = "draft21")]
5923 Self::Draft21(c) => request_stream!(c, Draft21, draft21, redirect: None),
5924 #[cfg(feature = "draft22")]
5925 Self::Draft22(c) => request_stream!(c, Draft22, draft22, redirect: None),
5926 #[cfg(feature = "draft23")]
5927 Self::Draft23(c) => request_stream!(c, Draft23, draft23, error_details: Vec::new()),
5928 #[allow(unreachable_patterns)]
5929 other => Err(AnyConnectionError::facade(format!(
5930 "recv_inbound: not yet wired up for draft {:?} via AnyConnection",
5931 other.draft()
5932 ))),
5933 }
5934 }
5935
5936 /// Answer a peer's SUBSCRIBE with SUBSCRIBE_OK. Supported on **every draft
5937 /// this build carries**.
5938 ///
5939 /// `request` is the handle from [`AnyArrival::Subscribe`]. What the unified
5940 /// shape cannot express is defaulted: no expiry, ascending group order, and
5941 /// no parameters, properties or track extensions — every draft's own
5942 /// `Connection::subscribe_ok` is reachable through the variant for a
5943 /// responder that wants any of them.
5944 ///
5945 /// # The Track Alias, and why it *is* an argument here
5946 ///
5947 /// The opposite of [`AnyConnection::subscribe`], and not by inconsistency.
5948 /// Sending a SUBSCRIBE, the alias is bookkeeping the caller has no opinion
5949 /// about — any free one names the track locally — so it is read off the
5950 /// endpoint. Answering one, the alias is what **this side's objects will
5951 /// carry on the wire**, and draft-14 Section 9.8 and draft-14 Section 9.13
5952 /// require one alias to name one track: two subscriptions to the same track
5953 /// must be answered with the *same* alias, and a fresh one per subscription
5954 /// would be wrong. The endpoint cannot know which track a caller considers
5955 /// this to be, so the choice is the caller's and cannot be inferred.
5956 ///
5957 /// **Drafts 07 through 11 ignore it.** Those drafts put the Track Alias on
5958 /// SUBSCRIBE and make it the subscriber's, so their SUBSCRIBE_OK has no
5959 /// such field and the value that counts already arrived — it is on the
5960 /// message in [`AnyArrival::Subscribe`]. The argument is accepted and
5961 /// dropped there rather than the signature splitting in two.
5962 ///
5963 /// # Drafts 07 through 10 have an arm here and no way to be reached
5964 ///
5965 /// Not a gap in this call. A request is legal only below the ceiling its
5966 /// recipient granted, and a ceiling nobody granted is zero, which forbids
5967 /// every request. Drafts 11 and later let this side grant one in
5968 /// CLIENT_SETUP, as Setup Parameter `0x02`; drafts 07 through 10 grant it
5969 /// with the MAX_SUBSCRIBE_ID **message**, type `0x15`, and no `Connection`
5970 /// in this crate has an entry point for sending one. So a peer's SUBSCRIBE
5971 /// on those four drafts is refused for exceeding a ceiling of zero long
5972 /// before it reaches here.
5973 ///
5974 /// The arms stay because they are right and become reachable the day that
5975 /// message can be sent, without being touched.
5976 #[allow(unused_variables)]
5977 pub async fn accept_subscribe(
5978 &mut self,
5979 request: &mut AnyInboundRequest,
5980 track_alias: moqtap_codec::varint::VarInt,
5981 ) -> Result<(), AnyConnectionError> {
5982 use moqtap_codec::varint::VarInt;
5983
5984 // No expiry, which every draft carrying the field spells as zero.
5985 let never_expires = VarInt::from_u64(0).expect("0 fits in VarInt");
5986
5987 match (self, request) {
5988 // Drafts 07 through 11: the subscriber chose the alias, so
5989 // SUBSCRIBE_OK has nowhere to put one.
5990 #[cfg(feature = "draft07")]
5991 (Self::Draft07(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
5992 .subscribe_ok(
5993 *request_id,
5994 never_expires,
5995 moqtap_codec::types::GroupOrder::Ascending,
5996 Vec::new(),
5997 )
5998 .await
5999 .map_err(AnyConnectionError::from),
6000 #[cfg(feature = "draft08")]
6001 (Self::Draft08(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6002 .subscribe_ok(
6003 *request_id,
6004 never_expires,
6005 moqtap_codec::types::GroupOrder::Ascending,
6006 Vec::new(),
6007 )
6008 .await
6009 .map_err(AnyConnectionError::from),
6010 #[cfg(feature = "draft09")]
6011 (Self::Draft09(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6012 .subscribe_ok(
6013 *request_id,
6014 never_expires,
6015 moqtap_codec::types::GroupOrder::Ascending,
6016 Vec::new(),
6017 )
6018 .await
6019 .map_err(AnyConnectionError::from),
6020 #[cfg(feature = "draft10")]
6021 (Self::Draft10(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6022 .subscribe_ok(
6023 *request_id,
6024 never_expires,
6025 moqtap_codec::types::GroupOrder::Ascending,
6026 Vec::new(),
6027 )
6028 .await
6029 .map_err(AnyConnectionError::from),
6030 #[cfg(feature = "draft11")]
6031 (Self::Draft11(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6032 .subscribe_ok(
6033 *request_id,
6034 never_expires,
6035 moqtap_codec::types::GroupOrder::Ascending,
6036 Vec::new(),
6037 )
6038 .await
6039 .map_err(AnyConnectionError::from),
6040 // Draft-12 moved the alias here, and it stays until draft-17 moves
6041 // the whole response onto the request's own stream.
6042 #[cfg(feature = "draft12")]
6043 (Self::Draft12(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6044 .subscribe_ok(
6045 *request_id,
6046 track_alias,
6047 never_expires,
6048 moqtap_codec::types::GroupOrder::Ascending,
6049 Vec::new(),
6050 )
6051 .await
6052 .map_err(AnyConnectionError::from),
6053 #[cfg(feature = "draft13")]
6054 (Self::Draft13(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6055 .subscribe_ok(
6056 *request_id,
6057 track_alias,
6058 never_expires,
6059 moqtap_codec::types::GroupOrder::Ascending,
6060 Vec::new(),
6061 )
6062 .await
6063 .map_err(AnyConnectionError::from),
6064 #[cfg(feature = "draft14")]
6065 (Self::Draft14(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6066 .subscribe_ok(
6067 *request_id,
6068 track_alias,
6069 never_expires,
6070 moqtap_codec::types::GroupOrder::Ascending,
6071 Vec::new(),
6072 )
6073 .await
6074 .map_err(AnyConnectionError::from),
6075 // Draft-15 turned expiry and group order into parameters, so the
6076 // two arguments above become an empty list rather than defaults.
6077 #[cfg(feature = "draft15")]
6078 (Self::Draft15(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6079 .subscribe_ok(*request_id, track_alias, Vec::new())
6080 .await
6081 .map_err(AnyConnectionError::from),
6082 // Draft-16 added a Track Extensions list beside the parameters.
6083 #[cfg(feature = "draft16")]
6084 (Self::Draft16(c), AnyInboundRequest::ControlPlane { request_id, .. }) => c
6085 .subscribe_ok(*request_id, track_alias, Vec::new(), Vec::new())
6086 .await
6087 .map_err(AnyConnectionError::from),
6088 // Drafts 17 through 20: the response goes back on the request's own
6089 // stream, which is why it carries no Request ID — the stream is the
6090 // correlation.
6091 #[cfg(feature = "draft17")]
6092 (Self::Draft17(c), AnyInboundRequest::Draft17(s)) => {
6093 use moqtap_codec::draft17::message::SubscribeOk;
6094 c.respond_subscribe_ok(
6095 s,
6096 SubscribeOk {
6097 track_alias,
6098 parameters: Vec::new(),
6099 track_properties: Vec::new(),
6100 },
6101 )
6102 .await
6103 .map_err(AnyConnectionError::from)
6104 }
6105 #[cfg(feature = "draft18")]
6106 (Self::Draft18(c), AnyInboundRequest::Draft18(s)) => {
6107 use moqtap_codec::draft18::message::SubscribeOk;
6108 c.respond_subscribe_ok(
6109 s,
6110 SubscribeOk {
6111 track_alias,
6112 parameters: Vec::new(),
6113 track_properties: Vec::new(),
6114 },
6115 )
6116 .await
6117 .map_err(AnyConnectionError::from)
6118 }
6119 #[cfg(feature = "draft19")]
6120 (Self::Draft19(c), AnyInboundRequest::Draft19(s)) => {
6121 use moqtap_codec::draft19::message::SubscribeOk;
6122 c.respond_subscribe_ok(
6123 s,
6124 SubscribeOk {
6125 track_alias,
6126 parameters: Vec::new(),
6127 track_properties: Vec::new(),
6128 },
6129 )
6130 .await
6131 .map_err(AnyConnectionError::from)
6132 }
6133 #[cfg(feature = "draft20")]
6134 (Self::Draft20(c), AnyInboundRequest::Draft20(s)) => {
6135 use moqtap_codec::draft20::message::SubscribeOk;
6136 c.respond_subscribe_ok(
6137 s,
6138 SubscribeOk {
6139 track_alias,
6140 parameters: Vec::new(),
6141 track_properties: Vec::new(),
6142 },
6143 )
6144 .await
6145 .map_err(AnyConnectionError::from)
6146 }
6147 #[cfg(feature = "draft21")]
6148 (Self::Draft21(c), AnyInboundRequest::Draft21(s)) => {
6149 use moqtap_codec::draft21::message::SubscribeOk;
6150 c.respond_subscribe_ok(
6151 s,
6152 SubscribeOk {
6153 track_alias,
6154 parameters: Vec::new(),
6155 track_properties: Vec::new(),
6156 },
6157 )
6158 .await
6159 .map_err(AnyConnectionError::from)
6160 }
6161 #[cfg(feature = "draft22")]
6162 (Self::Draft22(c), AnyInboundRequest::Draft22(s)) => {
6163 use moqtap_codec::draft22::message::SubscribeOk;
6164 c.respond_subscribe_ok(
6165 s,
6166 SubscribeOk {
6167 track_alias,
6168 parameters: Vec::new(),
6169 track_properties: Vec::new(),
6170 },
6171 )
6172 .await
6173 .map_err(AnyConnectionError::from)
6174 }
6175 #[cfg(feature = "draft23")]
6176 (Self::Draft23(c), AnyInboundRequest::Draft23(s)) => {
6177 use moqtap_codec::draft23::message::SubscribeOk;
6178 c.respond_subscribe_ok(
6179 s,
6180 SubscribeOk {
6181 track_alias,
6182 parameters: Vec::new(),
6183 track_properties: Vec::new(),
6184 },
6185 )
6186 .await
6187 .map_err(AnyConnectionError::from)
6188 }
6189 // A handle from another draft, refused rather than answered on
6190 // whatever stream happens to be at hand — the same rule
6191 // [`AnyConnection::recv_response`] follows, for the same reason.
6192 #[allow(unreachable_patterns)]
6193 (connection, request) => Err(AnyConnectionError::facade(format!(
6194 "accept_subscribe: a draft {:?} request cannot be answered on a draft {:?} \
6195 connection",
6196 request.draft(),
6197 connection.draft(),
6198 ))),
6199 }
6200 }
6201
6202 /// Open a subgroup stream and frame it, ready for objects.
6203 ///
6204 /// The first thing a relay can be asked that is not a question about
6205 /// control messages. Answering SUBSCRIBE is not delivering a track, and
6206 /// until something puts an object on the wire there is no way to tell the
6207 /// two apart from outside.
6208 ///
6209 /// # The header this writes, and the four shapes it takes
6210 ///
6211 /// An explicit Subgroup ID, no extension block where a draft can say so,
6212 /// and a publisher priority on the wire — the plainest conforming subgroup
6213 /// stream each draft can carry. The struct that says so is different four
6214 /// times over:
6215 ///
6216 /// * **Drafts 07 through 10** put the stream type outside the header
6217 /// entirely, so the header is four fields and nothing selects a layout.
6218 /// * **Drafts 11 through 13** name a `StreamType` variant per layout, and
6219 /// `SubgroupExplicit` is the one that carries an ID and no extensions.
6220 /// * **Draft-14** folds the type into the header as a flag word, and the
6221 /// Subgroup ID becomes an `Option` that must agree with it — a type
6222 /// saying a field follows, with no field, is refused.
6223 /// * **Drafts 15 through 20** replace the flags with a type byte:
6224 /// `0x14` is the subgroup base `0x10`, plus `0x04` for an explicit
6225 /// Subgroup ID, with the extension bit `0x01` clear, the end-of-group
6226 /// bit `0x08` clear, and the no-priority bit `0x20` clear. Drafts 15 and
6227 /// 16 read those two ID bits as a pair of table columns and drafts 17
6228 /// through 20 call them a SUBGROUP_ID_MODE field, which is a difference
6229 /// in wording and not in bytes.
6230 ///
6231 /// # Why the extension block is not an argument
6232 ///
6233 /// It is not a property of any object, so there is nothing an object-level
6234 /// caller could be asked. The header settles it for the whole stream, and
6235 /// an object that disagreed with its header would misframe every object
6236 /// after it — the missing length is read out of the next field along. A
6237 /// caller that needs to put extensions on a stream needs to say so when the
6238 /// stream opens, which is a second entry point rather than an argument
6239 /// here.
6240 ///
6241 /// Three drafts do not offer the choice at all, and in opposite
6242 /// directions. Draft-07 has no extension block anywhere, so its streams
6243 /// carry none because there is none to carry. Drafts 08 through 10 have one
6244 /// on every object and **no header field that could say otherwise**: their
6245 /// subgroup header is four values with no type byte, so every object writes
6246 /// a length whether it has anything to put after it or not. On those three
6247 /// [`moqtap_codec::dispatch::AnySubgroupHeader::carries_extension_block`]
6248 /// answers without consulting the header, and a caller asking for a stream
6249 /// without one is asking for a stream the draft does not define.
6250 ///
6251 /// Nothing checks that `track_alias` names a track this session agreed to
6252 /// publish, because a probe measuring a relay may want to send objects for
6253 /// one it did not.
6254 #[allow(unused_variables)]
6255 pub async fn open_subgroup(
6256 &self,
6257 track_alias: u64,
6258 group_id: u64,
6259 subgroup_id: u64,
6260 publisher_priority: u8,
6261 ) -> Result<AnySubgroupWriter, AnyConnectionError> {
6262 // Used only by the per-draft arms below, so a build with no draft
6263 // feature enabled — the `<zero drafts>` row of `just draft-matrix`,
6264 // which exists to prove the crate still compiles with nothing
6265 // selected — compiles this import and reaches no arm that reads it.
6266 #[allow(unused_imports)]
6267 use moqtap_codec::dispatch::AnySubgroupHeader;
6268 use moqtap_codec::varint::VarInt;
6269 let alias = VarInt::from_u64(track_alias)
6270 .map_err(|e| AnyConnectionError::facade(format!("track alias {track_alias}: {e}")))?;
6271 let group = VarInt::from_u64(group_id)
6272 .map_err(|e| AnyConnectionError::facade(format!("group id {group_id}: {e}")))?;
6273 let subgroup = VarInt::from_u64(subgroup_id)
6274 .map_err(|e| AnyConnectionError::facade(format!("subgroup id {subgroup_id}: {e}")))?;
6275
6276 #[allow(unused_macros)]
6277 macro_rules! open {
6278 ($c:ident, $variant:ident, $header:expr) => {{
6279 let header = AnySubgroupHeader::$variant($header);
6280 $c.open_subgroup_stream(&header)
6281 .await
6282 .map(AnySubgroupWriter::$variant)
6283 .map_err(AnyConnectionError::from)
6284 }};
6285 }
6286
6287 match self {
6288 #[cfg(feature = "draft07")]
6289 Self::Draft07(c) => open!(
6290 c,
6291 Draft07,
6292 moqtap_codec::draft07::data_stream::SubgroupHeader {
6293 track_alias: alias,
6294 group_id: group,
6295 subgroup_id: subgroup,
6296 publisher_priority,
6297 }
6298 ),
6299 #[cfg(feature = "draft08")]
6300 Self::Draft08(c) => open!(
6301 c,
6302 Draft08,
6303 moqtap_codec::draft08::data_stream::SubgroupHeader {
6304 track_alias: alias,
6305 group_id: group,
6306 subgroup_id: subgroup,
6307 publisher_priority,
6308 }
6309 ),
6310 #[cfg(feature = "draft09")]
6311 Self::Draft09(c) => open!(
6312 c,
6313 Draft09,
6314 moqtap_codec::draft09::data_stream::SubgroupHeader {
6315 track_alias: alias,
6316 group_id: group,
6317 subgroup_id: subgroup,
6318 publisher_priority,
6319 }
6320 ),
6321 #[cfg(feature = "draft10")]
6322 Self::Draft10(c) => open!(
6323 c,
6324 Draft10,
6325 moqtap_codec::draft10::data_stream::SubgroupHeader {
6326 track_alias: alias,
6327 group_id: group,
6328 subgroup_id: subgroup,
6329 publisher_priority,
6330 }
6331 ),
6332 #[cfg(feature = "draft11")]
6333 Self::Draft11(c) => open!(
6334 c,
6335 Draft11,
6336 moqtap_codec::draft11::data_stream::SubgroupHeader {
6337 stream_type: moqtap_codec::draft11::data_stream::StreamType::SubgroupExplicit,
6338 track_alias: alias,
6339 group_id: group,
6340 subgroup_id: subgroup,
6341 publisher_priority,
6342 }
6343 ),
6344 #[cfg(feature = "draft12")]
6345 Self::Draft12(c) => open!(
6346 c,
6347 Draft12,
6348 moqtap_codec::draft12::data_stream::SubgroupHeader {
6349 stream_type: moqtap_codec::draft12::data_stream::StreamType::SubgroupExplicit,
6350 track_alias: alias,
6351 group_id: group,
6352 subgroup_id: subgroup,
6353 publisher_priority,
6354 }
6355 ),
6356 #[cfg(feature = "draft13")]
6357 Self::Draft13(c) => open!(
6358 c,
6359 Draft13,
6360 moqtap_codec::draft13::data_stream::SubgroupHeader {
6361 stream_type: moqtap_codec::draft13::data_stream::StreamType::SubgroupExplicit,
6362 track_alias: alias,
6363 group_id: group,
6364 subgroup_id: subgroup,
6365 publisher_priority,
6366 }
6367 ),
6368 #[cfg(feature = "draft14")]
6369 Self::Draft14(c) => open!(
6370 c,
6371 Draft14,
6372 moqtap_codec::draft14::data_stream::SubgroupHeader {
6373 // Subgroup ID field present, not taken from the first
6374 // object, no extensions, not the end of the group.
6375 stream_type: moqtap_codec::draft14::data_stream::SubgroupStreamType::from_flags(
6376 true, false, false, false,
6377 ),
6378 track_alias: alias,
6379 group_id: group,
6380 subgroup_id: Some(subgroup),
6381 publisher_priority,
6382 }
6383 ),
6384 #[cfg(feature = "draft15")]
6385 Self::Draft15(c) => open!(
6386 c,
6387 Draft15,
6388 moqtap_codec::draft15::data_stream::SubgroupHeader {
6389 header_type: 0x14,
6390 track_alias: alias,
6391 group_id: group,
6392 subgroup_id: subgroup,
6393 publisher_priority: Some(publisher_priority),
6394 }
6395 ),
6396 #[cfg(feature = "draft16")]
6397 Self::Draft16(c) => open!(
6398 c,
6399 Draft16,
6400 moqtap_codec::draft16::data_stream::SubgroupHeader {
6401 header_type: 0x14,
6402 track_alias: alias,
6403 group_id: group,
6404 subgroup_id: subgroup,
6405 publisher_priority: Some(publisher_priority),
6406 }
6407 ),
6408 #[cfg(feature = "draft17")]
6409 Self::Draft17(c) => open!(
6410 c,
6411 Draft17,
6412 moqtap_codec::draft17::data_stream::SubgroupHeader {
6413 header_type: 0x14,
6414 track_alias: alias,
6415 group_id: group,
6416 subgroup_id: subgroup,
6417 publisher_priority: Some(publisher_priority),
6418 }
6419 ),
6420 #[cfg(feature = "draft18")]
6421 Self::Draft18(c) => open!(
6422 c,
6423 Draft18,
6424 moqtap_codec::draft18::data_stream::SubgroupHeader {
6425 header_type: 0x14,
6426 track_alias: alias,
6427 group_id: group,
6428 subgroup_id: subgroup,
6429 publisher_priority: Some(publisher_priority),
6430 }
6431 ),
6432 #[cfg(feature = "draft19")]
6433 Self::Draft19(c) => open!(
6434 c,
6435 Draft19,
6436 moqtap_codec::draft19::data_stream::SubgroupHeader {
6437 header_type: 0x14,
6438 track_alias: alias,
6439 group_id: group,
6440 subgroup_id: subgroup,
6441 publisher_priority: Some(publisher_priority),
6442 }
6443 ),
6444 #[cfg(feature = "draft20")]
6445 Self::Draft20(c) => open!(
6446 c,
6447 Draft20,
6448 moqtap_codec::draft20::data_stream::SubgroupHeader {
6449 header_type: 0x14,
6450 track_alias: alias,
6451 group_id: group,
6452 subgroup_id: subgroup,
6453 publisher_priority: Some(publisher_priority),
6454 }
6455 ),
6456 #[cfg(feature = "draft21")]
6457 Self::Draft21(c) => open!(
6458 c,
6459 Draft21,
6460 moqtap_codec::draft21::data_stream::SubgroupHeader {
6461 header_type: 0x14,
6462 track_alias: alias,
6463 group_id: group,
6464 subgroup_id: subgroup,
6465 publisher_priority: Some(publisher_priority),
6466 }
6467 ),
6468 #[cfg(feature = "draft22")]
6469 Self::Draft22(c) => open!(
6470 c,
6471 Draft22,
6472 moqtap_codec::draft22::data_stream::SubgroupHeader {
6473 header_type: 0x14,
6474 track_alias: alias,
6475 group_id: group,
6476 subgroup_id: subgroup,
6477 publisher_priority: Some(publisher_priority),
6478 }
6479 ),
6480 #[cfg(feature = "draft23")]
6481 Self::Draft23(c) => open!(
6482 c,
6483 Draft23,
6484 moqtap_codec::draft23::data_stream::SubgroupHeader {
6485 header_type: 0x14,
6486 track_alias: alias,
6487 group_id: group,
6488 subgroup_id: subgroup,
6489 publisher_priority: Some(publisher_priority),
6490 }
6491 ),
6492 #[allow(unreachable_patterns)]
6493 _ => Err(AnyConnectionError::facade("no draft feature is enabled")),
6494 }
6495 }
6496
6497 /// Accept the next subgroup stream the peer opens, and read its header.
6498 ///
6499 /// The header comes back beside the reader because it is the only place
6500 /// several things are said: which track the objects belong to (by Track
6501 /// Alias), which group, and — through
6502 /// [`carries_extension_block`](moqtap_codec::dispatch::AnySubgroupHeader::carries_extension_block)
6503 /// — whether the objects on this stream write an extension block at all. A
6504 /// reader that did not know the last of those could not frame a single
6505 /// object.
6506 ///
6507 /// Reading the header is not separable from accepting the stream, and the
6508 /// per-draft connections do both in one call for a reason beyond
6509 /// convenience: the header settles the track's forwarding preference and
6510 /// binds the stream to the endpoint's object bookkeeping for that alias, so
6511 /// a stream accepted without its header read would be a stream whose
6512 /// objects nothing is measuring.
6513 ///
6514 /// This waits on the *next* unidirectional stream, whatever it carries. A
6515 /// draft that sends something else on one — and every draft does, for
6516 /// fetches — will fail to parse a subgroup header out of it, which is the
6517 /// same wall [`AnyConnection::accept_fetch`] hits from the other side.
6518 /// Nothing here reads the stream type first and branches: a caller that
6519 /// could receive either has to know which it is expecting, because the
6520 /// header decides how every object after it is framed.
6521 pub async fn accept_subgroup(
6522 &self,
6523 ) -> Result<(moqtap_codec::dispatch::AnySubgroupHeader, AnySubgroupReader), AnyConnectionError>
6524 {
6525 #[allow(unused_macros)]
6526 macro_rules! accept {
6527 ($c:ident, $variant:ident) => {{
6528 $c.accept_subgroup_stream()
6529 .await
6530 .map(|(header, stream)| (header, AnySubgroupReader::$variant(stream)))
6531 .map_err(AnyConnectionError::from)
6532 }};
6533 }
6534
6535 match self {
6536 #[cfg(feature = "draft07")]
6537 Self::Draft07(c) => accept!(c, Draft07),
6538 #[cfg(feature = "draft08")]
6539 Self::Draft08(c) => accept!(c, Draft08),
6540 #[cfg(feature = "draft09")]
6541 Self::Draft09(c) => accept!(c, Draft09),
6542 #[cfg(feature = "draft10")]
6543 Self::Draft10(c) => accept!(c, Draft10),
6544 #[cfg(feature = "draft11")]
6545 Self::Draft11(c) => accept!(c, Draft11),
6546 #[cfg(feature = "draft12")]
6547 Self::Draft12(c) => accept!(c, Draft12),
6548 #[cfg(feature = "draft13")]
6549 Self::Draft13(c) => accept!(c, Draft13),
6550 #[cfg(feature = "draft14")]
6551 Self::Draft14(c) => accept!(c, Draft14),
6552 #[cfg(feature = "draft15")]
6553 Self::Draft15(c) => accept!(c, Draft15),
6554 #[cfg(feature = "draft16")]
6555 Self::Draft16(c) => accept!(c, Draft16),
6556 #[cfg(feature = "draft17")]
6557 Self::Draft17(c) => accept!(c, Draft17),
6558 #[cfg(feature = "draft18")]
6559 Self::Draft18(c) => accept!(c, Draft18),
6560 #[cfg(feature = "draft19")]
6561 Self::Draft19(c) => accept!(c, Draft19),
6562 #[cfg(feature = "draft20")]
6563 Self::Draft20(c) => accept!(c, Draft20),
6564 #[cfg(feature = "draft21")]
6565 Self::Draft21(c) => accept!(c, Draft21),
6566 #[cfg(feature = "draft22")]
6567 Self::Draft22(c) => accept!(c, Draft22),
6568 #[cfg(feature = "draft23")]
6569 Self::Draft23(c) => accept!(c, Draft23),
6570 #[allow(unreachable_patterns)]
6571 _ => Err(AnyConnectionError::facade("no draft feature is enabled")),
6572 }
6573 }
6574
6575 /// Accept the next fetch stream the peer opens, and read its FETCH_HEADER.
6576 ///
6577 /// [`AnyConnection::accept_subgroup`]'s twin, and it waits on the same
6578 /// queue: the *next* unidirectional stream, whatever it carries. A subgroup
6579 /// stream arriving here fails to parse as a FETCH_HEADER, and a fetch stream
6580 /// arriving there fails the other way. A caller reading both kinds on one
6581 /// session has to know which is next, and this facade will not guess for it
6582 /// — see `accept_subgroup` for why guessing is the wrong shape.
6583 ///
6584 /// The header comes back because it names the request: everything on the
6585 /// stream after it answers the FETCH whose Request ID it carries, and a
6586 /// caller with more than one fetch in flight has no other way to tell the
6587 /// streams apart.
6588 ///
6589 /// # Why this takes a Group Order and `accept_subgroup` takes nothing
6590 ///
6591 /// Because on four drafts the reader cannot be started without it, and
6592 /// starting it wrong is silent. Drafts 18 through 23 encode an object's
6593 /// Group ID as a **delta**, and draft-18 Section 11.4.4.1 makes that delta
6594 /// count upward under Ascending and downward under Descending. A reader
6595 /// started in the wrong direction still parses every frame and reports Group
6596 /// IDs that walk the wrong way — the same trap
6597 /// `Connection::accept_fill_stream` documents on draft-20, where the
6598 /// endpoint happens to know the answer and this facade does not.
6599 ///
6600 /// Here nothing on this side holds it: the order is on the FETCH_OK, which
6601 /// is a control message a data stream never sees, and which the caller has
6602 /// already read. [`fetch_group_order`] takes it off that message so the
6603 /// lookup is written once rather than per caller.
6604 ///
6605 /// On the other drafts the argument is inert, and it is an argument
6606 /// rather than an `Option` because a caller that has a FETCH_OK in hand can
6607 /// always answer it, and one that cannot has not read the answer yet.
6608 #[allow(unused_variables)]
6609 pub async fn accept_fetch(
6610 &self,
6611 group_order: moqtap_codec::types::GroupOrder,
6612 ) -> Result<(moqtap_codec::dispatch::AnyFetchHeader, AnyFetchReader), AnyConnectionError> {
6613 #[allow(unused_macros)]
6614 macro_rules! accept {
6615 ($c:ident, $variant:ident) => {{
6616 $c.accept_fetch_stream()
6617 .await
6618 .map(|(header, stream)| (header, AnyFetchReader::$variant(stream)))
6619 .map_err(AnyConnectionError::from)
6620 }};
6621 // The three drafts whose object reader has to be pointed in a
6622 // direction before it will read anything. Each of them declares its
6623 // own two-valued `GroupOrder` beside the reader that consumes it,
6624 // because a delta either adds or subtracts and there is no third
6625 // thing it could do. `Publisher` — the third value of the control
6626 // plane's own enum, and the one a FETCH_OK omitting the property
6627 // leaves — becomes Ascending here, which is what draft-20 Section
6628 // 10.2.8 makes the default.
6629 ($c:ident, $variant:ident, $module:ident) => {{
6630 let order = match group_order {
6631 moqtap_codec::types::GroupOrder::Descending => {
6632 moqtap_codec::$module::data_stream::GroupOrder::Descending
6633 }
6634 _ => moqtap_codec::$module::data_stream::GroupOrder::Ascending,
6635 };
6636 $c.accept_fetch_stream()
6637 .await
6638 .map(|(header, mut stream)| {
6639 stream.begin_fetch_objects(order);
6640 (header, AnyFetchReader::$variant(stream))
6641 })
6642 .map_err(AnyConnectionError::from)
6643 }};
6644 }
6645
6646 match self {
6647 #[cfg(feature = "draft07")]
6648 Self::Draft07(c) => accept!(c, Draft07),
6649 #[cfg(feature = "draft08")]
6650 Self::Draft08(c) => accept!(c, Draft08),
6651 #[cfg(feature = "draft09")]
6652 Self::Draft09(c) => accept!(c, Draft09),
6653 #[cfg(feature = "draft10")]
6654 Self::Draft10(c) => accept!(c, Draft10),
6655 #[cfg(feature = "draft11")]
6656 Self::Draft11(c) => accept!(c, Draft11),
6657 #[cfg(feature = "draft12")]
6658 Self::Draft12(c) => accept!(c, Draft12),
6659 #[cfg(feature = "draft13")]
6660 Self::Draft13(c) => accept!(c, Draft13),
6661 #[cfg(feature = "draft14")]
6662 Self::Draft14(c) => accept!(c, Draft14),
6663 #[cfg(feature = "draft15")]
6664 Self::Draft15(c) => accept!(c, Draft15),
6665 // The resolver is created here rather than seeded from the header,
6666 // because draft-16's first object supplies its own Location and
6667 // every later one inherits from the object before it.
6668 #[cfg(feature = "draft16")]
6669 Self::Draft16(c) => c
6670 .accept_fetch_stream()
6671 .await
6672 .map(|(header, stream)| {
6673 (
6674 header,
6675 AnyFetchReader::Draft16(Draft16FetchStream {
6676 stream,
6677 reader: moqtap_codec::draft16::data_stream::FetchObjectReader::new(),
6678 }),
6679 )
6680 })
6681 .map_err(AnyConnectionError::from),
6682 #[cfg(feature = "draft17")]
6683 Self::Draft17(c) => accept!(c, Draft17),
6684 #[cfg(feature = "draft18")]
6685 Self::Draft18(c) => accept!(c, Draft18, draft18),
6686 #[cfg(feature = "draft19")]
6687 Self::Draft19(c) => accept!(c, Draft19, draft19),
6688 #[cfg(feature = "draft20")]
6689 Self::Draft20(c) => accept!(c, Draft20, draft20),
6690 #[cfg(feature = "draft21")]
6691 Self::Draft21(c) => accept!(c, Draft21, draft21),
6692 #[cfg(feature = "draft22")]
6693 Self::Draft22(c) => accept!(c, Draft22, draft22),
6694 #[cfg(feature = "draft23")]
6695 Self::Draft23(c) => accept!(c, Draft23, draft23),
6696 #[allow(unreachable_patterns)]
6697 _ => Err(AnyConnectionError::facade("no draft feature is enabled")),
6698 }
6699 }
6700
6701 /// Send a SUBSCRIBE_UPDATE for an active subscription. Draft-14 only.
6702 ///
6703 /// # Why no later draft is wired, and why that is not this call's to fix
6704 ///
6705 /// Draft-15 renamed the message REQUEST_UPDATE and rebuilt it, and from
6706 /// draft-17 it travels **on the request's own bidirectional stream** rather
6707 /// than on a control stream — so an update needs the [`AnyRequest`] the
6708 /// original request returned, which this signature does not take and cannot
6709 /// be given without becoming a different call. Draft-20 goes further and
6710 /// has no `start_location` / `end_group` pair at all: Section 10.9 carries
6711 /// the new range as a `LOCATION_FILTER` parameter, on the same inclusive
6712 /// terms [`FetchRange`] describes. Reach a draft's own
6713 /// `Connection::send_on_request_stream` through the variant.
6714 #[allow(unused_variables)]
6715 pub async fn subscribe_update(
6716 &mut self,
6717 subscription_request_id: moqtap_codec::varint::VarInt,
6718 start_location: moqtap_codec::types::Location,
6719 end_group: moqtap_codec::varint::VarInt,
6720 subscriber_priority: u8,
6721 forward: moqtap_codec::types::Forward,
6722 ) -> Result<(), AnyConnectionError> {
6723 match self {
6724 #[cfg(feature = "draft14")]
6725 Self::Draft14(c) => c
6726 .subscribe_update(
6727 subscription_request_id,
6728 start_location,
6729 end_group,
6730 subscriber_priority,
6731 forward,
6732 Vec::new(),
6733 )
6734 .await
6735 .map(|_| ())
6736 .map_err(AnyConnectionError::from),
6737 #[allow(unreachable_patterns)]
6738 other => Err(AnyConnectionError::facade(format!(
6739 "subscribe_update: not yet wired up for draft {:?} via AnyConnection",
6740 other.draft()
6741 ))),
6742 }
6743 }
6744}
6745
6746/// Trait for receiving events from an [`AnyConnection`].
6747///
6748/// Implementations must be `Send + Sync` because the adapter installed on
6749/// the inner draft-specific connection may emit events from async tasks.
6750/// `on_event` takes `&self` — implementations that need mutation should use
6751/// interior mutability (e.g. `Mutex`, `mpsc::Sender`).
6752///
6753/// The per-draft adapter clones the draft-specific event into the matching
6754/// [`AnyClientEvent`] variant before invoking `on_event`.
6755pub trait AnyConnectionObserver: Send + Sync {
6756 /// Called when a connection event occurs on any draft.
6757 fn on_event(&self, event: &AnyClientEvent);
6758}
6759
6760/// A no-op observer that discards all events.
6761pub struct NoOpObserver;
6762
6763impl AnyConnectionObserver for NoOpObserver {
6764 fn on_event(&self, _event: &AnyClientEvent) {}
6765}
6766
6767/// One control message as it crossed the wire, lifted out of whichever draft's
6768/// event carried it.
6769///
6770/// [`AnyClientEvent`] wraps a draft's own `ClientEvent`, so an observer holding
6771/// one can read [`draft`](AnyClientEvent::draft) and nothing else without
6772/// matching every enabled variant — which means a consumer outside this crate
6773/// writing its own cascade over every draft, the exact duplication this module
6774/// exists to hold in one place. [`AnyClientEvent::control_frame`] answers with
6775/// this instead.
6776///
6777/// Everything here borrows from the event, so it is a view rather than a
6778/// record: a consumer that wants to keep a frame copies the bytes out.
6779#[non_exhaustive]
6780#[derive(Debug, Clone, Copy)]
6781pub struct ControlFrame<'a> {
6782 /// The draft whose rules were used to decode it.
6783 pub draft: DraftVersion,
6784 /// Whether this endpoint sent it, as opposed to receiving it.
6785 ///
6786 /// A `bool` rather than a shared direction enum because each draft declares
6787 /// its own `Direction` and there is no cross-draft one to lift them into;
6788 /// inventing a shared one to convert every draft's into would be a type
6789 /// whose only purpose is to be converted.
6790 pub outbound: bool,
6791 /// The decoded message. [`message_type_id`] and [`message_type_name`] name
6792 /// the codepoint it arrived under.
6793 ///
6794 /// [`message_type_id`]: moqtap_codec::dispatch::AnyControlMessage::message_type_id
6795 /// [`message_type_name`]: moqtap_codec::dispatch::AnyControlMessage::message_type_name
6796 pub message: &'a moqtap_codec::dispatch::AnyControlMessage,
6797 /// The framed wire bytes — type, length and payload — as they arrived.
6798 ///
6799 /// `None` when the connection was reading without an observer attached and
6800 /// therefore never cloned them. That cannot happen for an event an observer
6801 /// is being handed, so in practice this is `None` only for a frame built by
6802 /// hand.
6803 ///
6804 /// Kept because the encoding is evidence the decoding discards: two peers
6805 /// sending the same field can still disagree on how wide a varint they
6806 /// wrote it in, and the decoded form answers the same either way.
6807 pub raw: Option<&'a [u8]>,
6808}
6809
6810/// A received control frame the connection refused, lifted out of whichever
6811/// draft's `ControlMessageRefused` event carried it. See
6812/// [`AnyClientEvent::refused_frame`].
6813#[non_exhaustive]
6814#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6815pub struct RefusedFrame<'a> {
6816 /// The draft whose rules refused it.
6817 pub draft: DraftVersion,
6818 /// The stream it arrived on, or `None` for the control stream. Always
6819 /// `None` on drafts 07 through 15, which have no other stream for it.
6820 pub stream_id: Option<u64>,
6821 /// The framed wire bytes the refusal is about.
6822 pub raw: &'a [u8],
6823}