Skip to main content

moqtap_codec/
dispatch.rs

1//! Unified types and version-aware decode/encode for runtime draft dispatch.
2//!
3//! This module provides wrapper enums (`Any*`) that hold any enabled draft's
4//! types and dispatch encoding/decoding based on
5//! [`DraftVersion`].
6//!
7//! Each enum variant is gated on its draft feature flag. Enable multiple draft
8//! features (e.g. `draft07` + `draft14`) for runtime dispatch between drafts.
9
10use bytes::{Buf, BufMut};
11
12use crate::error::CodecError;
13use crate::version::DraftVersion;
14
15pub use crate::data_dispatch::{
16    reemit_subgroup_object, AnyFetchEndOfRange, AnyFetchFrame, AnyFetchGroupOrder, AnyFetchObject,
17    AnyFetchObjectMeta, AnyFetchObjectReader, AnyFetchObjectWriter, AnySubgroupObject,
18    AnySubgroupObjectMeta, AnySubgroupObjectReader, AnySubgroupObjectWriter, FetchReemit, Reemit,
19};
20
21/// Generates a dispatch enum with one variant per enabled draft feature.
22///
23/// Each variant wraps the draft-specific type and delegates encode/decode
24/// to the appropriate draft module.
25macro_rules! dispatch_enum {
26    (
27        $(#[$meta:meta])*
28        $vis:vis enum $name:ident {
29            $(
30                #[cfg(feature = $feat:literal)]
31                $variant:ident => $module:path,
32            )+
33        }
34        decode($decode_fn:ident);
35        encode($encode_fn:ident -> $encode_ret:ty);
36    ) => {
37        $(#[$meta])*
38        $vis enum $name {
39            $(
40                #[cfg(feature = $feat)]
41                #[doc = concat!("Draft-", $feat, " variant.")]
42                $variant($module),
43            )+
44        }
45
46        impl $name {
47            /// Decode from wire using the specified draft version.
48            #[allow(unused_variables)]
49            pub fn decode(
50                version: DraftVersion,
51                buf: &mut impl Buf,
52            ) -> Result<Self, CodecError> {
53                match version {
54                    $(
55                        #[cfg(feature = $feat)]
56                        DraftVersion::$variant => {
57                            <$module>::$decode_fn(buf).map($name::$variant)
58                        }
59                    )+
60                    #[allow(unreachable_patterns)]
61                    _ => Err(CodecError::UnsupportedDraft(
62                        format!("draft {:?} not enabled via feature flag", version),
63                    )),
64                }
65            }
66
67            /// Encode to wire using the appropriate draft's format.
68            #[allow(unused_variables, unreachable_code)]
69            pub fn encode(&self, buf: &mut impl BufMut) -> $encode_ret {
70                match self {
71                    $(
72                        #[cfg(feature = $feat)]
73                        $name::$variant(inner) => inner.$encode_fn(buf),
74                    )+
75                    #[allow(unreachable_patterns)]
76                    _ => unreachable!("AnyXxx enum has no enabled variants"),
77                }
78            }
79
80            /// Returns the draft version this value belongs to.
81            #[allow(unreachable_code)]
82            pub fn draft(&self) -> DraftVersion {
83                match self {
84                    $(
85                        #[cfg(feature = $feat)]
86                        $name::$variant(_) => DraftVersion::$variant,
87                    )+
88                    #[allow(unreachable_patterns)]
89                    _ => unreachable!("AnyXxx enum has no enabled variants"),
90                }
91            }
92        }
93    };
94}
95
96/// Generates one uniform [`AnySubgroupHeader`] accessor.
97///
98/// Bodies are written once per group of drafts that share one; every arm is
99/// `#[cfg]`-gated on its own draft feature and a catch-all closes the match,
100/// so a single-draft build and a zero-draft build both compile — the same
101/// shape [`dispatch_enum!`] generates for `draft()`.
102macro_rules! subgroup_header_accessor {
103    (
104        $(#[$meta:meta])*
105        $name:ident -> $ret:ty;
106        $(
107            [ $( $variant:ident @ $feat:literal ),+ $(,)? ] => |$h:ident| $body:expr
108        ),+ $(,)?
109    ) => {
110        $(#[$meta])*
111        #[allow(unreachable_code)]
112        pub fn $name(&self) -> $ret {
113            match self {
114                $($(
115                    #[cfg(feature = $feat)]
116                    AnySubgroupHeader::$variant($h) => $body,
117                )+)+
118                #[allow(unreachable_patterns)]
119                _ => unreachable!("AnySubgroupHeader has no enabled variants"),
120            }
121        }
122    };
123}
124
125// ── Control messages ────────────────────────────────────────
126
127dispatch_enum! {
128    /// A control message from any enabled draft.
129    #[derive(Debug, Clone)]
130    pub enum AnyControlMessage {
131        #[cfg(feature = "draft07")]
132        Draft07 => crate::draft07::message::ControlMessage,
133        #[cfg(feature = "draft08")]
134        Draft08 => crate::draft08::message::ControlMessage,
135        #[cfg(feature = "draft09")]
136        Draft09 => crate::draft09::message::ControlMessage,
137        #[cfg(feature = "draft10")]
138        Draft10 => crate::draft10::message::ControlMessage,
139        #[cfg(feature = "draft11")]
140        Draft11 => crate::draft11::message::ControlMessage,
141        #[cfg(feature = "draft12")]
142        Draft12 => crate::draft12::message::ControlMessage,
143        #[cfg(feature = "draft13")]
144        Draft13 => crate::draft13::message::ControlMessage,
145        #[cfg(feature = "draft14")]
146        Draft14 => crate::draft14::message::ControlMessage,
147        #[cfg(feature = "draft15")]
148        Draft15 => crate::draft15::message::ControlMessage,
149        #[cfg(feature = "draft16")]
150        Draft16 => crate::draft16::message::ControlMessage,
151        #[cfg(feature = "draft17")]
152        Draft17 => crate::draft17::message::ControlMessage,
153        #[cfg(feature = "draft18")]
154        Draft18 => crate::draft18::message::ControlMessage,
155        #[cfg(feature = "draft19")]
156        Draft19 => crate::draft19::message::ControlMessage,
157        #[cfg(feature = "draft20")]
158        Draft20 => crate::draft20::message::ControlMessage,
159        #[cfg(feature = "draft21")]
160        Draft21 => crate::draft21::message::ControlMessage,
161    }
162    decode(decode);
163    encode(encode -> Result<(), CodecError>);
164}
165
166impl AnyControlMessage {
167    /// Returns `true` if this is a CLIENT_SETUP or SERVER_SETUP message.
168    ///
169    /// Drafts 07 through 16 carry the two as separate messages and drafts 17
170    /// and later fold them into one SETUP, so the question has two spellings
171    /// and one answer.
172    ///
173    /// There is no answer for a draft this build left out, because there is no
174    /// question: a draft with no feature has no variant on
175    /// [`AnyControlMessage`], so no value naming it can reach the match.
176    pub fn is_setup(&self) -> bool {
177        // `*self` rather than `self`, and no catch-all under the arms. The two
178        // go together, and the second is the point: every arm is gated on its
179        // own draft, so the arms are exactly the variants under every feature
180        // set, and a draft added to the enum without an arm here stops the
181        // build. A `_` would compile instead and answer `false` for every setup
182        // message that draft ever carries.
183        //
184        // The build with no draft at all is the one where that leaves no arms,
185        // and `AnyControlMessage` is there an enum with no variants that no
186        // value inhabits. The compiler sees that only through the place: a `&`
187        // is inhabited whatever it points at, so `match self {}` is refused
188        // where `match *self {}` is exhaustive. `ref` on each binding is the
189        // price of saying it that way.
190        //
191        // What that avoids is a `cfg(not(any(…)))` naming all fifteen features
192        // to gate a catch-all for the empty build. Such a list has to be
193        // extended by hand for every new draft, and forgetting is silent in
194        // exactly one configuration — the build compiling only the new draft,
195        // where the unextended list is false, the catch-all it gates comes
196        // back, and the one variant it does not name falls straight into it.
197        match *self {
198            #[cfg(feature = "draft07")]
199            AnyControlMessage::Draft07(ref m) => matches!(
200                m,
201                crate::draft07::message::ControlMessage::ClientSetup(_)
202                    | crate::draft07::message::ControlMessage::ServerSetup(_)
203            ),
204            #[cfg(feature = "draft08")]
205            AnyControlMessage::Draft08(ref m) => matches!(
206                m,
207                crate::draft08::message::ControlMessage::ClientSetup(_)
208                    | crate::draft08::message::ControlMessage::ServerSetup(_)
209            ),
210            #[cfg(feature = "draft09")]
211            AnyControlMessage::Draft09(ref m) => matches!(
212                m,
213                crate::draft09::message::ControlMessage::ClientSetup(_)
214                    | crate::draft09::message::ControlMessage::ServerSetup(_)
215            ),
216            #[cfg(feature = "draft10")]
217            AnyControlMessage::Draft10(ref m) => matches!(
218                m,
219                crate::draft10::message::ControlMessage::ClientSetup(_)
220                    | crate::draft10::message::ControlMessage::ServerSetup(_)
221            ),
222            #[cfg(feature = "draft11")]
223            AnyControlMessage::Draft11(ref m) => matches!(
224                m,
225                crate::draft11::message::ControlMessage::ClientSetup(_)
226                    | crate::draft11::message::ControlMessage::ServerSetup(_)
227            ),
228            #[cfg(feature = "draft12")]
229            AnyControlMessage::Draft12(ref m) => matches!(
230                m,
231                crate::draft12::message::ControlMessage::ClientSetup(_)
232                    | crate::draft12::message::ControlMessage::ServerSetup(_)
233            ),
234            #[cfg(feature = "draft13")]
235            AnyControlMessage::Draft13(ref m) => matches!(
236                m,
237                crate::draft13::message::ControlMessage::ClientSetup(_)
238                    | crate::draft13::message::ControlMessage::ServerSetup(_)
239            ),
240            #[cfg(feature = "draft14")]
241            AnyControlMessage::Draft14(ref m) => matches!(
242                m,
243                crate::draft14::message::ControlMessage::ClientSetup(_)
244                    | crate::draft14::message::ControlMessage::ServerSetup(_)
245            ),
246            #[cfg(feature = "draft15")]
247            AnyControlMessage::Draft15(ref m) => matches!(
248                m,
249                crate::draft15::message::ControlMessage::ClientSetup(_)
250                    | crate::draft15::message::ControlMessage::ServerSetup(_)
251            ),
252            #[cfg(feature = "draft16")]
253            AnyControlMessage::Draft16(ref m) => matches!(
254                m,
255                crate::draft16::message::ControlMessage::ClientSetup(_)
256                    | crate::draft16::message::ControlMessage::ServerSetup(_)
257            ),
258            #[cfg(feature = "draft17")]
259            AnyControlMessage::Draft17(ref m) => {
260                matches!(m, crate::draft17::message::ControlMessage::Setup(_))
261            }
262            #[cfg(feature = "draft18")]
263            AnyControlMessage::Draft18(ref m) => {
264                matches!(m, crate::draft18::message::ControlMessage::Setup(_))
265            }
266            #[cfg(feature = "draft19")]
267            AnyControlMessage::Draft19(ref m) => {
268                matches!(m, crate::draft19::message::ControlMessage::Setup(_))
269            }
270            #[cfg(feature = "draft20")]
271            AnyControlMessage::Draft20(ref m) => {
272                matches!(m, crate::draft20::message::ControlMessage::Setup(_))
273            }
274            #[cfg(feature = "draft21")]
275            AnyControlMessage::Draft21(ref m) => {
276                matches!(m, crate::draft21::message::ControlMessage::Setup(_))
277            }
278        }
279    }
280
281    /// This message's fields, named as its own draft names them.
282    ///
283    /// The keys are the draft's field names in snake_case and the order is the
284    /// order the draft defines, so two drafts that spell one concept
285    /// differently each keep their own spelling and nothing has to agree on a
286    /// vocabulary none of them uses. A reader that has never heard of a
287    /// message can still show it.
288    ///
289    /// Optional fields the message did not carry are absent from the map. A
290    /// field that was not sent and a field carrying zero are different, and
291    /// only omission can say which happened.
292    ///
293    /// No answer for a draft this build left out, for the reason
294    /// [`Self::is_setup`] gives: such a draft has no variant here, so nothing
295    /// can arrive asking. Of the three accessors that shape protects, this is
296    /// the one where the alternative would hurt most — an empty
297    /// [`FieldMap`](crate::fields::FieldMap) renders as a message that carried
298    /// no fields, which is exactly how a message with none renders, so a draft
299    /// the match had not met would reach a reader as data rather than as an
300    /// error.
301    pub fn fields(&self) -> crate::fields::FieldMap {
302        // `*self` and no catch-all; see `is_setup` for why both.
303        match *self {
304            #[cfg(feature = "draft07")]
305            AnyControlMessage::Draft07(ref m) => crate::draft07::fields::message_fields(m),
306            #[cfg(feature = "draft08")]
307            AnyControlMessage::Draft08(ref m) => crate::draft08::fields::message_fields(m),
308            #[cfg(feature = "draft09")]
309            AnyControlMessage::Draft09(ref m) => crate::draft09::fields::message_fields(m),
310            #[cfg(feature = "draft10")]
311            AnyControlMessage::Draft10(ref m) => crate::draft10::fields::message_fields(m),
312            #[cfg(feature = "draft11")]
313            AnyControlMessage::Draft11(ref m) => crate::draft11::fields::message_fields(m),
314            #[cfg(feature = "draft12")]
315            AnyControlMessage::Draft12(ref m) => crate::draft12::fields::message_fields(m),
316            #[cfg(feature = "draft13")]
317            AnyControlMessage::Draft13(ref m) => crate::draft13::fields::message_fields(m),
318            #[cfg(feature = "draft14")]
319            AnyControlMessage::Draft14(ref m) => crate::draft14::fields::message_fields(m),
320            #[cfg(feature = "draft15")]
321            AnyControlMessage::Draft15(ref m) => crate::draft15::fields::message_fields(m),
322            #[cfg(feature = "draft16")]
323            AnyControlMessage::Draft16(ref m) => crate::draft16::fields::message_fields(m),
324            #[cfg(feature = "draft17")]
325            AnyControlMessage::Draft17(ref m) => crate::draft17::fields::message_fields(m),
326            #[cfg(feature = "draft18")]
327            AnyControlMessage::Draft18(ref m) => crate::draft18::fields::message_fields(m),
328            #[cfg(feature = "draft19")]
329            AnyControlMessage::Draft19(ref m) => crate::draft19::fields::message_fields(m),
330            #[cfg(feature = "draft20")]
331            AnyControlMessage::Draft20(ref m) => crate::draft20::fields::message_fields(m),
332            #[cfg(feature = "draft21")]
333            AnyControlMessage::Draft21(ref m) => crate::draft21::fields::message_fields(m),
334        }
335    }
336
337    /// This message's wire type ID and the name its own draft gives it.
338    ///
339    /// One match rather than two accessors' worth, because the two halves come
340    /// from the same `MessageType` and a build where they could disagree is one
341    /// nobody should be able to write.
342    #[allow(unreachable_code)]
343    fn message_type(&self) -> (u64, &'static str) {
344        // Invoked by every draft's arm and by nothing else, so the zero-draft
345        // build — `--no-default-features` with no `draftNN`, which
346        // `just test-features` and `just draft-matrix` each compile — defines
347        // it and calls it nowhere. That is the same build `#[allow]` on the
348        // function above is for, one lint later; `unused_macros` is not
349        // implied by `unreachable_code` and has to be said separately.
350        //
351        // Not a `cfg`, for the reason the arms below are not one
352        // either: the condition would be `any(feature = "draft07", …,
353        // feature = "draft21")`, one more hand-kept copy of the draft list, and
354        // a draft added to the arms and forgotten in the `cfg` would delete the
355        // macro out from under its own caller. The allow cannot be wrong about
356        // anything, because a build with any draft at all invokes the macro.
357        #[allow(unused_macros)]
358        macro_rules! named {
359            ($m:expr) => {{
360                let t = $m.message_type();
361                (t.id(), t.name())
362            }};
363        }
364        match self {
365            #[cfg(feature = "draft07")]
366            AnyControlMessage::Draft07(m) => named!(m),
367            #[cfg(feature = "draft08")]
368            AnyControlMessage::Draft08(m) => named!(m),
369            #[cfg(feature = "draft09")]
370            AnyControlMessage::Draft09(m) => named!(m),
371            #[cfg(feature = "draft10")]
372            AnyControlMessage::Draft10(m) => named!(m),
373            #[cfg(feature = "draft11")]
374            AnyControlMessage::Draft11(m) => named!(m),
375            #[cfg(feature = "draft12")]
376            AnyControlMessage::Draft12(m) => named!(m),
377            #[cfg(feature = "draft13")]
378            AnyControlMessage::Draft13(m) => named!(m),
379            #[cfg(feature = "draft14")]
380            AnyControlMessage::Draft14(m) => named!(m),
381            #[cfg(feature = "draft15")]
382            AnyControlMessage::Draft15(m) => named!(m),
383            #[cfg(feature = "draft16")]
384            AnyControlMessage::Draft16(m) => named!(m),
385            #[cfg(feature = "draft17")]
386            AnyControlMessage::Draft17(m) => named!(m),
387            #[cfg(feature = "draft18")]
388            AnyControlMessage::Draft18(m) => named!(m),
389            #[cfg(feature = "draft19")]
390            AnyControlMessage::Draft19(m) => named!(m),
391            #[cfg(feature = "draft20")]
392            AnyControlMessage::Draft20(m) => named!(m),
393            #[cfg(feature = "draft21")]
394            AnyControlMessage::Draft21(m) => named!(m),
395            // The no-draft build, where the enum has no variants and no value
396            // of it can exist. A refusal rather than an answer, because there
397            // is no id and no name to invent for a message that cannot exist.
398            // It is an arm at all — where the three accessors around it leave
399            // the match to run out — because the refusal is what makes a `_`
400            // safe here; the difference is which build reports an omission,
401            // since a draft added without an arm here panics at its first call
402            // rather than stopping the compile.
403            #[allow(unreachable_patterns)]
404            _ => unreachable!("AnyControlMessage has no enabled variants"),
405        }
406    }
407
408    /// This message's control message type ID, as its own draft assigns it.
409    ///
410    /// The ids are reused rather than retired across the drafts — 0x07 is
411    /// ANNOUNCE_OK through draft-13, PUBLISH_NAMESPACE_OK on draft-14 and
412    /// REQUEST_OK from draft-15 on — so this number means nothing without
413    /// [`draft`](Self::draft) beside it. [`message_type_name`](Self::message_type_name)
414    /// is the one that has already combined them.
415    pub fn message_type_id(&self) -> u64 {
416        self.message_type().0
417    }
418
419    /// The name this message's own draft gives its type, in the corpus's
420    /// snake_case spelling — `subscribe`, `publish_namespace`, `request_error`.
421    ///
422    /// The same string
423    /// [`message_type_name`](crate::message_type_name) answers for this
424    /// message's draft and id, and the same one that draft's
425    /// `codec/messages/*.json` vectors carry, so a message named here reads the
426    /// same way as one named from a trace.
427    ///
428    /// Never `None`: the free function has to allow for an id no draft assigns
429    /// and for a draft this build left out, and a decoded message can be
430    /// neither.
431    pub fn message_type_name(&self) -> &'static str {
432        self.message_type().1
433    }
434
435    /// The Request ID and Group Order of a FETCH, on the drafts where the
436    /// FETCH settles the order by itself.
437    ///
438    /// A fetch response's Objects arrive in the order the request asked for.
439    /// Draft-19 Section 10.12.3: "The publisher responding to a FETCH is
440    /// responsible for delivering all available Objects in the requested
441    /// range in the requested order (see Section 10.2.8)." Draft-19 Section
442    /// 10.2.8 carries the order itself, as the GROUP_ORDER parameter, and states
443    /// what its absence means: "If omitted from FETCH, the receiver uses
444    /// Ascending (0x1)." So on those drafts one message answers the question
445    /// outright, whether or not it carries the parameter, and that is what
446    /// this returns.
447    ///
448    /// The answer matters most on drafts 18 and 19, whose fetch Objects write
449    /// a Group ID as a difference from the Object before and leave the order
450    /// to decide its sign — see
451    /// [`AnyFetchObjectReader::new`]. Drafts 15, 16 and 17
452    /// state the same rule about the same parameter and their fetch streams
453    /// resolve without it, so this answers for them too rather than for the
454    /// two that happen to need it.
455    ///
456    /// # What answers `None`
457    ///
458    /// Any message that is not a FETCH, and **every FETCH on drafts 07-14**.
459    /// Those drafts carry Group Order as a field of the FETCH rather than as
460    /// a parameter, and its value 0x0 means the subscriber expressed no
461    /// preference — which leaves the order to the publisher, who states it in
462    /// the FETCH_OK. That is a two-message negotiation, and a function handed
463    /// one message cannot answer it. Answering Ascending there would be a
464    /// guess wearing the same return type as a fact.
465    ///
466    /// Also `None` for a GROUP_ORDER value that is neither Ascending (0x1)
467    /// nor Descending (0x2), which drafts 15-21 make a session-closing
468    /// PROTOCOL_VIOLATION and this crate's decoder refuses before building a
469    /// message. Defensive, and deliberately not the Ascending default: an
470    /// out-of-range value is not an omitted one.
471    ///
472    /// Not `None` — not anything — for a draft this build left out, for the
473    /// reason [`Self::is_setup`] gives: such a draft has no variant here, so
474    /// nothing can arrive asking.
475    pub fn fetch_group_order(&self) -> Option<(u64, AnyFetchGroupOrder)> {
476        /// GROUP_ORDER, Parameter Type 0x22 on every draft that has it.
477        ///
478        /// Both of these go unused in a build compiling none of drafts 15-21,
479        /// which is the honest report: no draft in such a build carries a
480        /// fetch's Group Order as a parameter, so every arm that would consult
481        /// them is gated out and what is left answers `None` outright.
482        #[allow(dead_code)]
483        const GROUP_ORDER: u64 = 0x22;
484
485        #[allow(dead_code)]
486        fn fetch_group_order(
487            request_id: crate::varint::VarInt,
488            parameters: &[crate::kvp::KeyValuePair],
489        ) -> Option<(u64, AnyFetchGroupOrder)> {
490            // The first, because drafts 15-21 refuse a repeated parameter
491            // before a message is built, so there is never a second.
492            let order = match parameters.iter().find(|p| p.key.into_inner() == GROUP_ORDER) {
493                None => AnyFetchGroupOrder::Ascending,
494                Some(p) => match &p.value {
495                    crate::kvp::KvpValue::Varint(v) => match v.into_inner() {
496                        0x1 => AnyFetchGroupOrder::Ascending,
497                        0x2 => AnyFetchGroupOrder::Descending,
498                        _ => return None,
499                    },
500                    // An even key type carries a varint, so this shape does
501                    // not survive decoding either.
502                    crate::kvp::KvpValue::Bytes(_) => return None,
503                },
504            };
505            Some((request_id.into_inner(), order))
506        }
507
508        // `*self` and no catch-all; see `is_setup` for why both.
509        match *self {
510            #[cfg(feature = "draft15")]
511            AnyControlMessage::Draft15(crate::draft15::message::ControlMessage::Fetch(ref f)) => {
512                fetch_group_order(f.request_id, &f.parameters)
513            }
514            #[cfg(feature = "draft16")]
515            AnyControlMessage::Draft16(crate::draft16::message::ControlMessage::Fetch(ref f)) => {
516                fetch_group_order(f.request_id, &f.parameters)
517            }
518            #[cfg(feature = "draft17")]
519            AnyControlMessage::Draft17(crate::draft17::message::ControlMessage::Fetch(ref f)) => {
520                fetch_group_order(f.request_id, &f.parameters)
521            }
522            #[cfg(feature = "draft18")]
523            AnyControlMessage::Draft18(crate::draft18::message::ControlMessage::Fetch(ref f)) => {
524                fetch_group_order(f.request_id, &f.parameters)
525            }
526            #[cfg(feature = "draft19")]
527            AnyControlMessage::Draft19(crate::draft19::message::ControlMessage::Fetch(ref f)) => {
528                fetch_group_order(f.request_id, &f.parameters)
529            }
530            #[cfg(feature = "draft20")]
531            AnyControlMessage::Draft20(crate::draft20::message::ControlMessage::Fetch(ref f)) => {
532                fetch_group_order(f.request_id, &f.parameters)
533            }
534            #[cfg(feature = "draft21")]
535            AnyControlMessage::Draft21(crate::draft21::message::ControlMessage::Fetch(ref f)) => {
536                fetch_group_order(f.request_id, &f.parameters)
537            }
538            // Every message that is not a FETCH, and every FETCH on a draft
539            // that does not settle the order by itself.
540            //
541            // One arm per draft, and this is the accessor where that costs
542            // something: `None` is a reachable, correct answer on all
543            // drafts, so there is no refusal to put in a `_` and make it loud.
544            // A catch-all would therefore take a draft added to the enum,
545            // compile, and answer `None` — indistinguishable from the honest
546            // `None` drafts 07-14 give. Naming the drafts costs a line each and
547            // makes the omission a build error instead.
548            #[cfg(feature = "draft07")]
549            AnyControlMessage::Draft07(_) => None,
550            #[cfg(feature = "draft08")]
551            AnyControlMessage::Draft08(_) => None,
552            #[cfg(feature = "draft09")]
553            AnyControlMessage::Draft09(_) => None,
554            #[cfg(feature = "draft10")]
555            AnyControlMessage::Draft10(_) => None,
556            #[cfg(feature = "draft11")]
557            AnyControlMessage::Draft11(_) => None,
558            #[cfg(feature = "draft12")]
559            AnyControlMessage::Draft12(_) => None,
560            #[cfg(feature = "draft13")]
561            AnyControlMessage::Draft13(_) => None,
562            #[cfg(feature = "draft14")]
563            AnyControlMessage::Draft14(_) => None,
564            #[cfg(feature = "draft15")]
565            AnyControlMessage::Draft15(_) => None,
566            #[cfg(feature = "draft16")]
567            AnyControlMessage::Draft16(_) => None,
568            #[cfg(feature = "draft17")]
569            AnyControlMessage::Draft17(_) => None,
570            #[cfg(feature = "draft18")]
571            AnyControlMessage::Draft18(_) => None,
572            #[cfg(feature = "draft19")]
573            AnyControlMessage::Draft19(_) => None,
574            #[cfg(feature = "draft20")]
575            AnyControlMessage::Draft20(_) => None,
576            #[cfg(feature = "draft21")]
577            AnyControlMessage::Draft21(_) => None,
578        }
579    }
580}
581
582// ── Data stream headers ─────────────────────────────────────
583
584dispatch_enum! {
585    /// A subgroup header from any enabled draft.
586    #[derive(Debug, Clone)]
587    pub enum AnySubgroupHeader {
588        #[cfg(feature = "draft07")]
589        Draft07 => crate::draft07::data_stream::SubgroupHeader,
590        #[cfg(feature = "draft08")]
591        Draft08 => crate::draft08::data_stream::SubgroupHeader,
592        #[cfg(feature = "draft09")]
593        Draft09 => crate::draft09::data_stream::SubgroupHeader,
594        #[cfg(feature = "draft10")]
595        Draft10 => crate::draft10::data_stream::SubgroupHeader,
596        #[cfg(feature = "draft11")]
597        Draft11 => crate::draft11::data_stream::SubgroupHeader,
598        #[cfg(feature = "draft12")]
599        Draft12 => crate::draft12::data_stream::SubgroupHeader,
600        #[cfg(feature = "draft13")]
601        Draft13 => crate::draft13::data_stream::SubgroupHeader,
602        #[cfg(feature = "draft14")]
603        Draft14 => crate::draft14::data_stream::SubgroupHeader,
604        #[cfg(feature = "draft15")]
605        Draft15 => crate::draft15::data_stream::SubgroupHeader,
606        #[cfg(feature = "draft16")]
607        Draft16 => crate::draft16::data_stream::SubgroupHeader,
608        #[cfg(feature = "draft17")]
609        Draft17 => crate::draft17::data_stream::SubgroupHeader,
610        #[cfg(feature = "draft18")]
611        Draft18 => crate::draft18::data_stream::SubgroupHeader,
612        #[cfg(feature = "draft19")]
613        Draft19 => crate::draft19::data_stream::SubgroupHeader,
614        #[cfg(feature = "draft20")]
615        Draft20 => crate::draft20::data_stream::SubgroupHeader,
616        #[cfg(feature = "draft21")]
617        Draft21 => crate::draft21::data_stream::SubgroupHeader,
618    }
619    decode(decode);
620    encode(encode -> ());
621}
622
623impl AnySubgroupHeader {
624    /// Decode a subgroup stream header including its leading stream-type
625    /// field, for any enabled draft.
626    ///
627    /// Drafts 07-13 encode the stream type as a varint ahead of the header
628    /// body; drafts 14-21 fold it into the header itself. This entry point
629    /// hides that difference: callers hand it the stream's first byte onwards
630    /// and it consumes exactly the header, type field included.
631    ///
632    /// On drafts 11-13 the stream type also selects the header layout and
633    /// fixes whether objects carry extension headers, which
634    /// [`Self::decode`] cannot know; prefer this entry point whenever the
635    /// stream's first byte is available.
636    #[allow(unused_variables)]
637    pub fn decode_stream(version: DraftVersion, buf: &mut impl Buf) -> Result<Self, CodecError> {
638        match version {
639            #[cfg(feature = "draft07")]
640            DraftVersion::Draft07 => {
641                crate::draft07::data_stream::SubgroupHeader::decode_stream(buf)
642                    .map(AnySubgroupHeader::Draft07)
643            }
644            #[cfg(feature = "draft08")]
645            DraftVersion::Draft08 => {
646                crate::draft08::data_stream::SubgroupHeader::decode_stream(buf)
647                    .map(AnySubgroupHeader::Draft08)
648            }
649            #[cfg(feature = "draft09")]
650            DraftVersion::Draft09 => {
651                crate::draft09::data_stream::SubgroupHeader::decode_stream(buf)
652                    .map(AnySubgroupHeader::Draft09)
653            }
654            #[cfg(feature = "draft10")]
655            DraftVersion::Draft10 => {
656                crate::draft10::data_stream::SubgroupHeader::decode_stream(buf)
657                    .map(AnySubgroupHeader::Draft10)
658            }
659            #[cfg(feature = "draft11")]
660            DraftVersion::Draft11 => {
661                crate::draft11::data_stream::SubgroupHeader::decode_stream(buf)
662                    .map(AnySubgroupHeader::Draft11)
663            }
664            #[cfg(feature = "draft12")]
665            DraftVersion::Draft12 => {
666                crate::draft12::data_stream::SubgroupHeader::decode_stream(buf)
667                    .map(AnySubgroupHeader::Draft12)
668            }
669            #[cfg(feature = "draft13")]
670            DraftVersion::Draft13 => {
671                crate::draft13::data_stream::SubgroupHeader::decode_stream(buf)
672                    .map(AnySubgroupHeader::Draft13)
673            }
674            #[cfg(feature = "draft14")]
675            DraftVersion::Draft14 => crate::draft14::data_stream::SubgroupHeader::decode(buf)
676                .map(AnySubgroupHeader::Draft14),
677            #[cfg(feature = "draft15")]
678            DraftVersion::Draft15 => crate::draft15::data_stream::SubgroupHeader::decode(buf)
679                .map(AnySubgroupHeader::Draft15),
680            #[cfg(feature = "draft16")]
681            DraftVersion::Draft16 => crate::draft16::data_stream::SubgroupHeader::decode(buf)
682                .map(AnySubgroupHeader::Draft16),
683            #[cfg(feature = "draft17")]
684            DraftVersion::Draft17 => crate::draft17::data_stream::SubgroupHeader::decode(buf)
685                .map(AnySubgroupHeader::Draft17),
686            #[cfg(feature = "draft18")]
687            DraftVersion::Draft18 => crate::draft18::data_stream::SubgroupHeader::decode(buf)
688                .map(AnySubgroupHeader::Draft18),
689            #[cfg(feature = "draft19")]
690            DraftVersion::Draft19 => crate::draft19::data_stream::SubgroupHeader::decode(buf)
691                .map(AnySubgroupHeader::Draft19),
692            #[cfg(feature = "draft20")]
693            DraftVersion::Draft20 => crate::draft20::data_stream::SubgroupHeader::decode(buf)
694                .map(AnySubgroupHeader::Draft20),
695            #[cfg(feature = "draft21")]
696            DraftVersion::Draft21 => crate::draft21::data_stream::SubgroupHeader::decode(buf)
697                .map(AnySubgroupHeader::Draft21),
698            #[allow(unreachable_patterns)]
699            _ => Err(CodecError::UnsupportedDraft(format!(
700                "draft {version:?} not enabled via feature flag"
701            ))),
702        }
703    }
704
705    /// Encode a subgroup stream header including its leading stream-type
706    /// field, the inverse of [`Self::decode_stream`].
707    ///
708    /// [`Self::encode`] is not that inverse on drafts 07-13 and never was:
709    /// it writes the header body alone, so bytes written with it and read
710    /// back with [`Self::decode_stream`] lose their first field and shift
711    /// every field after it. Use this for a stream's first write and
712    /// [`Self::encode`] only once the stream is already open.
713    // A build with no draft feature compiles this match to no arms at all,
714    // which leaves the parameter read by nothing. That is the same shape
715    // `unreachable_code` is allowed for here, and it is a real configuration
716    // — CI checks it — rather than a hypothetical one.
717    #[allow(unreachable_code, unused_variables)]
718    pub fn encode_stream(&self, buf: &mut impl BufMut) {
719        match self {
720            #[cfg(feature = "draft07")]
721            AnySubgroupHeader::Draft07(h) => h.encode_stream(buf),
722            #[cfg(feature = "draft08")]
723            AnySubgroupHeader::Draft08(h) => h.encode_stream(buf),
724            #[cfg(feature = "draft09")]
725            AnySubgroupHeader::Draft09(h) => h.encode_stream(buf),
726            #[cfg(feature = "draft10")]
727            AnySubgroupHeader::Draft10(h) => h.encode_stream(buf),
728            #[cfg(feature = "draft11")]
729            AnySubgroupHeader::Draft11(h) => h.encode_stream(buf),
730            #[cfg(feature = "draft12")]
731            AnySubgroupHeader::Draft12(h) => h.encode_stream(buf),
732            #[cfg(feature = "draft13")]
733            AnySubgroupHeader::Draft13(h) => h.encode_stream(buf),
734            // Drafts 14-21 fold the stream type into the header, so their
735            // `encode` already writes it and `decode_stream` already reads
736            // it back.
737            #[cfg(feature = "draft14")]
738            AnySubgroupHeader::Draft14(h) => h.encode(buf),
739            #[cfg(feature = "draft15")]
740            AnySubgroupHeader::Draft15(h) => h.encode(buf),
741            #[cfg(feature = "draft16")]
742            AnySubgroupHeader::Draft16(h) => h.encode(buf),
743            #[cfg(feature = "draft17")]
744            AnySubgroupHeader::Draft17(h) => h.encode(buf),
745            #[cfg(feature = "draft18")]
746            AnySubgroupHeader::Draft18(h) => h.encode(buf),
747            #[cfg(feature = "draft19")]
748            AnySubgroupHeader::Draft19(h) => h.encode(buf),
749            #[cfg(feature = "draft20")]
750            AnySubgroupHeader::Draft20(h) => h.encode(buf),
751            #[cfg(feature = "draft21")]
752            AnySubgroupHeader::Draft21(h) => h.encode(buf),
753            #[allow(unreachable_patterns)]
754            _ => unreachable!("AnySubgroupHeader has no enabled variants"),
755        }
756    }
757
758    /// Encode the header body, refusing a value the stream type will not carry,
759    /// and write the type field in front of it.
760    ///
761    /// The checked form of [`Self::encode_stream`]. Every draft from 11 on has
762    /// a header type table with a column the value can disagree with - a
763    /// Subgroup ID the type does not write, an `Option` that does not match
764    /// what the type says is present - and disagreeing does not produce a
765    /// malformed stream. It produces a well-formed stream for a different
766    /// subgroup, or with a different priority, which the peer has no way to
767    /// question. Each draft's own `encode_checked` says no to that; this is the
768    /// one entry point that reaches all of them.
769    ///
770    /// Drafts 07 through 10 have nothing to refuse: their SUBGROUP_HEADER has
771    /// one shape, every field is written every time, and no type byte selects
772    /// between them. They are written unchanged.
773    ///
774    /// # Errors
775    ///
776    /// [`CodecError::InvalidField`] if the header's fields disagree with its
777    /// own type. A refused header leaves `buf` untouched.
778    #[allow(unreachable_code, unused_variables, unused_mut)]
779    pub fn encode_stream_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
780        let mut body = Vec::with_capacity(32);
781        match self {
782            #[cfg(feature = "draft07")]
783            AnySubgroupHeader::Draft07(h) => h.encode_stream(&mut body),
784            #[cfg(feature = "draft08")]
785            AnySubgroupHeader::Draft08(h) => h.encode_stream(&mut body),
786            #[cfg(feature = "draft09")]
787            AnySubgroupHeader::Draft09(h) => h.encode_stream(&mut body),
788            #[cfg(feature = "draft10")]
789            AnySubgroupHeader::Draft10(h) => h.encode_stream(&mut body),
790            // Drafts 11-13 write the stream type ahead of a body their
791            // `encode_checked` produces on its own.
792            #[cfg(feature = "draft11")]
793            AnySubgroupHeader::Draft11(h) => {
794                crate::varint::VarInt::from_usize(h.stream_type as usize).encode(&mut body);
795                h.encode_checked(&mut body)?;
796            }
797            #[cfg(feature = "draft12")]
798            AnySubgroupHeader::Draft12(h) => {
799                crate::varint::VarInt::from_usize(h.stream_type as usize).encode(&mut body);
800                h.encode_checked(&mut body)?;
801            }
802            #[cfg(feature = "draft13")]
803            AnySubgroupHeader::Draft13(h) => {
804                crate::varint::VarInt::from_usize(h.stream_type as usize).encode(&mut body);
805                h.encode_checked(&mut body)?;
806            }
807            // Drafts 14-21 fold the type into the header, so their
808            // `encode_checked` already writes it.
809            #[cfg(feature = "draft14")]
810            AnySubgroupHeader::Draft14(h) => h.encode_checked(&mut body)?,
811            #[cfg(feature = "draft15")]
812            AnySubgroupHeader::Draft15(h) => h.encode_checked(&mut body)?,
813            #[cfg(feature = "draft16")]
814            AnySubgroupHeader::Draft16(h) => h.encode_checked(&mut body)?,
815            #[cfg(feature = "draft17")]
816            AnySubgroupHeader::Draft17(h) => h.encode_checked(&mut body)?,
817            #[cfg(feature = "draft18")]
818            AnySubgroupHeader::Draft18(h) => h.encode_checked(&mut body)?,
819            #[cfg(feature = "draft19")]
820            AnySubgroupHeader::Draft19(h) => h.encode_checked(&mut body)?,
821            #[cfg(feature = "draft20")]
822            AnySubgroupHeader::Draft20(h) => h.encode_checked(&mut body)?,
823            #[cfg(feature = "draft21")]
824            AnySubgroupHeader::Draft21(h) => h.encode_checked(&mut body)?,
825            #[allow(unreachable_patterns)]
826            _ => unreachable!("AnySubgroupHeader has no enabled variants"),
827        }
828        buf.put_slice(&body);
829        Ok(())
830    }
831
832    subgroup_header_accessor! {
833        /// The Track Alias every object on this stream belongs to.
834        track_alias -> u64;
835        [
836            Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
837            Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
838            Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
839            Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
840            Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
841        ] => |h| h.track_alias.into_inner(),
842    }
843
844    subgroup_header_accessor! {
845        /// The Group ID every object on this stream belongs to.
846        group_id -> u64;
847        [
848            Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
849            Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
850            Draft13 @ "draft13", Draft14 @ "draft14", Draft15 @ "draft15",
851            Draft16 @ "draft16", Draft17 @ "draft17", Draft18 @ "draft18",
852            Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
853        ] => |h| h.group_id.into_inner(),
854    }
855
856    subgroup_header_accessor! {
857        /// The Publisher Priority, or `None` when the header set a
858        /// default-priority flag and omitted the field (drafts 15+).
859        publisher_priority -> Option<u8>;
860        [
861            Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
862            Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
863            Draft13 @ "draft13", Draft14 @ "draft14",
864        ] => |h| Some(h.publisher_priority),
865        [
866            Draft15 @ "draft15", Draft16 @ "draft16", Draft17 @ "draft17",
867            Draft18 @ "draft18", Draft19 @ "draft19", Draft20 @ "draft20", Draft21 @ "draft21",
868        ] => |h| h.publisher_priority,
869    }
870
871    subgroup_header_accessor! {
872        /// The Subgroup ID this header fixes for its objects, or `None` when
873        /// the header does not determine one.
874        ///
875        /// `None` covers two cases. The first is the *subgroup ID is the first
876        /// object's ID* stream, which **every draft from 11 on** defines — ten
877        /// of the drafts, draft-15 included — and this codec never resolves.
878        /// The second is a header whose type the draft does not assign at all:
879        /// drafts 17-21 mode 3, and the same fourth combination of the `0x06`
880        /// bits on drafts 15 and 16. In every one of them the codec stores a
881        /// placeholder zero that a caller must not report.
882        ///
883        /// Imposes draft-14's `!has_subgroup_id_field()` guard uniformly. Every
884        /// per-draft accessor it reaches through reads the Subgroup ID carrier
885        /// the way that draft's own decoder does, so there is no disagreement
886        /// here for this accessor to paper over.
887        subgroup_id -> Option<u64>;
888        [
889            Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
890            Draft10 @ "draft10",
891        ] => |h| Some(h.subgroup_id.into_inner()),
892        [Draft11 @ "draft11"] => |h| {
893            use crate::draft11::data_stream::StreamType;
894            match h.stream_type {
895                StreamType::SubgroupFirstObj | StreamType::SubgroupFirstObjExt => None,
896                _ => Some(h.subgroup_id.into_inner()),
897            }
898        },
899        [Draft12 @ "draft12"] => |h| {
900            use crate::draft12::data_stream::StreamType;
901            match h.stream_type {
902                StreamType::SubgroupFirstObj
903                | StreamType::SubgroupFirstObjExt
904                | StreamType::SubgroupFirstObjEog
905                | StreamType::SubgroupFirstObjEogExt => None,
906                _ => Some(h.subgroup_id.into_inner()),
907            }
908        },
909        [Draft13 @ "draft13"] => |h| {
910            use crate::draft13::data_stream::StreamType;
911            match h.stream_type {
912                StreamType::SubgroupFirstObj
913                | StreamType::SubgroupFirstObjExt
914                | StreamType::SubgroupFirstObjEog
915                | StreamType::SubgroupFirstObjEogExt => None,
916                _ => Some(h.subgroup_id.into_inner()),
917            }
918        },
919        [Draft14 @ "draft14"] => |h| {
920            if h.stream_type.has_subgroup_id_field() {
921                Some(h.subgroup_id.map_or(0, |id| id.into_inner()))
922            } else if h.stream_type.subgroup_id_is_first_object() {
923                None
924            } else {
925                Some(0)
926            }
927        },
928        // Drafts 15 and 16 read the same three carriers out of the same two
929        // bits, so they share an answer — draft-16 naming them a
930        // SUBGROUP_ID_MODE and draft-15 giving them as a pair of table
931        // columns, which is a difference in wording and not in bytes.
932        //
933        // `None` is the first-object carrier: the ID is not on the wire and
934        // only the stream reader, which has seen the first object, can supply
935        // it. Answering `Some(0)` there collapses every first-object subgroup
936        // onto subgroup zero, and two subgroups of one group must never share a
937        // stream.
938        //
939        // `None` is also the fourth combination, which neither draft assigns:
940        // draft-16 reserves those type values by name, draft-15 reaches the
941        // same eight by leaving them out of Table 6. No such header decodes,
942        // so reaching this arm with one means a caller built it rather than
943        // read it, and that caller is the one this accessor exists to protect.
944        // `Some(0)` would hand it subgroup zero for a stream no draft defines;
945        // `None` says the header determines no Subgroup ID, which is true.
946        // Drafts 17-21 already answer `None` for their mode 3, so this is the
947        // same rule stated once for all five.
948        [Draft15 @ "draft15", Draft16 @ "draft16"] => |h| {
949            // The unassigned combination has to be tested somewhere. With the
950            // mode reserved neither carrier predicate answers `true`, so the
951            // fall-through would report subgroup zero for a stream no draft
952            // defines. It is tested first for legibility only; neither carrier
953            // predicate answers `true` for the reserved mode, so the ordering
954            // is not load-bearing.
955            if h.header_type & 0x06 == 0x06 {
956                None
957            } else if h.has_explicit_subgroup_id() {
958                Some(h.subgroup_id.into_inner())
959            } else if h.subgroup_id_from_first_object() {
960                None
961            } else {
962                Some(0)
963            }
964        },
965        [
966            Draft17 @ "draft17", Draft18 @ "draft18", Draft19 @ "draft19",
967            Draft20 @ "draft20", Draft21 @ "draft21",
968        ] => |h| {
969            match h.subgroup_id_mode() {
970                0 => Some(0),
971                2 => Some(h.subgroup_id.into_inner()),
972                // Mode 1 is *the first object's ID* and mode 3 is reserved; the
973                // decoder stores a placeholder zero for each.
974                _ => None,
975            }
976        },
977    }
978
979    subgroup_header_accessor! {
980        /// The two-bit subgroup-ID mode, on the five drafts that put one in
981        /// the header type, or `None` on the eight that do not.
982        ///
983        /// `0` = the header carries no subgroup ID and it is zero; `1` = the
984        /// subgroup ID is the first object's ID; `2` = an explicit ID
985        /// follows; `3` = the fourth combination, which no draft assigns.
986        ///
987        /// Exists because on those five drafts [`Self::subgroup_id`] returns
988        /// `None` for **both** mode 1 and mode 3 — the decoder stores a
989        /// placeholder zero for each — and the two mean different things to a
990        /// caller deciding whether an object may be elided. Without it,
991        /// eliding index 0 of a reserved-mode stream is indistinguishable
992        /// from eliding it on a stream whose subgroup ID the first object
993        /// defines.
994        ///
995        /// **Reported wherever that ambiguity exists, and that is what picks
996        /// the five.** Drafts 16 through 19 name a SUBGROUP_ID_MODE field;
997        /// draft-15 does not, and spells the same three carriers out as a
998        /// Subgroup ID Field Present column beside a Subgroup ID Value one,
999        /// reaching the fourth combination by leaving it out of the table
1000        /// rather than by reserving it. That is a difference in wording and
1001        /// not in bytes — same mask, same shift, same four values — so the
1002        /// question this accessor asks has one answer on both. It is named
1003        /// for the question and not for any draft's field, as
1004        /// [`Self::carries_extension_block`] is, and answering it here adds
1005        /// nothing to `draft15`, which goes on describing its own bits in its
1006        /// own words.
1007        ///
1008        /// `None` on drafts 07 through 14 means the ambiguity is absent, not
1009        /// the carrier. Drafts 07-10 always put the subgroup ID on the wire.
1010        /// Drafts 11 through 14 give each carrier a stream type of its own and
1011        /// assign every type they define, so [`Self::subgroup_id`] answers
1012        /// `None` for the first-object carrier and for nothing else, and there
1013        /// is no second reading for a mode to resolve.
1014        subgroup_id_mode -> Option<u8>;
1015        [
1016            Draft07 @ "draft07", Draft08 @ "draft08", Draft09 @ "draft09",
1017            Draft10 @ "draft10", Draft11 @ "draft11", Draft12 @ "draft12",
1018            Draft13 @ "draft13", Draft14 @ "draft14",
1019        ] => |_h| None,
1020        // Read off the type byte rather than through a per-draft accessor.
1021        // Draft-15 has no name for the field and gains no method for one;
1022        // draft-16's would have exactly this caller. The mask is the same
1023        // literal the arm two accessors above tests, and both headers expose
1024        // `header_type` directly.
1025        [Draft15 @ "draft15", Draft16 @ "draft16"] => |h| {
1026            Some((h.header_type & 0x06) >> 1)
1027        },
1028        [
1029            Draft17 @ "draft17", Draft18 @ "draft18", Draft19 @ "draft19",
1030            Draft20 @ "draft20", Draft21 @ "draft21",
1031        ] => |h| { Some(h.subgroup_id_mode()) },
1032    }
1033
1034    subgroup_header_accessor! {
1035        /// Whether every object on this stream writes a length-prefixed
1036        /// extension block — the field drafts 17-21 renamed Properties.
1037        ///
1038        /// A property of the *stream*, not of any object on it. The header's
1039        /// type settles it once, and an object with nothing to put in the
1040        /// block still writes a length of zero on a stream that carries one.
1041        /// So a writer cannot work the answer out from the object in its hand,
1042        /// and one that guesses puts a stream on the wire that no reader can
1043        /// follow: the missing length is read out of the next field along, and
1044        /// every object after it is misframed.
1045        ///
1046        /// Answered `false` on draft-07, which has no such block at all, and
1047        /// `true` on drafts 08 through 10, where every object carries one and
1048        /// no header type can say otherwise. From draft-11 on it is the
1049        /// header's own answer.
1050        ///
1051        /// Exists because nothing else exposed it. `subgroup_id` and
1052        /// `publisher_priority` report what the header *holds*; this reports
1053        /// what the objects after it must *write*, and only the first kind was
1054        /// reachable without matching on the concrete per-draft variant.
1055        carries_extension_block -> bool;
1056        [Draft07 @ "draft07"] => |_h| false,
1057        [Draft08 @ "draft08", Draft09 @ "draft09", Draft10 @ "draft10"] => |_h| true,
1058        [Draft11 @ "draft11", Draft12 @ "draft12", Draft13 @ "draft13"] => |h| {
1059            h.stream_type.has_extensions()
1060        },
1061        [Draft14 @ "draft14"] => |h| h.stream_type.extensions_present(),
1062        [Draft15 @ "draft15", Draft16 @ "draft16"] => |h| h.has_extensions(),
1063        [
1064            Draft17 @ "draft17", Draft18 @ "draft18", Draft19 @ "draft19",
1065            Draft20 @ "draft20", Draft21 @ "draft21",
1066        ] => |h| { h.has_properties() },
1067    }
1068}
1069
1070dispatch_enum! {
1071    /// An object header from any enabled draft.
1072    #[derive(Debug, Clone)]
1073    pub enum AnyObjectHeader {
1074        #[cfg(feature = "draft07")]
1075        Draft07 => crate::draft07::data_stream::ObjectHeader,
1076        #[cfg(feature = "draft08")]
1077        Draft08 => crate::draft08::data_stream::ObjectHeader,
1078        #[cfg(feature = "draft09")]
1079        Draft09 => crate::draft09::data_stream::ObjectHeader,
1080        #[cfg(feature = "draft10")]
1081        Draft10 => crate::draft10::data_stream::ObjectHeader,
1082        #[cfg(feature = "draft11")]
1083        Draft11 => crate::draft11::data_stream::ObjectHeader,
1084        #[cfg(feature = "draft12")]
1085        Draft12 => crate::draft12::data_stream::ObjectHeader,
1086        #[cfg(feature = "draft13")]
1087        Draft13 => crate::draft13::data_stream::ObjectHeader,
1088        // NOTE: drafts 14-21 have no standalone ObjectHeader — their
1089        // subgroup objects are delta-encoded against the previous object
1090        // on the stream. Use [`AnySubgroupObjectReader`], which covers
1091        // every draft 07-21 and also consumes object payloads.
1092    }
1093    decode(decode);
1094    encode(encode -> ());
1095}
1096
1097dispatch_enum! {
1098    /// A datagram header from any enabled draft.
1099    ///
1100    /// [`encode`](Self::encode) is fallible on every draft. It dispatches to
1101    /// each draft's `DatagramHeader::encode_checked` (draft-14's
1102    /// `DatagramObject::encode_checked`), which refuses a header whose Object
1103    /// Status the framing it names cannot carry rather than writing the bytes
1104    /// and dropping the status. Every draft 07-18 says "Any object with a
1105    /// status code other than zero MUST have an empty payload"; draft-19
1106    /// replaces that blanket rule with a per-status Payload column in the
1107    /// Object Status registry of its Section 15.9. Either way there is no
1108    /// datagram that states End of Group and carries a payload, so a value
1109    /// asking for one is answered with [`CodecError::InvalidField`] and
1110    /// nothing is written.
1111    ///
1112    /// The per-draft `encode` methods are infallible; they take the framing the
1113    /// value names as the authority and silently discard whatever does not fit
1114    /// it. Reach for one of those only when that is what you want.
1115    #[derive(Debug, Clone)]
1116    pub enum AnyDatagramHeader {
1117        #[cfg(feature = "draft07")]
1118        Draft07 => crate::draft07::data_stream::Datagram,
1119        #[cfg(feature = "draft08")]
1120        Draft08 => crate::draft08::data_stream::Datagram,
1121        #[cfg(feature = "draft09")]
1122        Draft09 => crate::draft09::data_stream::Datagram,
1123        #[cfg(feature = "draft10")]
1124        Draft10 => crate::draft10::data_stream::Datagram,
1125        #[cfg(feature = "draft11")]
1126        Draft11 => crate::draft11::data_stream::Datagram,
1127        #[cfg(feature = "draft12")]
1128        Draft12 => crate::draft12::data_stream::Datagram,
1129        #[cfg(feature = "draft13")]
1130        Draft13 => crate::draft13::data_stream::Datagram,
1131        #[cfg(feature = "draft14")]
1132        Draft14 => crate::draft14::data_stream::DatagramObject,
1133        #[cfg(feature = "draft15")]
1134        Draft15 => crate::draft15::data_stream::DatagramHeader,
1135        #[cfg(feature = "draft16")]
1136        Draft16 => crate::draft16::data_stream::DatagramHeader,
1137        #[cfg(feature = "draft17")]
1138        Draft17 => crate::draft17::data_stream::DatagramHeader,
1139        #[cfg(feature = "draft18")]
1140        Draft18 => crate::draft18::data_stream::DatagramHeader,
1141        #[cfg(feature = "draft19")]
1142        Draft19 => crate::draft19::data_stream::DatagramHeader,
1143        #[cfg(feature = "draft20")]
1144        Draft20 => crate::draft20::data_stream::DatagramHeader,
1145        #[cfg(feature = "draft21")]
1146        Draft21 => crate::draft21::data_stream::DatagramHeader,
1147    }
1148    decode(decode);
1149    encode(encode_checked -> Result<(), CodecError>);
1150}
1151
1152/// One datagram's identity, resolved, without its payload.
1153///
1154/// The five fields a caller keys on, taken off whichever of the
1155/// per-draft datagram shapes this value holds. Produced by
1156/// [`AnyDatagramHeader::meta`], and the reason it exists is that the shapes
1157/// disagree about far more than their field order: drafts 07 through 13 split
1158/// a payload datagram and a status datagram into two structs, draft-14 merges
1159/// them behind an optional status, and drafts 15 through 19 hang both the
1160/// status and the priority off bits in a type byte.
1161///
1162/// Every field is a primitive, so keying on a datagram never means naming a
1163/// per-draft codec type — the same contract
1164/// [`AnySubgroupObjectMeta`] holds for a subgroup object.
1165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1166pub struct AnyDatagramMeta {
1167    /// Track alias identifying the subscription this datagram answers.
1168    pub track_alias: u64,
1169    /// Group ID.
1170    pub group_id: u64,
1171    /// Object ID.
1172    ///
1173    /// Always a value, on every draft, including the six whose type byte can
1174    /// leave the field off the wire. Drafts 14 through 19 give the omission a
1175    /// meaning rather than making the field absent — draft-16 Section 10.3.1:
1176    /// "The ZERO_OBJECT_ID bit (0x04) indicates when the Object ID field is
1177    /// present. When set to 1, the Object ID field is omitted and the Object
1178    /// ID is 0." So the zero behind an omitted field is the Object's ID and
1179    /// not a placeholder standing in for one, which is the opposite of what a
1180    /// fetch object's absent Subgroup ID means and is why this field is not an
1181    /// `Option`.
1182    pub object_id: u64,
1183    /// Publisher priority, or `None` where the datagram states none.
1184    ///
1185    /// Absent only on drafts 15 through 19, whose type byte carries a
1186    /// default-priority bit; an Object that leaves it clear takes the priority
1187    /// the control message that established the subscription specified, which
1188    /// is not on this datagram and not knowable from it. Drafts 07 through 14
1189    /// always carry the field.
1190    pub publisher_priority: Option<u8>,
1191    /// The Object Status this datagram states, or `None` when it carries a
1192    /// payload instead.
1193    ///
1194    /// The framing decides which, and each cohort frames it differently: a
1195    /// declared payload length of zero on drafts 07 and 08, a separate status
1196    /// datagram on 08 through 13, an optional field on 14, and a status bit in
1197    /// the type byte from 15 on. Draft-08 appears in that list twice because it
1198    /// states a status both ways — it kept draft-07's optional status field
1199    /// under a zero payload length and added OBJECT_DATAGRAM_STATUS beside it,
1200    /// and draft-09 is where the first of the two goes away. The code is always
1201    /// one the draft assigns, because every draft's decoder refuses the values
1202    /// it does not.
1203    pub status: Option<u64>,
1204}
1205
1206impl AnyDatagramHeader {
1207    /// This datagram's identity, without its payload.
1208    ///
1209    /// One call in place of an arm per draft. A caller that wants a track
1210    /// alias, a Location or a priority off a datagram has otherwise to
1211    /// destructure the concrete per-draft variant — and on drafts 07 through 13
1212    /// to destructure again, because those carry a payload datagram and a
1213    /// status datagram as two different structs behind one enum.
1214    ///
1215    /// See [`AnyDatagramMeta::object_id`] for the one field whose absence from
1216    /// the wire is not an absence of the value.
1217    #[allow(unreachable_patterns)]
1218    pub fn meta(&self) -> AnyDatagramMeta {
1219        /// Drafts 09 through 13, which are the same two-struct shape five
1220        /// times: the payload form carries no status field at all, so the
1221        /// enum arm is the whole of the answer.
1222        ///
1223        /// Gated on the five it serves. A build carrying none of them has no
1224        /// caller for it, and an ungated definition would be a `-D warnings`
1225        /// error on every such per-draft row rather than on the all-features
1226        /// build a reviewer runs.
1227        #[cfg(any(
1228            feature = "draft09",
1229            feature = "draft10",
1230            feature = "draft11",
1231            feature = "draft12",
1232            feature = "draft13"
1233        ))]
1234        macro_rules! split_datagram {
1235            ($module:ident, $value:expr) => {
1236                match $value {
1237                    crate::$module::data_stream::Datagram::Payload(h) => AnyDatagramMeta {
1238                        track_alias: h.track_alias.into_inner(),
1239                        group_id: h.group_id.into_inner(),
1240                        object_id: h.object_id.into_inner(),
1241                        publisher_priority: Some(h.publisher_priority),
1242                        status: None,
1243                    },
1244                    crate::$module::data_stream::Datagram::Status(h) => AnyDatagramMeta {
1245                        track_alias: h.track_alias.into_inner(),
1246                        group_id: h.group_id.into_inner(),
1247                        object_id: h.object_id.into_inner(),
1248                        publisher_priority: Some(h.publisher_priority),
1249                        status: Some(h.object_status.as_u64()),
1250                    },
1251                }
1252            };
1253        }
1254
1255        match self {
1256            // Drafts 07 and 08 are the two that hang a status off a declared
1257            // payload length of zero, so on both the payload form can state one
1258            // and the enum arm is not the whole of the answer. Draft-07's
1259            // OBJECT_DATAGRAM is `… Object Payload Length (i), [Object Status
1260            // (i)], Object Payload (..)` and it is the only datagram that draft
1261            // has; draft-08 keeps that layout and adds OBJECT_DATAGRAM_STATUS
1262            // beside it, so it says the same thing two ways. Draft-09 dropped
1263            // both the length and the status from the payload form, which is
1264            // why every draft from there on can read the arm alone.
1265            #[cfg(feature = "draft07")]
1266            AnyDatagramHeader::Draft07(d) => {
1267                let states_status = d.is_status();
1268                match d {
1269                    crate::draft07::data_stream::Datagram::Payload(h) => AnyDatagramMeta {
1270                        track_alias: h.track_alias.into_inner(),
1271                        group_id: h.group_id.into_inner(),
1272                        object_id: h.object_id.into_inner(),
1273                        publisher_priority: Some(h.publisher_priority),
1274                        status: states_status.then(|| h.object_status.as_u64()),
1275                    },
1276                }
1277            }
1278            #[cfg(feature = "draft08")]
1279            AnyDatagramHeader::Draft08(d) => {
1280                let states_status = d.is_status();
1281                match d {
1282                    crate::draft08::data_stream::Datagram::Payload(h) => AnyDatagramMeta {
1283                        track_alias: h.track_alias.into_inner(),
1284                        group_id: h.group_id.into_inner(),
1285                        object_id: h.object_id.into_inner(),
1286                        publisher_priority: Some(h.publisher_priority),
1287                        status: states_status.then(|| h.object_status.as_u64()),
1288                    },
1289                    crate::draft08::data_stream::Datagram::Status(h) => AnyDatagramMeta {
1290                        track_alias: h.track_alias.into_inner(),
1291                        group_id: h.group_id.into_inner(),
1292                        object_id: h.object_id.into_inner(),
1293                        publisher_priority: Some(h.publisher_priority),
1294                        status: Some(h.object_status.as_u64()),
1295                    },
1296                }
1297            }
1298            #[cfg(feature = "draft09")]
1299            AnyDatagramHeader::Draft09(d) => split_datagram!(draft09, d),
1300            #[cfg(feature = "draft10")]
1301            AnyDatagramHeader::Draft10(d) => split_datagram!(draft10, d),
1302            #[cfg(feature = "draft11")]
1303            AnyDatagramHeader::Draft11(d) => split_datagram!(draft11, d),
1304            #[cfg(feature = "draft12")]
1305            AnyDatagramHeader::Draft12(d) => split_datagram!(draft12, d),
1306            #[cfg(feature = "draft13")]
1307            AnyDatagramHeader::Draft13(d) => split_datagram!(draft13, d),
1308            #[cfg(feature = "draft14")]
1309            AnyDatagramHeader::Draft14(d) => AnyDatagramMeta {
1310                track_alias: d.track_alias.into_inner(),
1311                group_id: d.group_id.into_inner(),
1312                object_id: d.object_id.into_inner(),
1313                publisher_priority: Some(d.publisher_priority),
1314                status: d.status.map(|s| s.as_u64()),
1315            },
1316            // Drafts 15 and 16 write the status field whenever the type byte's
1317            // status bit is set, and an unset value under a set bit encodes as
1318            // Normal — so the bit is the authority on presence and the field is
1319            // the authority on nothing else.
1320            #[cfg(feature = "draft15")]
1321            AnyDatagramHeader::Draft15(d) => AnyDatagramMeta {
1322                track_alias: d.track_alias.into_inner(),
1323                group_id: d.group_id.into_inner(),
1324                object_id: d.object_id.into_inner(),
1325                publisher_priority: d.publisher_priority,
1326                status: d.is_status().then(|| {
1327                    d.object_status.unwrap_or(crate::draft15::types::ObjectStatus::Normal).as_u64()
1328                }),
1329            },
1330            #[cfg(feature = "draft16")]
1331            AnyDatagramHeader::Draft16(d) => AnyDatagramMeta {
1332                track_alias: d.track_alias.into_inner(),
1333                group_id: d.group_id.into_inner(),
1334                object_id: d.object_id.into_inner(),
1335                publisher_priority: d.publisher_priority,
1336                status: d.is_status().then(|| {
1337                    d.object_status.unwrap_or(crate::draft16::types::ObjectStatus::Normal).as_u64()
1338                }),
1339            },
1340            // Drafts 17 through 19 resolve the same pair themselves.
1341            #[cfg(feature = "draft17")]
1342            AnyDatagramHeader::Draft17(d) => AnyDatagramMeta {
1343                track_alias: d.track_alias.into_inner(),
1344                group_id: d.group_id.into_inner(),
1345                object_id: d.object_id.into_inner(),
1346                publisher_priority: d.publisher_priority,
1347                status: d.has_status().then(|| d.status().as_u64()),
1348            },
1349            #[cfg(feature = "draft18")]
1350            AnyDatagramHeader::Draft18(d) => AnyDatagramMeta {
1351                track_alias: d.track_alias.into_inner(),
1352                group_id: d.group_id.into_inner(),
1353                object_id: d.object_id.into_inner(),
1354                publisher_priority: d.publisher_priority,
1355                status: d.has_status().then(|| d.status().as_u64()),
1356            },
1357            #[cfg(feature = "draft19")]
1358            AnyDatagramHeader::Draft19(d) => AnyDatagramMeta {
1359                track_alias: d.track_alias.into_inner(),
1360                group_id: d.group_id.into_inner(),
1361                object_id: d.object_id.into_inner(),
1362                publisher_priority: d.publisher_priority,
1363                status: d.has_status().then(|| d.status().as_u64()),
1364            },
1365            #[cfg(feature = "draft20")]
1366            AnyDatagramHeader::Draft20(d) => AnyDatagramMeta {
1367                track_alias: d.track_alias.into_inner(),
1368                group_id: d.group_id.into_inner(),
1369                object_id: d.object_id.into_inner(),
1370                publisher_priority: d.publisher_priority,
1371                status: d.has_status().then(|| d.status().as_u64()),
1372            },
1373            #[cfg(feature = "draft21")]
1374            AnyDatagramHeader::Draft21(d) => AnyDatagramMeta {
1375                track_alias: d.track_alias.into_inner(),
1376                group_id: d.group_id.into_inner(),
1377                object_id: d.object_id.into_inner(),
1378                publisher_priority: d.publisher_priority,
1379                status: d.has_status().then(|| d.status().as_u64()),
1380            },
1381            _ => unreachable!("AnyDatagramHeader has no enabled variants"),
1382        }
1383    }
1384
1385    /// Whether this datagram may carry a non-empty payload.
1386    ///
1387    /// Drafts 07 through 18 state one blanket rule — "Any object with a status
1388    /// code other than zero MUST have an empty payload" — and draft-19 replaces
1389    /// it with a Payload column in the Object Status registry of its Section
1390    /// 15.9, which grants a payload to the same one status the blanket rule
1391    /// did. The answer is therefore the same shape on every draft, and it is
1392    /// the framing that gives it: every draft either splits payload and status
1393    /// datagrams into separate types (08 through 14, and the type byte on 15
1394    /// and 16) or hangs the status off a declared length of zero (07), so a
1395    /// datagram that states a status is one that has no payload to carry.
1396    ///
1397    /// Note this asks what the framing *permits*, not what the value holds. A
1398    /// datagram permitted a payload may still carry none; a zero-length Normal
1399    /// object is legal everywhere.
1400    ///
1401    /// Without it a caller has to match the concrete per-draft variant to ask
1402    /// at all.
1403    #[allow(unreachable_patterns)]
1404    pub fn permits_payload(&self) -> bool {
1405        match self {
1406            #[cfg(feature = "draft07")]
1407            AnyDatagramHeader::Draft07(d) => !d.is_status(),
1408            #[cfg(feature = "draft08")]
1409            AnyDatagramHeader::Draft08(d) => !d.is_status(),
1410            #[cfg(feature = "draft09")]
1411            AnyDatagramHeader::Draft09(d) => !d.is_status(),
1412            #[cfg(feature = "draft10")]
1413            AnyDatagramHeader::Draft10(d) => !d.is_status(),
1414            #[cfg(feature = "draft11")]
1415            AnyDatagramHeader::Draft11(d) => !d.is_status(),
1416            #[cfg(feature = "draft12")]
1417            AnyDatagramHeader::Draft12(d) => !d.is_status(),
1418            #[cfg(feature = "draft13")]
1419            AnyDatagramHeader::Draft13(d) => !d.is_status(),
1420            #[cfg(feature = "draft14")]
1421            AnyDatagramHeader::Draft14(d) => !d.datagram_type.is_status(),
1422            #[cfg(feature = "draft15")]
1423            AnyDatagramHeader::Draft15(d) => !d.is_status(),
1424            #[cfg(feature = "draft16")]
1425            AnyDatagramHeader::Draft16(d) => !d.is_status(),
1426            // Drafts 17-21 answer the per-status question directly, which from 19
1427            // is the registry column rather than the blanket rule.
1428            #[cfg(feature = "draft17")]
1429            AnyDatagramHeader::Draft17(d) => d.permits_payload(),
1430            #[cfg(feature = "draft18")]
1431            AnyDatagramHeader::Draft18(d) => d.permits_payload(),
1432            #[cfg(feature = "draft19")]
1433            AnyDatagramHeader::Draft19(d) => d.permits_payload(),
1434            #[cfg(feature = "draft20")]
1435            AnyDatagramHeader::Draft20(d) => d.permits_payload(),
1436            #[cfg(feature = "draft21")]
1437            AnyDatagramHeader::Draft21(d) => d.permits_payload(),
1438            _ => unreachable!("AnyDatagramHeader has no enabled variants"),
1439        }
1440    }
1441
1442    /// Whether this datagram's status is allowed to carry the extension headers
1443    /// it has, or `None` where the draft states no such rule.
1444    ///
1445    /// The rule enters the specification twice, in two different widths, and a
1446    /// draft-neutral caller must not apply either one outside its range:
1447    ///
1448    /// - **Drafts 07 through 10 state nothing.** Draft-07's datagram has no
1449    ///   extension block at all, and drafts 08, 09 and 10 have one with no rule
1450    ///   attached. These answer `None` rather than `true`, because "permitted"
1451    ///   would imply a rule was consulted.
1452    /// - **Drafts 11 through 14 state the narrow form**, in the section naming
1453    ///   the Object Extension Header: "Any Object may have extension headers
1454    ///   except those with Object Status 'Object Does Not Exist'." One status,
1455    ///   and End of Group and End of Track may carry extensions freely.
1456    /// - **Drafts 15 through 19 state the general form**: "Any Object with
1457    ///   status Normal can have extension headers. If an endpoint receives
1458    ///   extension headers on Objects with status that is not Normal, it MUST
1459    ///   close the session with a PROTOCOL_VIOLATION." Draft-16 also dropped
1460    ///   the Object Does Not Exist status, so the narrow form's subject no
1461    ///   longer exists there.
1462    ///
1463    /// Drafts 17 and later call the block Properties rather than Extensions;
1464    /// the name here follows [`AnySubgroupObject::extension_headers`], which
1465    /// spans the same rename.
1466    ///
1467    /// This reports rather than refuses, on every draft. A frame carrying
1468    /// extensions beside a status is well formed — every length is honest and
1469    /// every field parses — so a decoder hands it back intact and a tool that
1470    /// reproduces a capture can re-emit it. Refusing on decode would make a
1471    /// captured violation unreadable, which loses the one artifact anybody
1472    /// debugging it needs.
1473    #[allow(unreachable_patterns)]
1474    pub fn extensions_permitted(&self) -> Option<bool> {
1475        match self {
1476            // No rule stated: see above.
1477            #[cfg(feature = "draft07")]
1478            AnyDatagramHeader::Draft07(_) => None,
1479            #[cfg(feature = "draft08")]
1480            AnyDatagramHeader::Draft08(_) => None,
1481            #[cfg(feature = "draft09")]
1482            AnyDatagramHeader::Draft09(_) => None,
1483            #[cfg(feature = "draft10")]
1484            AnyDatagramHeader::Draft10(_) => None,
1485            // The narrow form. A payload datagram's status is Normal, so only
1486            // the status form can state the violation.
1487            #[cfg(feature = "draft11")]
1488            AnyDatagramHeader::Draft11(d) => Some(match d {
1489                crate::draft11::data_stream::Datagram::Payload(_) => true,
1490                crate::draft11::data_stream::Datagram::Status(s) => {
1491                    s.extensions.is_empty()
1492                        || s.object_status
1493                            != crate::draft11::types::ObjectStatus::ObjectDoesNotExist
1494                }
1495            }),
1496            #[cfg(feature = "draft12")]
1497            AnyDatagramHeader::Draft12(d) => Some(match d {
1498                crate::draft12::data_stream::Datagram::Payload(_) => true,
1499                crate::draft12::data_stream::Datagram::Status(s) => {
1500                    s.extensions.is_empty()
1501                        || s.object_status
1502                            != crate::draft12::types::ObjectStatus::ObjectDoesNotExist
1503                }
1504            }),
1505            #[cfg(feature = "draft13")]
1506            AnyDatagramHeader::Draft13(d) => Some(match d {
1507                crate::draft13::data_stream::Datagram::Payload(_) => true,
1508                crate::draft13::data_stream::Datagram::Status(s) => {
1509                    s.extensions.is_empty()
1510                        || s.object_status
1511                            != crate::draft13::types::ObjectStatus::ObjectDoesNotExist
1512                }
1513            }),
1514            // Draft-14 folds both forms into one value, so an absent status
1515            // means Normal rather than *no status field here*.
1516            #[cfg(feature = "draft14")]
1517            AnyDatagramHeader::Draft14(d) => Some(
1518                d.extension_headers.is_empty()
1519                    || d.status != Some(crate::draft14::types::ObjectStatus::ObjectDoesNotExist),
1520            ),
1521            // The general form, already answered per draft.
1522            #[cfg(feature = "draft15")]
1523            AnyDatagramHeader::Draft15(d) => Some(d.extensions_permitted()),
1524            #[cfg(feature = "draft16")]
1525            AnyDatagramHeader::Draft16(d) => Some(d.extensions_permitted()),
1526            #[cfg(feature = "draft17")]
1527            AnyDatagramHeader::Draft17(d) => Some(d.properties_permitted()),
1528            #[cfg(feature = "draft18")]
1529            AnyDatagramHeader::Draft18(d) => Some(d.properties_permitted()),
1530            #[cfg(feature = "draft19")]
1531            AnyDatagramHeader::Draft19(d) => Some(d.properties_permitted()),
1532            #[cfg(feature = "draft20")]
1533            AnyDatagramHeader::Draft20(d) => Some(d.properties_permitted()),
1534            #[cfg(feature = "draft21")]
1535            AnyDatagramHeader::Draft21(d) => Some(d.properties_permitted()),
1536            _ => unreachable!("AnyDatagramHeader has no enabled variants"),
1537        }
1538    }
1539}
1540
1541dispatch_enum! {
1542    /// A fetch header from any enabled draft.
1543    ///
1544    /// Note: Header structure varies significantly across drafts.
1545    /// Draft-07 has a minimal fetch header, Draft-14 has a full header.
1546    #[derive(Debug, Clone)]
1547    pub enum AnyFetchHeader {
1548        #[cfg(feature = "draft07")]
1549        Draft07 => crate::draft07::data_stream::FetchHeader,
1550        #[cfg(feature = "draft08")]
1551        Draft08 => crate::draft08::data_stream::FetchHeader,
1552        #[cfg(feature = "draft09")]
1553        Draft09 => crate::draft09::data_stream::FetchHeader,
1554        #[cfg(feature = "draft10")]
1555        Draft10 => crate::draft10::data_stream::FetchHeader,
1556        #[cfg(feature = "draft11")]
1557        Draft11 => crate::draft11::data_stream::FetchHeader,
1558        #[cfg(feature = "draft12")]
1559        Draft12 => crate::draft12::data_stream::FetchHeader,
1560        #[cfg(feature = "draft13")]
1561        Draft13 => crate::draft13::data_stream::FetchHeader,
1562        #[cfg(feature = "draft14")]
1563        Draft14 => crate::draft14::data_stream::FetchHeader,
1564        #[cfg(feature = "draft15")]
1565        Draft15 => crate::draft15::data_stream::FetchHeader,
1566        #[cfg(feature = "draft16")]
1567        Draft16 => crate::draft16::data_stream::FetchHeader,
1568        #[cfg(feature = "draft17")]
1569        Draft17 => crate::draft17::data_stream::FetchHeader,
1570        #[cfg(feature = "draft18")]
1571        Draft18 => crate::draft18::data_stream::FetchHeader,
1572        #[cfg(feature = "draft19")]
1573        Draft19 => crate::draft19::data_stream::FetchHeader,
1574        #[cfg(feature = "draft20")]
1575        Draft20 => crate::draft20::data_stream::FetchHeader,
1576        #[cfg(feature = "draft21")]
1577        Draft21 => crate::draft21::data_stream::FetchHeader,
1578    }
1579    decode(decode);
1580    encode(encode -> ());
1581}
1582
1583impl AnyFetchHeader {
1584    /// The id of the request this fetch stream answers.
1585    ///
1586    /// Every draft puts it in the header and nothing else: drafts 07-10 call
1587    /// it the Subscribe ID and drafts 11-21 the Request ID, and it names the
1588    /// request the publisher is responding to either way. Draft-19 Section
1589    /// 11.4.4: "When a stream begins with FETCH_HEADER, all objects on the
1590    /// stream belong to the track requested in the Fetch message identified by
1591    /// Request ID."
1592    ///
1593    /// It is what ties a fetch data stream back to the control exchange that
1594    /// opened it, which is the only route by which anything the stream does
1595    /// not state — on drafts 18 and 19, the Group Order its Group ID Deltas
1596    /// resolve against — can reach a reader.
1597    #[allow(unreachable_patterns)]
1598    pub fn request_id(&self) -> u64 {
1599        match self {
1600            #[cfg(feature = "draft07")]
1601            AnyFetchHeader::Draft07(h) => h.subscribe_id.into_inner(),
1602            #[cfg(feature = "draft08")]
1603            AnyFetchHeader::Draft08(h) => h.subscribe_id.into_inner(),
1604            #[cfg(feature = "draft09")]
1605            AnyFetchHeader::Draft09(h) => h.subscribe_id.into_inner(),
1606            #[cfg(feature = "draft10")]
1607            AnyFetchHeader::Draft10(h) => h.subscribe_id.into_inner(),
1608            #[cfg(feature = "draft11")]
1609            AnyFetchHeader::Draft11(h) => h.request_id.into_inner(),
1610            #[cfg(feature = "draft12")]
1611            AnyFetchHeader::Draft12(h) => h.request_id.into_inner(),
1612            #[cfg(feature = "draft13")]
1613            AnyFetchHeader::Draft13(h) => h.request_id.into_inner(),
1614            #[cfg(feature = "draft14")]
1615            AnyFetchHeader::Draft14(h) => h.request_id.into_inner(),
1616            #[cfg(feature = "draft15")]
1617            AnyFetchHeader::Draft15(h) => h.request_id.into_inner(),
1618            #[cfg(feature = "draft16")]
1619            AnyFetchHeader::Draft16(h) => h.request_id.into_inner(),
1620            #[cfg(feature = "draft17")]
1621            AnyFetchHeader::Draft17(h) => h.request_id.into_inner(),
1622            #[cfg(feature = "draft18")]
1623            AnyFetchHeader::Draft18(h) => h.request_id.into_inner(),
1624            #[cfg(feature = "draft19")]
1625            AnyFetchHeader::Draft19(h) => h.request_id.into_inner(),
1626            #[cfg(feature = "draft20")]
1627            AnyFetchHeader::Draft20(h) => h.request_id.into_inner(),
1628            #[cfg(feature = "draft21")]
1629            AnyFetchHeader::Draft21(h) => h.request_id.into_inner(),
1630            _ => unreachable!("AnyFetchHeader has no enabled variants"),
1631        }
1632    }
1633
1634    /// As [`AnySubgroupHeader::encode_stream`], for fetch streams.
1635    ///
1636    /// Fetch was the carrier this pair was missing: `decode_stream` has
1637    /// existed here all along with nothing on the other side of it, so a
1638    /// fetch stream the codec wrote could not be read back by the codec.
1639    #[allow(unreachable_code, unused_variables)]
1640    pub fn encode_stream(&self, buf: &mut impl BufMut) {
1641        match self {
1642            #[cfg(feature = "draft07")]
1643            AnyFetchHeader::Draft07(h) => h.encode_stream(buf),
1644            #[cfg(feature = "draft08")]
1645            AnyFetchHeader::Draft08(h) => h.encode_stream(buf),
1646            #[cfg(feature = "draft09")]
1647            AnyFetchHeader::Draft09(h) => h.encode_stream(buf),
1648            #[cfg(feature = "draft10")]
1649            AnyFetchHeader::Draft10(h) => h.encode_stream(buf),
1650            #[cfg(feature = "draft11")]
1651            AnyFetchHeader::Draft11(h) => h.encode_stream(buf),
1652            #[cfg(feature = "draft12")]
1653            AnyFetchHeader::Draft12(h) => h.encode_stream(buf),
1654            #[cfg(feature = "draft13")]
1655            AnyFetchHeader::Draft13(h) => h.encode_stream(buf),
1656            // Drafts 14-21 fold the stream type into the header, so their
1657            // `encode` already writes it and `decode_stream` already reads
1658            // it back.
1659            #[cfg(feature = "draft14")]
1660            AnyFetchHeader::Draft14(h) => h.encode(buf),
1661            #[cfg(feature = "draft15")]
1662            AnyFetchHeader::Draft15(h) => h.encode(buf),
1663            #[cfg(feature = "draft16")]
1664            AnyFetchHeader::Draft16(h) => h.encode(buf),
1665            #[cfg(feature = "draft17")]
1666            AnyFetchHeader::Draft17(h) => h.encode(buf),
1667            #[cfg(feature = "draft18")]
1668            AnyFetchHeader::Draft18(h) => h.encode(buf),
1669            #[cfg(feature = "draft19")]
1670            AnyFetchHeader::Draft19(h) => h.encode(buf),
1671            #[cfg(feature = "draft20")]
1672            AnyFetchHeader::Draft20(h) => h.encode(buf),
1673            #[cfg(feature = "draft21")]
1674            AnyFetchHeader::Draft21(h) => h.encode(buf),
1675            #[allow(unreachable_patterns)]
1676            _ => unreachable!("AnyFetchHeader has no enabled variants"),
1677        }
1678    }
1679
1680    /// As [`AnySubgroupHeader::decode_stream`], for fetch streams.
1681    #[allow(unused_variables)]
1682    pub fn decode_stream(version: DraftVersion, buf: &mut impl Buf) -> Result<Self, CodecError> {
1683        match version {
1684            #[cfg(feature = "draft07")]
1685            DraftVersion::Draft07 => crate::draft07::data_stream::FetchHeader::decode_stream(buf)
1686                .map(AnyFetchHeader::Draft07),
1687            #[cfg(feature = "draft08")]
1688            DraftVersion::Draft08 => crate::draft08::data_stream::FetchHeader::decode_stream(buf)
1689                .map(AnyFetchHeader::Draft08),
1690            #[cfg(feature = "draft09")]
1691            DraftVersion::Draft09 => crate::draft09::data_stream::FetchHeader::decode_stream(buf)
1692                .map(AnyFetchHeader::Draft09),
1693            #[cfg(feature = "draft10")]
1694            DraftVersion::Draft10 => crate::draft10::data_stream::FetchHeader::decode_stream(buf)
1695                .map(AnyFetchHeader::Draft10),
1696            #[cfg(feature = "draft11")]
1697            DraftVersion::Draft11 => crate::draft11::data_stream::FetchHeader::decode_stream(buf)
1698                .map(AnyFetchHeader::Draft11),
1699            #[cfg(feature = "draft12")]
1700            DraftVersion::Draft12 => crate::draft12::data_stream::FetchHeader::decode_stream(buf)
1701                .map(AnyFetchHeader::Draft12),
1702            #[cfg(feature = "draft13")]
1703            DraftVersion::Draft13 => crate::draft13::data_stream::FetchHeader::decode_stream(buf)
1704                .map(AnyFetchHeader::Draft13),
1705            #[cfg(feature = "draft14")]
1706            DraftVersion::Draft14 => {
1707                crate::draft14::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft14)
1708            }
1709            #[cfg(feature = "draft15")]
1710            DraftVersion::Draft15 => {
1711                crate::draft15::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft15)
1712            }
1713            #[cfg(feature = "draft16")]
1714            DraftVersion::Draft16 => {
1715                crate::draft16::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft16)
1716            }
1717            #[cfg(feature = "draft17")]
1718            DraftVersion::Draft17 => {
1719                crate::draft17::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft17)
1720            }
1721            #[cfg(feature = "draft18")]
1722            DraftVersion::Draft18 => {
1723                crate::draft18::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft18)
1724            }
1725            #[cfg(feature = "draft19")]
1726            DraftVersion::Draft19 => {
1727                crate::draft19::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft19)
1728            }
1729            #[cfg(feature = "draft20")]
1730            DraftVersion::Draft20 => {
1731                crate::draft20::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft20)
1732            }
1733            #[cfg(feature = "draft21")]
1734            DraftVersion::Draft21 => {
1735                crate::draft21::data_stream::FetchHeader::decode(buf).map(AnyFetchHeader::Draft21)
1736            }
1737            #[allow(unreachable_patterns)]
1738            _ => Err(CodecError::UnsupportedDraft(format!(
1739                "draft {version:?} not enabled via feature flag"
1740            ))),
1741        }
1742    }
1743}
1744
1745// The one test below drives drafts 07, 12 and 18, each standing for one of the
1746// three framing shapes. Under a feature set naming none of them every arm
1747// compiles away, leaving the import with no user, so the module is gated on the
1748// same three rather than on `test` alone.
1749#[cfg(all(test, any(feature = "draft07", feature = "draft12", feature = "draft18")))]
1750mod tests {
1751    use super::*;
1752
1753    /// The draft-neutral entry point carries each draft's refusal out to the
1754    /// caller instead of resolving it the way the per-draft `encode` does.
1755    ///
1756    /// [`AnyDatagramHeader::encode`] dispatches to each draft's
1757    /// `encode_checked`, so a header whose Object Status its framing cannot
1758    /// carry is answered with [`CodecError::InvalidField`] and not a byte is
1759    /// written, rather than going out with the status quietly removed.
1760    ///
1761    /// Three drafts are driven here, one per shape they fall into.
1762    /// Draft-07 hangs the status field off a zero Object Payload Length;
1763    /// draft-18 hangs it off the STATUS bit in the type byte; draft-12 has no
1764    /// status field on this message at all, its statuses travelling on a
1765    /// separate OBJECT_DATAGRAM_STATUS, and so must keep accepting every header
1766    /// a publisher may send. A build with only some drafts enabled compiles
1767    /// only the arms it has.
1768    ///
1769    /// # What this catches, observed by making the change and running it
1770    ///
1771    /// Dropping the check from draft-07's `DatagramHeader::encode_checked`, so
1772    /// the dispatch layer has nothing to carry out:
1773    ///
1774    /// ```text
1775    /// draft-07 must refuse a status its framing cannot carry; got Ok(())
1776    /// ```
1777    #[test]
1778    fn any_datagram_header_encode_refuses_what_the_framing_cannot_carry() {
1779        #[cfg(feature = "draft07")]
1780        {
1781            let header =
1782                AnyDatagramHeader::Draft07(crate::draft07::data_stream::Datagram::Payload(
1783                    crate::draft07::data_stream::DatagramHeader {
1784                        track_alias: crate::varint::VarInt::from_usize(1),
1785                        group_id: crate::varint::VarInt::from_usize(0),
1786                        object_id: crate::varint::VarInt::from_usize(0),
1787                        publisher_priority: 128,
1788                        object_status: crate::draft07::types::ObjectStatus::EndOfGroup,
1789                        payload_length: crate::varint::VarInt::from_usize(4),
1790                    },
1791                ));
1792            let mut buf = Vec::new();
1793            let result = header.encode(&mut buf);
1794            assert!(
1795                matches!(result, Err(CodecError::InvalidField)),
1796                "draft-07 must refuse a status its framing cannot carry; got {result:?}"
1797            );
1798            assert!(buf.is_empty(), "draft-07 wrote {buf:?} for a header it refused");
1799        }
1800
1801        #[cfg(feature = "draft18")]
1802        {
1803            let header = AnyDatagramHeader::Draft18(crate::draft18::data_stream::DatagramHeader {
1804                // Type 0x00: every flag clear, so the STATUS bit is clear and
1805                // a payload follows the header.
1806                datagram_type: 0x00,
1807                track_alias: crate::varint::VarInt::from_usize(1),
1808                group_id: crate::varint::VarInt::from_usize(0),
1809                object_id: crate::varint::VarInt::from_usize(0),
1810                publisher_priority: Some(128),
1811                properties: Vec::new(),
1812                object_status: Some(crate::draft18::types::ObjectStatus::EndOfGroup),
1813            });
1814            let mut buf = Vec::new();
1815            let result = header.encode(&mut buf);
1816            assert!(
1817                matches!(result, Err(CodecError::InvalidField)),
1818                "draft-18 must refuse a status its framing cannot carry; got {result:?}"
1819            );
1820            assert!(buf.is_empty(), "draft-18 wrote {buf:?} for a header it refused");
1821        }
1822
1823        #[cfg(feature = "draft12")]
1824        {
1825            let header =
1826                AnyDatagramHeader::Draft12(crate::draft12::data_stream::Datagram::Payload(
1827                    crate::draft12::data_stream::DatagramHeader {
1828                        track_alias: crate::varint::VarInt::from_usize(1),
1829                        group_id: crate::varint::VarInt::from_usize(0),
1830                        object_id: crate::varint::VarInt::from_usize(7),
1831                        publisher_priority: 128,
1832                        extension_headers_length: crate::varint::VarInt::from_usize(0),
1833                        extensions: Vec::new(),
1834                        end_of_group: false,
1835                    },
1836                ));
1837            let mut buf = Vec::new();
1838            header
1839                .encode(&mut buf)
1840                .expect("draft-12's payload datagram carries no status to refuse");
1841            let mut cursor = &buf[..];
1842            let decoded = AnyDatagramHeader::decode(DraftVersion::Draft12, &mut cursor)
1843                .expect("the bytes the dispatch layer wrote must parse back");
1844            assert_eq!(decoded.draft(), DraftVersion::Draft12);
1845            assert!(!cursor.has_remaining(), "draft-12 left {cursor:?} unread");
1846        }
1847    }
1848
1849    /// The draft-neutral predicates answer the two questions that otherwise
1850    /// require matching the concrete per-draft variant.
1851    ///
1852    /// The same three drafts stand for the three eras of the extensions rule.
1853    /// Draft-07 has no extension block and no rule, and must answer `None`
1854    /// rather than `true` — reporting "permitted" would claim a rule was
1855    /// consulted. Draft-12 states the narrow form, so an extension block is a
1856    /// violation beside Object Does Not Exist and legal beside End of Group.
1857    /// Draft-18 states the general form, where both are violations.
1858    ///
1859    /// # What this catches, observed by making the change and running it
1860    ///
1861    /// Widening draft-12's arm to the general form, by comparing its status
1862    /// against `Normal` instead of against `ObjectDoesNotExist`:
1863    ///
1864    /// ```text
1865    /// draft-12 states the narrow form, which leaves End of Group free to
1866    /// carry extensions: expected Some(true), got Some(false)
1867    /// ```
1868    #[test]
1869    fn any_datagram_header_reports_payload_and_extension_permission() {
1870        #[cfg(feature = "draft07")]
1871        {
1872            let status =
1873                AnyDatagramHeader::Draft07(crate::draft07::data_stream::Datagram::Payload(
1874                    crate::draft07::data_stream::DatagramHeader {
1875                        track_alias: crate::varint::VarInt::from_usize(1),
1876                        group_id: crate::varint::VarInt::from_usize(0),
1877                        object_id: crate::varint::VarInt::from_usize(0),
1878                        publisher_priority: 128,
1879                        object_status: crate::draft07::types::ObjectStatus::EndOfGroup,
1880                        // Draft-07 has one datagram layout and hangs the status
1881                        // off a zero length, so this is what makes it a status.
1882                        payload_length: crate::varint::VarInt::from_usize(0),
1883                    },
1884                ));
1885            assert!(
1886                !status.permits_payload(),
1887                "draft-07 declares no payload bytes, so it may not carry any",
1888            );
1889            assert_eq!(
1890                status.extensions_permitted(),
1891                None,
1892                "draft-07 has no extension block and states no rule about one",
1893            );
1894        }
1895
1896        #[cfg(feature = "draft12")]
1897        {
1898            let with_extensions = |object_status| {
1899                AnyDatagramHeader::Draft12(crate::draft12::data_stream::Datagram::Status(
1900                    crate::draft12::data_stream::DatagramStatusHeader {
1901                        track_alias: crate::varint::VarInt::from_usize(1),
1902                        group_id: crate::varint::VarInt::from_usize(0),
1903                        object_id: crate::varint::VarInt::from_usize(0),
1904                        publisher_priority: 128,
1905                        extension_headers_length: crate::varint::VarInt::from_usize(2),
1906                        extensions: vec![0x3c, 0x01],
1907                        object_status,
1908                    },
1909                ))
1910            };
1911
1912            let absent = with_extensions(crate::draft12::types::ObjectStatus::ObjectDoesNotExist);
1913            assert!(!absent.permits_payload(), "a status datagram carries no payload");
1914            assert_eq!(
1915                absent.extensions_permitted(),
1916                Some(false),
1917                "Object Does Not Exist is the one status draft-12 bars extensions from",
1918            );
1919
1920            let end_of_group = with_extensions(crate::draft12::types::ObjectStatus::EndOfGroup);
1921            assert_eq!(
1922                end_of_group.extensions_permitted(),
1923                Some(true),
1924                "draft-12 states the narrow form, which leaves End of Group free to \
1925                 carry extensions: expected Some(true), got {:?}",
1926                end_of_group.extensions_permitted(),
1927            );
1928        }
1929
1930        #[cfg(feature = "draft18")]
1931        {
1932            // Type 0x21: the STATUS bit and the properties bit both set.
1933            let header = |object_status| {
1934                AnyDatagramHeader::Draft18(crate::draft18::data_stream::DatagramHeader {
1935                    datagram_type: 0x21,
1936                    track_alias: crate::varint::VarInt::from_usize(1),
1937                    group_id: crate::varint::VarInt::from_usize(0),
1938                    object_id: crate::varint::VarInt::from_usize(0),
1939                    publisher_priority: Some(128),
1940                    properties: vec![0x3c, 0x01],
1941                    object_status: Some(object_status),
1942                })
1943            };
1944
1945            let end_of_group = header(crate::draft18::types::ObjectStatus::EndOfGroup);
1946            assert!(!end_of_group.permits_payload(), "a status datagram carries no payload");
1947            assert_eq!(
1948                end_of_group.extensions_permitted(),
1949                Some(false),
1950                "draft-18 states the general form, which bars properties beside any \
1951                 status that is not Normal",
1952            );
1953        }
1954    }
1955}