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