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