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