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