Skip to main content

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}