Skip to main content

weida_protocol/
header.rs

1//! CBOR frame headers.
2//!
3//! The codecs are written by hand rather than derived. Three reasons:
4//!
5//! * the strictness rules (duplicate-key rejection, non-uint key rejection,
6//!   per-field string caps, depth-limited skipping) are protocol requirements,
7//!   not serialization defaults;
8//! * the exact byte layout is normative — see the golden vectors in
9//!   `docs/PROTOCOL.md` §8 — and a derive macro's field ordering is not a
10//!   contract we control;
11//! * every decoder here is a hostile-input boundary, so its allocation
12//!   behaviour has to be readable.
13//!
14//! All decode failures are protocol violations that close the connection.
15//!
16//! **No field of a DATA header is required by the decoder.** A header no longer
17//! carries the stream's role, so the decoder cannot know which fields the
18//! context demands; `endpoint`-on-initiating-streams is enforced by the
19//! transport's dispatch, which does know (`docs/PROTOCOL.md` §6.2).
20
21use std::convert::Infallible;
22
23use minicbor::data::Type;
24use minicbor::{Decoder, Encoder};
25use weida_core::Error;
26
27use crate::varint::{VarintError, decode_varint, encode_varint};
28
29/// Decoder limits. The string caps are normative
30/// (`docs/PROTOCOL.md` §6); the list and depth caps are defensive
31/// implementation limits documented in the same section.
32pub mod limits {
33    /// Cap for the DATA `endpoint` field.
34    pub const MAX_ENDPOINT_BYTES: usize = 512;
35    /// Cap for the DATA `content_type` field.
36    pub const MAX_CONTENT_TYPE_BYTES: usize = 256;
37    /// Cap for the DATA `traceparent` field.
38    pub const MAX_TRACEPARENT_BYTES: usize = 128;
39    /// Cap for the DATA `tracestate` field.
40    pub const MAX_TRACESTATE_BYTES: usize = 512;
41    /// Cap for the DATA `topic` field.
42    pub const MAX_TOPIC_BYTES: usize = 256;
43    /// Length of the DATA `producer` field: a raw 32-byte digest.
44    ///
45    /// Both a cap and an exact length. `docs/PROTOCOL.md` §6.2 defines the
46    /// value as "the raw 32-byte digest", so a longer one is a framing
47    /// violation and a shorter one names nothing this specification defines.
48    pub const PRODUCER_BYTES: usize = 32;
49    /// Cap for the SUBSCRIBE/UNSUBSCRIBE `filter` field.
50    pub const MAX_FILTER_BYTES: usize = 256;
51    /// Cap for the ERROR `message` field.
52    pub const MAX_MESSAGE_BYTES: usize = 1024;
53    /// Cap on the number of items in a HELLO list field.
54    ///
55    /// Without it, a hostile peer could pin `max_concurrent_uni_streams`
56    /// worth of large `Vec<u64>`s by opening many HELLO streams.
57    pub const MAX_LIST_ITEMS: usize = 64;
58    /// Cap on the number of levels a DATA header may order a report for
59    /// (`docs/PROTOCOL.md` §6.2, key `10`).
60    ///
61    /// A report order is a remote-controlled list, so it needs a cap for the
62    /// same reason [`MAX_LIST_ITEMS`] exists; 16 is more levels than the level
63    /// space defines below the application floor, so it constrains nothing a
64    /// sender legitimately wants.
65    pub const MAX_REPORT_LEVELS: usize = 16;
66    /// Nesting depth allowed when skipping an unknown field.
67    pub const MAX_SKIP_DEPTH: usize = 8;
68}
69
70/// HELLO keys.
71mod hello_key {
72    pub const VERSIONS: u64 = 0;
73    pub const MAX_HEADER_BYTES: u64 = 1;
74    pub const MAX_TRANSFERS: u64 = 2;
75    pub const CAPABILITIES: u64 = 3;
76    pub const REQUIRED_CAPABILITIES: u64 = 4;
77    pub const GUARANTEES_OFFERED: u64 = 5;
78    pub const GUARANTEES_REQUIRED: u64 = 6;
79}
80
81/// DATA keys.
82mod data_key {
83    pub const ENDPOINT: u64 = 0;
84    pub const CONTENT_LEN: u64 = 1;
85    pub const CONTENT_TYPE: u64 = 2;
86    pub const TRACEPARENT: u64 = 3;
87    pub const TRACESTATE: u64 = 4;
88    pub const TOPIC: u64 = 5;
89    pub const SEQUENCE: u64 = 6;
90    pub const PRODUCER: u64 = 7;
91    pub const ACHIEVED: u64 = 8;
92    pub const REPORT_ID: u64 = 9;
93    pub const REPORT: u64 = 10;
94    pub const REPORT_MODE: u64 = 11;
95    // Key 12 (`delivery_attempt`) is reserved in docs/PROTOCOL.md §6.2 and
96    // coded by the slice that writes it.
97    pub const SEGMENT: u64 = 13;
98}
99
100/// ERROR keys.
101mod error_key {
102    pub const CODE: u64 = 0;
103    pub const MESSAGE: u64 = 1;
104}
105
106/// SUBSCRIBE and UNSUBSCRIBE keys.
107mod subscription_key {
108    pub const ENDPOINT: u64 = 0;
109    pub const FILTER: u64 = 1;
110    pub const MAX_AGE_MS: u64 = 2;
111}
112
113/// CREDIT keys.
114mod credit_key {
115    pub const ENDPOINT: u64 = 0;
116    pub const FILTER: u64 = 1;
117    pub const LIMIT: u64 = 2;
118}
119
120/// CURSOR head-frame keys.
121mod cursor_key {
122    pub const REPORT_ID: u64 = 0;
123}
124
125/// FLOW keys (`docs/PROTOCOL.md` §6.8).
126mod flow_key {
127    pub const ENDPOINT: u64 = 0;
128    pub const FLOW: u64 = 1;
129    pub const CONTENT_TYPE: u64 = 2;
130    pub const TRACEPARENT: u64 = 3;
131    pub const TRACESTATE: u64 = 4;
132    pub const TOPIC: u64 = 5;
133}
134
135/// The topic filter grammar of `docs/PROTOCOL.md` §6.4.
136///
137/// A topic and a filter are byte strings split on [`filter::SEPARATOR`] into
138/// segments. The two wildcards are whole-segment tokens, and everything else
139/// is literal: there is no escape character, no normalization and no case
140/// folding ([decisions/0007](../../../docs/decisions/0007-topic-namespace.md)
141/// §4.2). Both halves of the grammar live here: [`filter::validate`], the rule
142/// that says which filters may exist at all — so an illegal one is refused at
143/// the codec boundary rather than reaching a matcher that would have to cope
144/// with it — and [`filter::matches`], the matcher itself. The matcher used to
145/// sit with the fan-out it served, in `weida::pubsub`, and moved when a second
146/// layer needed it: an L2 queue selects a consumer with the same grammar
147/// (B-202), and a second implementation of a wildcard language is exactly the
148/// kind of drift one definition exists to prevent.
149pub mod filter {
150    use super::HeaderError;
151
152    /// Segment separator: `.`, one byte.
153    pub const SEPARATOR: char = '.';
154    /// Matches exactly one whole segment.
155    pub const ONE_SEGMENT: &str = "*";
156    /// Matches zero or more trailing segments; legal only as the last segment.
157    pub const REST: &str = "#";
158
159    /// Does `topic` match `filter`?
160    ///
161    /// The segmented grammar of `docs/PROTOCOL.md` §6.4: segments split on `.`,
162    /// `*` for exactly one whole segment, a trailing `#` for zero or more, every
163    /// other byte literal, and the empty filter matching everything.
164    ///
165    /// The objection this function used to carry was that treating `*` as a
166    /// wildcard "would make topics with a literal `*` unaddressable and would put
167    /// a matching language in the hot path". Both halves were true and both are
168    /// accepted deliberately
169    /// ([decisions/0007](../../../../docs/decisions/0007-topic-namespace.md) §4.6): a
170    /// filter can no longer select a segment containing `.`, `*` or `#`
171    /// literally — there is no escape character, and no sheet reports a use for
172    /// one — while a byte prefix could not express a boundary at all, so
173    /// `sensors.temp` also selected `sensors.temperature`. The hot-path half is
174    /// answered by the shape rather than by the choice: `#` is legal only as the
175    /// final segment, so this is one left-to-right walk with no backtracking, no
176    /// allocation and work bounded by the 256 B filter cap.
177    ///
178    /// A `topic` is never a pattern: `*` and `#` in a published topic are literal
179    /// bytes here, exactly like any other.
180    pub fn matches(topic: &str, filter: &str) -> bool {
181        if filter.is_empty() {
182            return true;
183        }
184        let mut topic_segments = topic.split(SEPARATOR);
185        let mut filter_segments = filter.split(SEPARATOR);
186        loop {
187            let Some(pattern) = filter_segments.next() else {
188                // The filter is spent: it matches only if the topic is too.
189                return topic_segments.next().is_none();
190            };
191            // Only ever the final segment — `filter::validate` rejects anything
192            // else at the codec boundary — so everything left over matches.
193            if pattern == REST {
194                return true;
195            }
196            let Some(segment) = topic_segments.next() else {
197                return false;
198            };
199            if pattern != ONE_SEGMENT && pattern != segment {
200                return false;
201            }
202        }
203    }
204
205    /// Checks `filter` against the grammar.
206    ///
207    /// The empty filter is legal and matches every topic. One pass, no
208    /// allocation.
209    pub fn validate(filter: &str) -> Result<(), HeaderError> {
210        let mut segments = filter.split(SEPARATOR).peekable();
211        while let Some(segment) = segments.next() {
212            let is_last = segments.peek().is_none();
213            if segment.contains(ONE_SEGMENT) && segment != ONE_SEGMENT {
214                return Err(HeaderError::InvalidFilter(
215                    "`*` must occupy a whole segment",
216                ));
217            }
218            if segment.contains(REST) {
219                if segment != REST {
220                    return Err(HeaderError::InvalidFilter(
221                        "`#` must occupy a whole segment",
222                    ));
223                }
224                if !is_last {
225                    return Err(HeaderError::InvalidFilter("`#` must be the final segment"));
226                }
227            }
228        }
229        Ok(())
230    }
231}
232
233/// Guarantee set keys (`docs/PROTOCOL.md` §6.5).
234mod guarantee_key {
235    pub const DELIVERY: u64 = 0;
236    pub const ACKNOWLEDGEMENT: u64 = 1;
237    pub const DURABILITY: u64 = 2;
238    pub const REPLICAS: u64 = 3;
239    pub const ORDERING: u64 = 4;
240    pub const DEDUPLICATION: u64 = 5;
241    pub const DEDUP_WINDOW_MS: u64 = 6;
242    pub const BACKPRESSURE: u64 = 7;
243    pub const PRODUCER_NAMING: u64 = 8;
244    pub const CONTROL_ISOLATED: u64 = 9;
245}
246
247/// Declares an enum whose wire form is a small `uint`, with the `core` level
248/// first so that `Default` and "absent means core" agree by construction.
249///
250/// The derived `Ord` ranks by declaration order while `to_wire` reads
251/// explicit literals, and the ladder comparisons rest on the two agreeing —
252/// `GuaranteeSet::intersect` picks the weaker level with `.min()`,
253/// `GuaranteeSet::reaches` compares with `<`, and `CursorLevel`'s derived
254/// order inherits the same coincidence — so the macro asserts the agreement
255/// at compile time rather than letting a variant inserted mid-block with a
256/// higher literal silently rank a stronger guarantee below a weaker one.
257macro_rules! wire_enum {
258    ($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident = $value:literal),+ $(,)? }) => {
259        $(#[$meta])*
260        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
261        pub enum $name {
262            $($(#[$vmeta])* $variant,)+
263        }
264
265        impl $name {
266            /// The wire value of `docs/PROTOCOL.md` §6.5.
267            pub fn to_wire(self) -> u64 {
268                match self {
269                    $($name::$variant => $value,)+
270                }
271            }
272
273            /// The level a wire value names, or `None` if the value is not
274            /// one this version defines.
275            pub fn from_wire(value: u64) -> Option<$name> {
276                match value {
277                    $($value => Some($name::$variant),)+
278                    _ => None,
279                }
280            }
281        }
282
283        // The invariant the ladder comparisons depend on. A build failure is
284        // the only acceptable outcome: at run time the mis-ranking is
285        // invisible — every value still encodes and decodes — and shows up
286        // only as a negotiated guarantee weaker than the one reported.
287        const _: () = {
288            let values = [$($value as u64),+];
289            let mut i = 1;
290            while i < values.len() {
291                assert!(
292                    values[i - 1] < values[i],
293                    concat!(
294                        stringify!($name),
295                        ": wire values must ascend with declaration order, ",
296                        "because the derived Ord is the ladder"
297                    )
298                );
299                i += 1;
300            }
301        };
302    };
303}
304
305wire_enum! {
306    /// Delivery dimension ([`GUARANTEES.md`] §3). A ladder: later is stronger.
307    ///
308    /// [`GUARANTEES.md`]: https://git.doodleshnookie.net/tuco86/weida/blob/main/docs/GUARANTEES.md
309    Delivery {
310        /// v0: no retries, losses reported.
311        #[default]
312        BestEffort = 0,
313        /// Reserved.
314        AtMostOnce = 1,
315        /// Reserved.
316        AtLeastOnce = 2,
317    }
318}
319
320wire_enum! {
321    /// Acknowledgement/completion dimension. A ladder; the durability axes of
322    /// [`Durability`] and `replicas` are *not* part of it.
323    Acknowledgement {
324        /// Nothing is reported.
325        None = 0,
326        /// v0: QUIC's fin-acknowledgement.
327        #[default]
328        TransportReceipt = 1,
329        /// Reserved for the L2 broker.
330        Accepted = 2,
331        /// Reserved for the L2 broker.
332        Stored = 3,
333        /// Reserved for the L2 broker.
334        Replicated = 4,
335        /// Reserved for the L2 broker.
336        Processed = 5,
337    }
338}
339
340wire_enum! {
341    /// Persistence axis of `Stored`/`Replicated`
342    /// ([decisions/0004](../../../docs/decisions/0004-durability-levels.md) §4.1).
343    Durability {
344        /// Survives the broker process.
345        #[default]
346        Written = 0,
347        /// Survives loss of power on that node.
348        Flushed = 1,
349    }
350}
351
352wire_enum! {
353    /// Ordering dimension. A ladder: later is stronger.
354    OrderingMode {
355        /// v0.
356        #[default]
357        None = 0,
358        /// Report gaps, deliver as messages arrive.
359        PerProducerDetect = 1,
360        /// Hold messages back up to a bounded buffer.
361        PerProducerReassemble = 2,
362        /// L2 only.
363        PerKey = 3,
364        /// Reserved.
365        Total = 4,
366    }
367}
368
369wire_enum! {
370    /// Deduplication dimension. A ladder: later is stronger.
371    Deduplication {
372        /// v0.
373        #[default]
374        None = 0,
375        /// Suppressed within a time window.
376        Bounded = 1,
377        /// L2 only.
378        Durable = 2,
379    }
380}
381
382wire_enum! {
383    /// Backpressure dimension. **Not ordered**: these are behaviours, not
384    /// strengths, so two peers state the same one or fail to negotiate.
385    Backpressure {
386        /// v0 for Req/Rep and Push/Pull.
387        #[default]
388        Block = 0,
389        /// Refuse past a cap.
390        Reject = 1,
391        /// v0 for fan-out.
392        Drop = 2,
393        /// Reserved.
394        Spill = 3,
395        /// Reserved.
396        Coalesce = 4,
397    }
398}
399
400wire_enum! {
401    /// How a producer is named for the sequence field of
402    /// [decisions/0001](../../../docs/decisions/0001-sequence-field.md) §7.3.
403    /// **Not ordered**: two peers state the same one or fail.
404    ProducerNaming {
405        /// The proved connection fingerprint; the counter restarts with the
406        /// connection (v0 default,
407        /// [decisions/0008](../../../docs/decisions/0008-session-identity.md) §4.3).
408        #[default]
409        Fingerprint = 0,
410        /// A name supplied above L0, carried in DATA key `7`.
411        Stable = 1,
412    }
413}
414
415wire_enum! {
416    /// How often a reporter emits a record (`docs/PROTOCOL.md` §6.2, key
417    /// `11`).
418    ///
419    /// **Not ordered**: these are two shapes of the same report, not two
420    /// strengths. A cursor is never load-bearing, so neither mode is a
421    /// guarantee and neither is negotiated
422    /// ([decisions/0023](../../../docs/decisions/0023-completion-is-a-cursor.md)
423    /// §4.5).
424    ReportMode {
425        /// Records as the level advances, coalesced at the reporter's own
426        /// granularity.
427        #[default]
428        Progress = 0,
429        /// One record per level, at the end.
430        FinalOnly = 1,
431    }
432}
433
434/// A level a cursor can name: one weida defines, or one the application does.
435///
436/// The level space is **open**
437/// ([decisions/0023](../../../docs/decisions/0023-completion-is-a-cursor.md)
438/// §4.4): values below [`CursorLevel::APPLICATION_FLOOR`] are weida's own
439/// ladder, [`Acknowledgement`], and everything at or above it is an
440/// application stage weida carries and orders but never interprets. An
441/// undefined value *below* the floor is a protocol violation rather than an
442/// application level, because the reserved range is where a later version of
443/// this specification will put its own stages.
444#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
445pub enum CursorLevel {
446    /// A level this version of the protocol defines.
447    Known(Acknowledgement),
448    /// An application stage, at or above the floor.
449    Application(u64),
450}
451
452impl CursorLevel {
453    /// First wire value an application may name.
454    pub const APPLICATION_FLOOR: u64 = 16;
455
456    /// The wire value.
457    pub fn to_wire(self) -> u64 {
458        match self {
459            CursorLevel::Known(level) => level.to_wire(),
460            CursorLevel::Application(value) => value,
461        }
462    }
463
464    /// Interprets a wire value, or `None` if it is an undefined value in the
465    /// reserved range.
466    pub fn from_wire(value: u64) -> Option<CursorLevel> {
467        if value >= CursorLevel::APPLICATION_FLOOR {
468            Some(CursorLevel::Application(value))
469        } else {
470            Acknowledgement::from_wire(value).map(CursorLevel::Known)
471        }
472    }
473
474    /// An application stage, or `None` below the floor: the reserved range is
475    /// not an application's to name.
476    pub fn application(value: u64) -> Option<CursorLevel> {
477        (value >= CursorLevel::APPLICATION_FLOOR).then_some(CursorLevel::Application(value))
478    }
479}
480
481/// One level per guarantee dimension, as declared in HELLO keys `5` and `6`
482/// (`docs/PROTOCOL.md` §6.5).
483///
484/// [`GuaranteeSet::CORE`] is the default set and is exactly what v0 does, so
485/// an absent HELLO key, an empty map and `CORE` are the same statement
486/// ([decisions/0006](../../../docs/decisions/0006-guarantee-sets.md) §4.2).
487/// Every field is a small `Copy` value: a set costs no allocation, which is
488/// what lets it ride a header a peer controls.
489#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
490pub struct GuaranteeSet {
491    /// Delivery dimension.
492    pub delivery: Delivery,
493    /// Acknowledgement/completion dimension.
494    pub acknowledgement: Acknowledgement,
495    /// Persistence axis; legal only with `Stored` or `Replicated`.
496    pub durability: Option<Durability>,
497    /// Replica count, leader included; legal only with `Replicated`, and ≥ 2.
498    pub replicas: Option<u64>,
499    /// Ordering dimension.
500    pub ordering: OrderingMode,
501    /// Deduplication dimension.
502    pub deduplication: Deduplication,
503    /// Dedup window; required with `Bounded`, forbidden otherwise.
504    pub dedup_window_ms: Option<u64>,
505    /// Backpressure behaviour. Not ordered.
506    pub backpressure: Backpressure,
507    /// How the producer of a sequenced transfer is named. Not ordered.
508    pub producer_naming: ProducerNaming,
509    /// Whether control traffic is isolated from bulk traffic
510    /// ([decisions/0002](../../../docs/decisions/0002-control-and-bulk-separation.md)
511    /// §6.1). Ordered: `true` is strictly stronger.
512    pub control_isolated: bool,
513}
514
515impl GuaranteeSet {
516    /// The default set: what v0 offers and requires.
517    pub const CORE: GuaranteeSet = GuaranteeSet {
518        delivery: Delivery::BestEffort,
519        acknowledgement: Acknowledgement::TransportReceipt,
520        durability: None,
521        replicas: None,
522        ordering: OrderingMode::None,
523        deduplication: Deduplication::None,
524        dedup_window_ms: None,
525        backpressure: Backpressure::Block,
526        producer_naming: ProducerNaming::Fingerprint,
527        control_isolated: false,
528    };
529
530    /// Is this the default set? A `core` declaration is never written: it is
531    /// what an absent key already means.
532    pub fn is_core(&self) -> bool {
533        *self == GuaranteeSet::CORE
534    }
535
536    /// Checks the dimension combinations §6.5 forbids.
537    fn validate(&self) -> Result<(), HeaderError> {
538        let stored_or_replicated = matches!(
539            self.acknowledgement,
540            Acknowledgement::Stored | Acknowledgement::Replicated
541        );
542        if self.durability.is_some() && !stored_or_replicated {
543            return Err(HeaderError::InvalidGuarantees(
544                "durability without Stored or Replicated",
545            ));
546        }
547        match self.replicas {
548            Some(_) if self.acknowledgement != Acknowledgement::Replicated => {
549                return Err(HeaderError::InvalidGuarantees(
550                    "replicas without Replicated",
551                ));
552            }
553            Some(n) if n < 2 => {
554                return Err(HeaderError::InvalidGuarantees(
555                    "a replica count below 2 is not a replication",
556                ));
557            }
558            _ => {}
559        }
560        match (self.deduplication, self.dedup_window_ms) {
561            (Deduplication::Bounded, None) => {
562                return Err(HeaderError::InvalidGuarantees(
563                    "Bounded deduplication without a window",
564                ));
565            }
566            (level, Some(_)) if level != Deduplication::Bounded => {
567                return Err(HeaderError::InvalidGuarantees(
568                    "a dedup window without Bounded deduplication",
569                ));
570            }
571            _ => {}
572        }
573        Ok(())
574    }
575
576    /// Writes the set as a CBOR map, omitting every dimension left at `core`.
577    fn encode_into(
578        &self,
579        e: &mut Encoder<Vec<u8>>,
580    ) -> Result<(), minicbor::encode::Error<Infallible>> {
581        let core = GuaranteeSet::CORE;
582        let count = u64::from(self.delivery != core.delivery)
583            + u64::from(self.acknowledgement != core.acknowledgement)
584            + u64::from(self.durability.is_some())
585            + u64::from(self.replicas.is_some())
586            + u64::from(self.ordering != core.ordering)
587            + u64::from(self.deduplication != core.deduplication)
588            + u64::from(self.dedup_window_ms.is_some())
589            + u64::from(self.backpressure != core.backpressure)
590            + u64::from(self.producer_naming != core.producer_naming)
591            + u64::from(self.control_isolated != core.control_isolated);
592        e.map(count)?;
593        if self.delivery != core.delivery {
594            e.u64(guarantee_key::DELIVERY)?
595                .u64(self.delivery.to_wire())?;
596        }
597        if self.acknowledgement != core.acknowledgement {
598            e.u64(guarantee_key::ACKNOWLEDGEMENT)?
599                .u64(self.acknowledgement.to_wire())?;
600        }
601        if let Some(durability) = self.durability {
602            e.u64(guarantee_key::DURABILITY)?
603                .u64(durability.to_wire())?;
604        }
605        if let Some(replicas) = self.replicas {
606            e.u64(guarantee_key::REPLICAS)?.u64(replicas)?;
607        }
608        if self.ordering != core.ordering {
609            e.u64(guarantee_key::ORDERING)?
610                .u64(self.ordering.to_wire())?;
611        }
612        if self.deduplication != core.deduplication {
613            e.u64(guarantee_key::DEDUPLICATION)?
614                .u64(self.deduplication.to_wire())?;
615        }
616        if let Some(window) = self.dedup_window_ms {
617            e.u64(guarantee_key::DEDUP_WINDOW_MS)?.u64(window)?;
618        }
619        if self.backpressure != core.backpressure {
620            e.u64(guarantee_key::BACKPRESSURE)?
621                .u64(self.backpressure.to_wire())?;
622        }
623        if self.producer_naming != core.producer_naming {
624            e.u64(guarantee_key::PRODUCER_NAMING)?
625                .u64(self.producer_naming.to_wire())?;
626        }
627        if self.control_isolated != core.control_isolated {
628            e.u64(guarantee_key::CONTROL_ISOLATED)?
629                .u64(u64::from(self.control_isolated))?;
630        }
631        Ok(())
632    }
633
634    /// Reads a set from the nested map at the decoder's position.
635    ///
636    /// The nesting is one level deep by specification (§5), and an unknown
637    /// dimension is skipped exactly like an unknown top-level key.
638    fn decode_from(m: &mut MapReader<'_, '_>) -> Result<GuaranteeSet, HeaderError> {
639        let mut set = GuaranteeSet::CORE;
640        let mut inner = MapReader::new(m.d)?;
641        while let Some(key) = inner.next_key()? {
642            match key {
643                guarantee_key::DELIVERY => set.delivery = level(inner.u64()?, "delivery")?,
644                guarantee_key::ACKNOWLEDGEMENT => {
645                    set.acknowledgement = level(inner.u64()?, "acknowledgement")?;
646                }
647                guarantee_key::DURABILITY => {
648                    set.durability = Some(level(inner.u64()?, "durability")?);
649                }
650                guarantee_key::REPLICAS => set.replicas = Some(inner.u64()?),
651                guarantee_key::ORDERING => set.ordering = level(inner.u64()?, "ordering")?,
652                guarantee_key::DEDUPLICATION => {
653                    set.deduplication = level(inner.u64()?, "deduplication")?;
654                }
655                guarantee_key::DEDUP_WINDOW_MS => set.dedup_window_ms = Some(inner.u64()?),
656                guarantee_key::BACKPRESSURE => {
657                    set.backpressure = level(inner.u64()?, "backpressure")?;
658                }
659                guarantee_key::PRODUCER_NAMING => {
660                    set.producer_naming = level(inner.u64()?, "producer naming")?;
661                }
662                guarantee_key::CONTROL_ISOLATED => {
663                    set.control_isolated = match inner.u64()? {
664                        0 => false,
665                        1 => true,
666                        _ => {
667                            return Err(HeaderError::InvalidGuarantees(
668                                "control_isolated is 0 or 1",
669                            ));
670                        }
671                    };
672                }
673                _ => inner.skip()?,
674            }
675        }
676        set.validate()?;
677        Ok(set)
678    }
679
680    /// The weaker of two offered sets, dimension by dimension
681    /// (`docs/PROTOCOL.md` §2.3 step 5).
682    ///
683    /// Ladders take the minimum. Dimensions that are **not** ordered —
684    /// backpressure, producer naming, and the two independent axes of a
685    /// durability level — have no "weaker", so the two declarations must be
686    /// equal; the name of the dimension comes back as the error so a peer can
687    /// be told which one disagreed.
688    pub fn intersect(&self, other: &GuaranteeSet) -> Result<GuaranteeSet, &'static str> {
689        if self.backpressure != other.backpressure {
690            return Err("backpressure");
691        }
692        if self.producer_naming != other.producer_naming {
693            return Err("producer naming");
694        }
695        if self.durability.is_some()
696            && other.durability.is_some()
697            && self.durability != other.durability
698        {
699            return Err("durability");
700        }
701        if self.replicas.is_some() && other.replicas.is_some() && self.replicas != other.replicas {
702            return Err("replicas");
703        }
704
705        let acknowledgement = self.acknowledgement.min(other.acknowledgement);
706        let keeps_durability = matches!(
707            acknowledgement,
708            Acknowledgement::Stored | Acknowledgement::Replicated
709        );
710        let deduplication = self.deduplication.min(other.deduplication);
711        let mut merged = GuaranteeSet {
712            delivery: self.delivery.min(other.delivery),
713            acknowledgement,
714            // A dimension the weakened acknowledgement can no longer carry is
715            // dropped rather than kept: dropping is what "weaker" means here,
716            // and keeping it would produce a set §6.5 forbids.
717            durability: keeps_durability
718                .then_some(self.durability.or(other.durability))
719                .flatten(),
720            replicas: (acknowledgement == Acknowledgement::Replicated)
721                .then_some(self.replicas.or(other.replicas))
722                .flatten(),
723            ordering: self.ordering.min(other.ordering),
724            deduplication,
725            // A shorter window is the weaker promise.
726            dedup_window_ms: None,
727            backpressure: self.backpressure,
728            producer_naming: self.producer_naming,
729            control_isolated: self.control_isolated && other.control_isolated,
730        };
731        if deduplication == Deduplication::Bounded {
732            merged.dedup_window_ms = match (self.dedup_window_ms, other.dedup_window_ms) {
733                (Some(a), Some(b)) => Some(a.min(b)),
734                (Some(a), None) | (None, Some(a)) => Some(a),
735                (None, None) => None,
736            };
737        }
738        Ok(merged)
739    }
740
741    /// Does this set reach `required` on every dimension?
742    ///
743    /// Ladders compare by level, the durability axes compare per axis, and the
744    /// unordered dimensions must match exactly. A longer dedup window is the
745    /// stronger promise.
746    pub fn reaches(&self, required: &GuaranteeSet) -> bool {
747        if self.delivery < required.delivery
748            || self.acknowledgement < required.acknowledgement
749            || self.ordering < required.ordering
750            || self.deduplication < required.deduplication
751        {
752            return false;
753        }
754        if self.backpressure != required.backpressure
755            || self.producer_naming != required.producer_naming
756        {
757            return false;
758        }
759        if !self.control_isolated && required.control_isolated {
760            return false;
761        }
762        match (self.durability, required.durability) {
763            (_, None) => {}
764            (Some(have), Some(want)) if have >= want => {}
765            _ => return false,
766        }
767        match (self.replicas, required.replicas) {
768            (_, None) => {}
769            (Some(have), Some(want)) if have >= want => {}
770            _ => return false,
771        }
772        match (self.dedup_window_ms, required.dedup_window_ms) {
773            (_, None) => {}
774            (Some(have), Some(want)) if have >= want => {}
775            _ => return false,
776        }
777        true
778    }
779}
780
781/// Maps a wire value to a level, naming the dimension when it is unknown.
782fn level<T: WireLevel>(value: u64, dimension: &'static str) -> Result<T, HeaderError> {
783    T::from_wire_value(value).ok_or(HeaderError::UnknownLevel { dimension, value })
784}
785
786/// Lets [`level`] work for every dimension enum without a macro per call.
787trait WireLevel: Sized {
788    fn from_wire_value(value: u64) -> Option<Self>;
789}
790
791macro_rules! impl_wire_level {
792    ($($name:ident),+ $(,)?) => {
793        $(impl WireLevel for $name {
794            fn from_wire_value(value: u64) -> Option<$name> {
795                $name::from_wire(value)
796            }
797        })+
798    };
799}
800
801impl_wire_level!(
802    Delivery,
803    Acknowledgement,
804    Durability,
805    OrderingMode,
806    Deduplication,
807    Backpressure,
808    ProducerNaming,
809    ReportMode,
810);
811
812/// Why a header was rejected. Every variant is a protocol violation.
813#[derive(Clone, Debug, PartialEq, Eq)]
814pub enum HeaderError {
815    /// The bytes are not well-formed CBOR, or a value had the wrong type.
816    Malformed(&'static str),
817    /// An indefinite-length item was used where the protocol forbids it.
818    Indefinite,
819    /// A map key appeared twice.
820    DuplicateKey(u64),
821    /// A map key was not greater than the preceding one; keys must ascend.
822    UnorderedKey(u64),
823    /// A map key was not an unsigned integer.
824    NonUintKey,
825    /// A required key was absent.
826    MissingKey(u64),
827    /// A text field exceeded its cap.
828    StringTooLong {
829        /// Key of the offending field.
830        key: u64,
831        /// Length found.
832        len: usize,
833        /// Cap for this field.
834        max: usize,
835    },
836    /// A list field declared more items than the decoder accepts.
837    ListTooLong {
838        /// Key of the offending field.
839        key: u64,
840        /// Declared item count.
841        len: u64,
842        /// Cap for list fields.
843        max: usize,
844    },
845    /// An unknown field nested deeper than [`limits::MAX_SKIP_DEPTH`].
846    DepthExceeded,
847    /// Bytes remained after the header map.
848    TrailingBytes,
849    /// A guarantee set, or a DATA header's achieved level, named a level this
850    /// version does not define.
851    UnknownLevel {
852        /// Dimension whose value was unknown.
853        dimension: &'static str,
854        /// The value found.
855        value: u64,
856    },
857    /// A guarantee set's dimension combination is one §6.5 forbids, or a
858    /// HELLO requires more than it offers (§6.1).
859    InvalidGuarantees(&'static str),
860    /// A topic filter violated the grammar of `docs/PROTOCOL.md` §6.4.
861    InvalidFilter(&'static str),
862    /// A DATA header's report order is malformed: the levels do not ascend,
863    /// there are too many of them, or the order and its id disagree
864    /// (`docs/PROTOCOL.md` §6.2, keys `9`-`11`).
865    InvalidReport(&'static str),
866}
867
868impl std::fmt::Display for HeaderError {
869    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
870        match self {
871            HeaderError::Malformed(what) => write!(f, "malformed header: {what}"),
872            HeaderError::Indefinite => f.write_str("indefinite-length items are not allowed"),
873            HeaderError::DuplicateKey(k) => write!(f, "duplicate header key {k}"),
874            HeaderError::UnorderedKey(k) => {
875                write!(f, "header key {k} is out of ascending order")
876            }
877            HeaderError::NonUintKey => f.write_str("header key is not an unsigned integer"),
878            HeaderError::MissingKey(k) => write!(f, "required header key {k} is missing"),
879            HeaderError::StringTooLong { key, len, max } => {
880                write!(
881                    f,
882                    "key {key}: text of {len} bytes exceeds the {max} byte cap"
883                )
884            }
885            HeaderError::ListTooLong { key, len, max } => {
886                write!(
887                    f,
888                    "key {key}: list of {len} items exceeds the {max} item cap"
889                )
890            }
891            HeaderError::DepthExceeded => f.write_str("unknown field nested too deeply"),
892            HeaderError::TrailingBytes => f.write_str("trailing bytes after the header"),
893            HeaderError::UnknownLevel { dimension, value } => {
894                write!(f, "unknown {dimension} level {value}")
895            }
896            HeaderError::InvalidGuarantees(why) => write!(f, "invalid guarantee set: {why}"),
897            HeaderError::InvalidFilter(why) => write!(f, "invalid topic filter: {why}"),
898            HeaderError::InvalidReport(reason) => write!(f, "invalid report: {reason}"),
899        }
900    }
901}
902
903impl std::error::Error for HeaderError {}
904
905impl From<HeaderError> for Error {
906    fn from(e: HeaderError) -> Error {
907        Error::Protocol(e.to_string())
908    }
909}
910
911/// Appends an encoded header to a buffer the caller owns.
912///
913/// Every `encode` in this file is a thin wrapper over an `encode_into` that
914/// goes through here, so a hot send path can reuse one buffer and the
915/// canonical form has exactly one implementation — the property that matters,
916/// because two encoders that can disagree about key order would be a wire
917/// divergence rather than an optimisation (B-250).
918fn encode_into_with(
919    out: &mut Vec<u8>,
920    f: impl FnOnce(&mut Encoder<Vec<u8>>) -> Result<(), minicbor::encode::Error<Infallible>>,
921) {
922    // `Encoder` owns its writer, so the buffer is handed over and taken back.
923    // `std::mem::take` keeps the caller's allocation: the `Vec` that comes
924    // back is the same one, grown at most by this header.
925    let mut e = Encoder::new(std::mem::take(out));
926    f(&mut e).expect("encoding into a Vec is infallible");
927    *out = e.into_writer();
928}
929
930/// Skips one CBOR value iteratively, refusing to recurse and refusing to nest
931/// deeper than `max_depth`.
932///
933/// `minicbor` has its own `skip`, but the protocol requires a specific,
934/// auditable bound on hostile nesting; a value that nests deeper is rejected
935/// rather than tolerated.
936fn skip_value(d: &mut Decoder<'_>, max_depth: usize) -> Result<(), HeaderError> {
937    // `stack` holds the outstanding item counts of enclosing containers; its
938    // length is the current nesting depth and is bounded by `max_depth`.
939    let mut stack: Vec<u64> = Vec::new();
940    let mut remaining: u64 = 1;
941
942    loop {
943        if remaining == 0 {
944            match stack.pop() {
945                Some(outer) => {
946                    remaining = outer;
947                    continue;
948                }
949                None => return Ok(()),
950            }
951        }
952        remaining -= 1;
953
954        let ty = d
955            .datatype()
956            .map_err(|_| HeaderError::Malformed("truncated value"))?;
957        let nested = match ty {
958            Type::Bool => {
959                d.bool().map_err(|_| HeaderError::Malformed("bool"))?;
960                None
961            }
962            Type::Null => {
963                d.null().map_err(|_| HeaderError::Malformed("null"))?;
964                None
965            }
966            Type::Undefined => {
967                d.undefined()
968                    .map_err(|_| HeaderError::Malformed("undefined"))?;
969                None
970            }
971            Type::U8
972            | Type::U16
973            | Type::U32
974            | Type::U64
975            | Type::I8
976            | Type::I16
977            | Type::I32
978            | Type::I64
979            | Type::Int => {
980                d.int().map_err(|_| HeaderError::Malformed("integer"))?;
981                None
982            }
983            Type::F32 | Type::F64 => {
984                d.f64().map_err(|_| HeaderError::Malformed("float"))?;
985                None
986            }
987            Type::Bytes => {
988                d.bytes()
989                    .map_err(|_| HeaderError::Malformed("byte string"))?;
990                None
991            }
992            Type::String => {
993                d.str().map_err(|_| HeaderError::Malformed("text string"))?;
994                None
995            }
996            Type::Array => Some(
997                d.array()
998                    .map_err(|_| HeaderError::Malformed("array"))?
999                    .ok_or(HeaderError::Indefinite)?,
1000            ),
1001            Type::Map => {
1002                let pairs = d
1003                    .map()
1004                    .map_err(|_| HeaderError::Malformed("map"))?
1005                    .ok_or(HeaderError::Indefinite)?;
1006                Some(
1007                    pairs
1008                        .checked_mul(2)
1009                        .ok_or(HeaderError::Malformed("map length overflow"))?,
1010                )
1011            }
1012            Type::BytesIndef | Type::StringIndef | Type::ArrayIndef | Type::MapIndef => {
1013                return Err(HeaderError::Indefinite);
1014            }
1015            Type::Break => return Err(HeaderError::Malformed("unexpected break")),
1016            // Tags, half-floats and other simple values carry no meaning in
1017            // weida headers; extensions must use plain data items.
1018            Type::Tag => return Err(HeaderError::Malformed("tags are not allowed")),
1019            Type::F16 => return Err(HeaderError::Malformed("half floats are not allowed")),
1020            Type::Simple => return Err(HeaderError::Malformed("simple values are not allowed")),
1021            Type::Unknown(_) => return Err(HeaderError::Malformed("unknown major type")),
1022        };
1023
1024        if let Some(count) = nested
1025            && count > 0
1026        {
1027            if stack.len() >= max_depth {
1028                return Err(HeaderError::DepthExceeded);
1029            }
1030            stack.push(remaining);
1031            remaining = count;
1032        }
1033    }
1034}
1035
1036/// Reader for one strict header map.
1037struct MapReader<'a, 'b> {
1038    d: &'a mut Decoder<'b>,
1039    remaining: u64,
1040    /// Bitmask of seen keys `0..=63`, for the required-key checks. The
1041    /// specification reserves that range, so presence needs no allocation.
1042    seen: u64,
1043    /// Previously read key.
1044    ///
1045    /// Keys are required to ascend strictly, which makes duplicate detection
1046    /// complete for *every* key — including extension keys the decoder skips —
1047    /// in constant space. A set of seen extension keys would be exactly the
1048    /// remote-controlled allocation the invariants forbid.
1049    last: Option<u64>,
1050}
1051
1052impl<'a, 'b> MapReader<'a, 'b> {
1053    fn new(d: &'a mut Decoder<'b>) -> Result<MapReader<'a, 'b>, HeaderError> {
1054        let len = d
1055            .map()
1056            .map_err(|_| HeaderError::Malformed("header is not a map"))?
1057            .ok_or(HeaderError::Indefinite)?;
1058        Ok(MapReader {
1059            d,
1060            remaining: len,
1061            seen: 0,
1062            last: None,
1063        })
1064    }
1065
1066    fn next_key(&mut self) -> Result<Option<u64>, HeaderError> {
1067        if self.remaining == 0 {
1068            return Ok(None);
1069        }
1070        self.remaining -= 1;
1071        match self.d.datatype() {
1072            Ok(Type::U8 | Type::U16 | Type::U32 | Type::U64) => {}
1073            Ok(_) => return Err(HeaderError::NonUintKey),
1074            Err(_) => return Err(HeaderError::Malformed("truncated key")),
1075        }
1076        let key = self.d.u64().map_err(|_| HeaderError::NonUintKey)?;
1077        if let Some(prev) = self.last {
1078            if key == prev {
1079                return Err(HeaderError::DuplicateKey(key));
1080            }
1081            if key < prev {
1082                return Err(HeaderError::UnorderedKey(key));
1083            }
1084        }
1085        self.last = Some(key);
1086        if key < 64 {
1087            self.seen |= 1u64 << key;
1088        }
1089        Ok(Some(key))
1090    }
1091
1092    fn saw(&self, key: u64) -> bool {
1093        key < 64 && self.seen & (1u64 << key) != 0
1094    }
1095
1096    fn require(&self, key: u64) -> Result<(), HeaderError> {
1097        if self.saw(key) {
1098            Ok(())
1099        } else {
1100            Err(HeaderError::MissingKey(key))
1101        }
1102    }
1103
1104    fn u64(&mut self) -> Result<u64, HeaderError> {
1105        self.d
1106            .u64()
1107            .map_err(|_| HeaderError::Malformed("expected an unsigned integer"))
1108    }
1109
1110    fn text(&mut self, key: u64, max: usize) -> Result<String, HeaderError> {
1111        let s = self
1112            .d
1113            .str()
1114            .map_err(|_| HeaderError::Malformed("expected a text string"))?;
1115        if s.len() > max {
1116            return Err(HeaderError::StringTooLong {
1117                key,
1118                len: s.len(),
1119                max,
1120            });
1121        }
1122        Ok(s.to_owned())
1123    }
1124
1125    /// Reads a byte string of exactly `N` bytes.
1126    ///
1127    /// The cap is checked before the length is trusted for anything, and a
1128    /// shorter value is rejected rather than padded: `docs/PROTOCOL.md` §6.2
1129    /// defines the one field that uses this as a raw 32-byte digest, and half
1130    /// a digest identifies nobody.
1131    fn byte_array<const N: usize>(&mut self, key: u64) -> Result<[u8; N], HeaderError> {
1132        let bytes = self
1133            .d
1134            .bytes()
1135            .map_err(|_| HeaderError::Malformed("expected a byte string"))?;
1136        if bytes.len() > N {
1137            return Err(HeaderError::StringTooLong {
1138                key,
1139                len: bytes.len(),
1140                max: N,
1141            });
1142        }
1143        bytes
1144            .try_into()
1145            .map_err(|_| HeaderError::Malformed("byte string has the wrong length"))
1146    }
1147
1148    fn uint_list(&mut self, key: u64) -> Result<Vec<u64>, HeaderError> {
1149        let len = self
1150            .d
1151            .array()
1152            .map_err(|_| HeaderError::Malformed("expected an array"))?
1153            .ok_or(HeaderError::Indefinite)?;
1154        if len > limits::MAX_LIST_ITEMS as u64 {
1155            return Err(HeaderError::ListTooLong {
1156                key,
1157                len,
1158                max: limits::MAX_LIST_ITEMS,
1159            });
1160        }
1161        // `len` is now bounded by MAX_LIST_ITEMS, so reserving is safe.
1162        let mut out = Vec::with_capacity(len as usize);
1163        for _ in 0..len {
1164            out.push(self.u64()?);
1165        }
1166        Ok(out)
1167    }
1168
1169    /// Reads a report order: a definite-length array of strictly ascending
1170    /// cursor levels, capped at [`limits::MAX_REPORT_LEVELS`].
1171    ///
1172    /// Ascent is checked here rather than after the fact for the same reason
1173    /// map keys are: it makes duplicate detection complete in constant space,
1174    /// and it makes the wire form canonical, so two peers ordering the same
1175    /// levels send the same bytes.
1176    fn report_levels(&mut self) -> Result<Vec<CursorLevel>, HeaderError> {
1177        let len = self
1178            .d
1179            .array()
1180            .map_err(|_| HeaderError::Malformed("expected an array"))?
1181            .ok_or(HeaderError::Indefinite)?;
1182        if len > limits::MAX_REPORT_LEVELS as u64 {
1183            return Err(HeaderError::InvalidReport("too many report levels"));
1184        }
1185        // `len` is now bounded by MAX_REPORT_LEVELS, so reserving is safe.
1186        let mut out: Vec<CursorLevel> = Vec::with_capacity(len as usize);
1187        let mut last: Option<u64> = None;
1188        for _ in 0..len {
1189            let value = self.u64()?;
1190            if let Some(prev) = last
1191                && value <= prev
1192            {
1193                return Err(HeaderError::InvalidReport("report levels must ascend"));
1194            }
1195            last = Some(value);
1196            out.push(
1197                CursorLevel::from_wire(value).ok_or(HeaderError::UnknownLevel {
1198                    dimension: "report",
1199                    value,
1200                })?,
1201            );
1202        }
1203        Ok(out)
1204    }
1205
1206    fn skip(&mut self) -> Result<(), HeaderError> {
1207        skip_value(self.d, limits::MAX_SKIP_DEPTH)
1208    }
1209}
1210
1211/// Rejects trailing bytes after a header map.
1212fn finish(d: &Decoder<'_>) -> Result<(), HeaderError> {
1213    if d.position() == d.input().len() {
1214        Ok(())
1215    } else {
1216        Err(HeaderError::TrailingBytes)
1217    }
1218}
1219
1220/// HELLO header: connection negotiation input.
1221#[derive(Clone, Debug, PartialEq, Eq)]
1222pub struct Hello {
1223    /// Wire protocol versions the sender supports.
1224    pub versions: Vec<u64>,
1225    /// Largest header the sender is willing to receive.
1226    pub max_header_bytes: u64,
1227    /// Advisory concurrent inbound transfer count.
1228    pub max_transfers: u64,
1229    /// Capability codes the sender supports.
1230    pub capabilities: Vec<u64>,
1231    /// Capability codes the sender requires the peer to support.
1232    pub required_capabilities: Vec<u64>,
1233    /// Guarantee set the sender can honour (key `5`).
1234    ///
1235    /// `None` means the default set: an absent key and
1236    /// [`GuaranteeSet::CORE`] are the same declaration, which is why a v0
1237    /// HELLO is unchanged on the wire.
1238    pub guarantees_offered: Option<GuaranteeSet>,
1239    /// Guarantee set the sender requires of the peer (key `6`).
1240    ///
1241    /// MUST be reachable by `guarantees_offered` on every dimension: requiring
1242    /// what you cannot honour yourself is a configuration error
1243    /// (`docs/PROTOCOL.md` §6.1), and a decoder rejects it.
1244    pub guarantees_required: Option<GuaranteeSet>,
1245}
1246
1247impl Hello {
1248    /// The HELLO a v0 implementation sends: no guarantee declarations, so
1249    /// `core` offered and `core` required.
1250    pub fn v0(max_header_bytes: u64, max_transfers: u64) -> Hello {
1251        Hello {
1252            versions: vec![crate::VERSION],
1253            max_header_bytes,
1254            max_transfers,
1255            capabilities: Vec::new(),
1256            required_capabilities: Vec::new(),
1257            guarantees_offered: None,
1258            guarantees_required: None,
1259        }
1260    }
1261
1262    /// The set this HELLO offers; an absent declaration means `core`.
1263    pub fn offered(&self) -> GuaranteeSet {
1264        self.guarantees_offered.unwrap_or(GuaranteeSet::CORE)
1265    }
1266
1267    /// The set this HELLO requires; an absent declaration means `core`.
1268    pub fn required(&self) -> GuaranteeSet {
1269        self.guarantees_required.unwrap_or(GuaranteeSet::CORE)
1270    }
1271
1272    /// Encodes the header.
1273    pub fn encode(&self) -> Vec<u8> {
1274        let mut out = Vec::new();
1275        self.encode_into(&mut out);
1276        out
1277    }
1278
1279    /// Appends the encoded header to `out`, for a send path that reuses a
1280    /// buffer (B-250). The canonical form has one implementation and this is
1281    /// it; [`Self::encode`] is a wrapper.
1282    pub fn encode_into(&self, out: &mut Vec<u8>) {
1283        encode_into_with(out, |e| {
1284            // A `core` declaration is never written: an absent key already
1285            // says it, and a v0 HELLO must stay byte-identical (§6.1).
1286            let offered = self.guarantees_offered.filter(|s| !s.is_core());
1287            let required = self.guarantees_required.filter(|s| !s.is_core());
1288            e.map(5 + u64::from(offered.is_some()) + u64::from(required.is_some()))?;
1289            e.u64(hello_key::VERSIONS)?
1290                .array(self.versions.len() as u64)?;
1291            for v in &self.versions {
1292                e.u64(*v)?;
1293            }
1294            e.u64(hello_key::MAX_HEADER_BYTES)?
1295                .u64(self.max_header_bytes)?;
1296            e.u64(hello_key::MAX_TRANSFERS)?.u64(self.max_transfers)?;
1297            e.u64(hello_key::CAPABILITIES)?
1298                .array(self.capabilities.len() as u64)?;
1299            for c in &self.capabilities {
1300                e.u64(*c)?;
1301            }
1302            e.u64(hello_key::REQUIRED_CAPABILITIES)?
1303                .array(self.required_capabilities.len() as u64)?;
1304            for c in &self.required_capabilities {
1305                e.u64(*c)?;
1306            }
1307            if let Some(set) = offered {
1308                e.u64(hello_key::GUARANTEES_OFFERED)?;
1309                set.encode_into(e)?;
1310            }
1311            if let Some(set) = required {
1312                e.u64(hello_key::GUARANTEES_REQUIRED)?;
1313                set.encode_into(e)?;
1314            }
1315            Ok(())
1316        })
1317    }
1318
1319    /// Decodes the header.
1320    pub fn decode(bytes: &[u8]) -> Result<Hello, HeaderError> {
1321        let mut d = Decoder::new(bytes);
1322        let mut versions = Vec::new();
1323        let mut max_header_bytes = 0;
1324        let mut max_transfers = 0;
1325        let mut capabilities = Vec::new();
1326        let mut required_capabilities = Vec::new();
1327        let mut guarantees_offered = None;
1328        let mut guarantees_required = None;
1329        {
1330            let mut m = MapReader::new(&mut d)?;
1331            while let Some(key) = m.next_key()? {
1332                match key {
1333                    hello_key::VERSIONS => versions = m.uint_list(key)?,
1334                    hello_key::MAX_HEADER_BYTES => max_header_bytes = m.u64()?,
1335                    hello_key::MAX_TRANSFERS => max_transfers = m.u64()?,
1336                    hello_key::CAPABILITIES => capabilities = m.uint_list(key)?,
1337                    hello_key::REQUIRED_CAPABILITIES => required_capabilities = m.uint_list(key)?,
1338                    hello_key::GUARANTEES_OFFERED => {
1339                        guarantees_offered = Some(GuaranteeSet::decode_from(&mut m)?);
1340                    }
1341                    hello_key::GUARANTEES_REQUIRED => {
1342                        guarantees_required = Some(GuaranteeSet::decode_from(&mut m)?);
1343                    }
1344                    _ => m.skip()?,
1345                }
1346            }
1347            for key in [
1348                hello_key::VERSIONS,
1349                hello_key::MAX_HEADER_BYTES,
1350                hello_key::MAX_TRANSFERS,
1351                hello_key::CAPABILITIES,
1352                hello_key::REQUIRED_CAPABILITIES,
1353            ] {
1354                m.require(key)?;
1355            }
1356        }
1357        finish(&d)?;
1358        let hello = Hello {
1359            versions,
1360            max_header_bytes,
1361            max_transfers,
1362            capabilities,
1363            required_capabilities,
1364            guarantees_offered,
1365            guarantees_required,
1366        };
1367        // §6.1: requiring more than you offer is a configuration error, and
1368        // one a decoder can see in a single header.
1369        if !hello.offered().reaches(&hello.required()) {
1370            return Err(HeaderError::InvalidGuarantees(
1371                "guarantees_required is not covered by guarantees_offered",
1372            ));
1373        }
1374        Ok(hello)
1375    }
1376}
1377
1378/// DATA header: one transfer.
1379///
1380/// Every field is optional at the decoder. Which of them the *context*
1381/// requires is a dispatch question: an initiating stream must name an endpoint
1382/// and the reply half of an exchange must not, but the decoder sees bytes, not
1383/// streams (`docs/PROTOCOL.md` §6.2).
1384#[derive(Clone, Debug, Default, PartialEq, Eq)]
1385pub struct DataHeader {
1386    /// Endpoint path. Required on an initiating stream, ignored on a reply.
1387    pub endpoint: Option<String>,
1388    /// Advisory payload length.
1389    pub content_len: Option<u64>,
1390    /// Opaque content type label.
1391    pub content_type: Option<String>,
1392    /// W3C `traceparent`.
1393    pub traceparent: Option<String>,
1394    /// W3C `tracestate`, opaque passthrough.
1395    pub tracestate: Option<String>,
1396    /// Pub/Sub topic; opaque bytes, selected by the filter grammar of
1397    /// `docs/PROTOCOL.md` §6.4. Only meaningful on transfers fanned out by a
1398    /// publisher.
1399    pub topic: Option<String>,
1400    /// Per-producer sequence number, for ordering and gap detection
1401    /// (`docs/PROTOCOL.md` §6.2, key `6`).
1402    ///
1403    /// Written by a publisher whose connection negotiated `PerProducer`
1404    /// ordering, and by nothing under `core`: the number is assigned once per
1405    /// published message, before fan-out, so a copy a subscriber lost shows up
1406    /// as a hole in its own sequence. It is not a transfer identifier and
1407    /// correlates nothing — an exchange is correlated by its stream.
1408    pub sequence: Option<u64>,
1409    /// Producer identity: the raw 32-byte digest (`docs/PROTOCOL.md` §6.2,
1410    /// key `7`).
1411    ///
1412    /// **Specified ahead of code**, and absent in the default case by design:
1413    /// the receiver already knows the sending peer's proved fingerprint from
1414    /// the handshake, so this names a producer only where it is *not* the
1415    /// connection peer — a relay, or a name an L2 subscription supplies
1416    /// ([decisions/0008](../../../docs/decisions/0008-session-identity.md)
1417    /// §4.4). The `sha256:<64 hex>` spelling is presentation only and never
1418    /// goes on the wire.
1419    pub producer: Option<[u8; limits::PRODUCER_BYTES]>,
1420    /// The completion level the sender **achieved** for the message it is
1421    /// answering (`docs/PROTOCOL.md` §6.2, key `8`).
1422    ///
1423    /// This is the L2 confirm, and it is a statement about one hop: a broker
1424    /// that has taken responsibility for a message in memory writes
1425    /// [`Acknowledgement::Accepted`] on the reply half of the producer's
1426    /// exchange, which is what makes the reply a publisher confirm without a
1427    /// frame kind of its own
1428    /// ([decisions/0018](../../../docs/decisions/0018-minimal-broker.md)
1429    /// §4.6). It is *achieved*, never requested — a level a peer wants is
1430    /// negotiated in HELLO and refused there if it cannot be reached
1431    /// ([0006](../../../docs/decisions/0006-guarantee-sets.md) §4.4) — and it
1432    /// is never relayed: the producer's confirm says nothing about what a
1433    /// consumer later does with the message
1434    /// ([GUARANTEES.md] §2).
1435    ///
1436    /// A v0 sender leaves it absent, and an absent key is not
1437    /// `Acknowledgement::None`: it says this hop makes no claim beyond the
1438    /// transport receipt QUIC already gave.
1439    ///
1440    /// [GUARANTEES.md]: https://git.doodleshnookie.net/tuco86/weida/blob/main/docs/GUARANTEES.md
1441    pub achieved: Option<Acknowledgement>,
1442    /// Identifier the sender assigns to the report it orders (key `9`).
1443    ///
1444    /// Present exactly when [`DataHeader::report`] is non-empty. It names the
1445    /// CURSOR stream that will report on *this* transfer, and it is scoped to
1446    /// the connection and to the direction that allocated it: a peer reports
1447    /// only on transfers it received, so the two directions cannot collide.
1448    pub report_id: Option<u64>,
1449    /// Levels the sender asks to be reported, strictly ascending (key `10`).
1450    ///
1451    /// An **order**, not a guarantee: a receiver that cannot reach a level
1452    /// simply does not report it, and the transfer does not fail for it. A
1453    /// level a peer must reach is the negotiated `acknowledgement` dimension
1454    /// of HELLO instead
1455    /// ([decisions/0006](../../../docs/decisions/0006-guarantee-sets.md)
1456    /// §4.4).
1457    pub report: Vec<CursorLevel>,
1458    /// How often the reporter should emit a record (key `11`).
1459    ///
1460    /// [`ReportMode::Progress`] is the default and is never written.
1461    pub report_mode: ReportMode,
1462    /// RADIO segment number per `(radio path, topic)`, from 0 (key `13`).
1463    ///
1464    /// Written by a radio only
1465    /// ([decisions/0034](../../../docs/decisions/0034-late-is-lost.md)
1466    /// §4.6). It is not a `PerProducer` sequence: it is written whatever
1467    /// ordering was negotiated, and a dish uses it to discard a segment older
1468    /// than the newest it delivered.
1469    pub segment: Option<u64>,
1470}
1471
1472impl DataHeader {
1473    /// A header addressing `endpoint`, for the initiating half of a stream.
1474    pub fn addressed(endpoint: impl Into<String>) -> DataHeader {
1475        DataHeader {
1476            endpoint: Some(endpoint.into()),
1477            ..DataHeader::default()
1478        }
1479    }
1480
1481    /// A header for the reply half of an exchange: no endpoint, no topic.
1482    ///
1483    /// The stream is the correlation, so a reply carries no identifier of the
1484    /// request it answers.
1485    pub fn reply() -> DataHeader {
1486        DataHeader::default()
1487    }
1488
1489    /// Encodes the header.
1490    ///
1491    /// Key `10` goes out in the canonical form §6.2 makes normative —
1492    /// strictly ascending by wire value, no repeats — whatever order
1493    /// [`DataHeader::report`] happens to hold. That rule is enforced here
1494    /// because encoding cannot fail: a vector in any other order would
1495    /// otherwise produce bytes that close the connection at every conformant
1496    /// peer, and there would be no way to tell the caller so.
1497    ///
1498    /// The report's other two rules stay the caller's for exactly that
1499    /// reason — both need an error, and this function has none to give. At
1500    /// most [`limits::MAX_REPORT_LEVELS`] distinct levels, and key `9`
1501    /// present exactly when key `10` is: `weida`'s `data_header` refuses an
1502    /// oversized order with `Error::LimitExceeded` and allocates the report
1503    /// id alongside the order, so no caller reaches this encoder with either
1504    /// mistake.
1505    pub fn encode(&self) -> Vec<u8> {
1506        let mut out = Vec::new();
1507        self.encode_into(&mut out);
1508        out
1509    }
1510
1511    /// Appends the encoded header to `out`, for a send path that reuses a
1512    /// buffer (B-250). The canonical form has one implementation and this is
1513    /// it; [`Self::encode`] is a wrapper.
1514    pub fn encode_into(&self, out: &mut Vec<u8>) {
1515        // Sorted and deduplicated by wire value, not by the enum's derived
1516        // order, because the wire value is what ascends on the wire.
1517        let mut report: Vec<u64> = self.report.iter().map(|level| level.to_wire()).collect();
1518        report.sort_unstable();
1519        report.dedup();
1520        let count = u64::from(self.endpoint.is_some())
1521            + u64::from(self.content_len.is_some())
1522            + u64::from(self.content_type.is_some())
1523            + u64::from(self.traceparent.is_some())
1524            + u64::from(self.tracestate.is_some())
1525            + u64::from(self.topic.is_some())
1526            + u64::from(self.sequence.is_some())
1527            + u64::from(self.producer.is_some())
1528            + u64::from(self.achieved.is_some())
1529            + u64::from(self.report_id.is_some())
1530            + u64::from(!report.is_empty())
1531            + u64::from(self.report_mode != ReportMode::default())
1532            + u64::from(self.segment.is_some());
1533        encode_into_with(out, |e| {
1534            e.map(count)?;
1535            if let Some(endpoint) = &self.endpoint {
1536                e.u64(data_key::ENDPOINT)?.str(endpoint)?;
1537            }
1538            if let Some(len) = self.content_len {
1539                e.u64(data_key::CONTENT_LEN)?.u64(len)?;
1540            }
1541            if let Some(ct) = &self.content_type {
1542                e.u64(data_key::CONTENT_TYPE)?.str(ct)?;
1543            }
1544            if let Some(tp) = &self.traceparent {
1545                e.u64(data_key::TRACEPARENT)?.str(tp)?;
1546            }
1547            if let Some(ts) = &self.tracestate {
1548                e.u64(data_key::TRACESTATE)?.str(ts)?;
1549            }
1550            if let Some(topic) = &self.topic {
1551                e.u64(data_key::TOPIC)?.str(topic)?;
1552            }
1553            // Keys 6 and 7 are written only when set, which for every v0
1554            // sender means never: nothing in `weida` populates them yet.
1555            if let Some(sequence) = self.sequence {
1556                e.u64(data_key::SEQUENCE)?.u64(sequence)?;
1557            }
1558            if let Some(producer) = &self.producer {
1559                e.u64(data_key::PRODUCER)?.bytes(producer)?;
1560            }
1561            if let Some(achieved) = self.achieved {
1562                e.u64(data_key::ACHIEVED)?.u64(achieved.to_wire())?;
1563            }
1564            if let Some(report_id) = self.report_id {
1565                e.u64(data_key::REPORT_ID)?.u64(report_id)?;
1566            }
1567            if !report.is_empty() {
1568                e.u64(data_key::REPORT)?.array(report.len() as u64)?;
1569                for value in &report {
1570                    e.u64(*value)?;
1571                }
1572            }
1573            // `Progress` is never written: an absent key already says it, so
1574            // a header that orders a report in the default mode stays as
1575            // short as the mode is uninteresting (§6.5's rule for levels).
1576            if self.report_mode != ReportMode::default() {
1577                e.u64(data_key::REPORT_MODE)?
1578                    .u64(self.report_mode.to_wire())?;
1579            }
1580            if let Some(segment) = self.segment {
1581                e.u64(data_key::SEGMENT)?.u64(segment)?;
1582            }
1583            Ok(())
1584        })
1585    }
1586
1587    /// Decodes the header.
1588    pub fn decode(bytes: &[u8]) -> Result<DataHeader, HeaderError> {
1589        let mut d = Decoder::new(bytes);
1590        let mut header = DataHeader::default();
1591        {
1592            let mut m = MapReader::new(&mut d)?;
1593            while let Some(key) = m.next_key()? {
1594                match key {
1595                    data_key::ENDPOINT => {
1596                        header.endpoint = Some(m.text(key, limits::MAX_ENDPOINT_BYTES)?)
1597                    }
1598                    data_key::CONTENT_LEN => header.content_len = Some(m.u64()?),
1599                    data_key::CONTENT_TYPE => {
1600                        header.content_type = Some(m.text(key, limits::MAX_CONTENT_TYPE_BYTES)?)
1601                    }
1602                    data_key::TRACEPARENT => {
1603                        header.traceparent = Some(m.text(key, limits::MAX_TRACEPARENT_BYTES)?)
1604                    }
1605                    data_key::TRACESTATE => {
1606                        header.tracestate = Some(m.text(key, limits::MAX_TRACESTATE_BYTES)?)
1607                    }
1608                    data_key::TOPIC => header.topic = Some(m.text(key, limits::MAX_TOPIC_BYTES)?),
1609                    data_key::SEQUENCE => header.sequence = Some(m.u64()?),
1610                    data_key::PRODUCER => header.producer = Some(m.byte_array(key)?),
1611                    // An unknown level is not a level: a peer naming one this
1612                    // version does not define is refused rather than silently
1613                    // read as the weakest, because the value decides what a
1614                    // producer believes about its message.
1615                    data_key::ACHIEVED => {
1616                        let value = m.u64()?;
1617                        header.achieved = Some(Acknowledgement::from_wire(value).ok_or(
1618                            HeaderError::UnknownLevel {
1619                                dimension: "achieved",
1620                                value,
1621                            },
1622                        )?);
1623                    }
1624                    data_key::REPORT_ID => header.report_id = Some(m.u64()?),
1625                    data_key::REPORT => header.report = m.report_levels()?,
1626                    data_key::REPORT_MODE => {
1627                        header.report_mode = level(m.u64()?, "report_mode")?;
1628                    }
1629                    data_key::SEGMENT => header.segment = Some(m.u64()?),
1630                    _ => m.skip()?,
1631                }
1632            }
1633        }
1634        finish(&d)?;
1635        // Keys 9 and 10 are one statement in two halves: an order with no
1636        // stream to report on, or a stream with nothing to report, names a
1637        // report nobody can serve.
1638        if !header.report.is_empty() && header.report_id.is_none() {
1639            return Err(HeaderError::InvalidReport("report without report_id"));
1640        }
1641        if header.report_id.is_some() && header.report.is_empty() {
1642            return Err(HeaderError::InvalidReport("report_id without report"));
1643        }
1644        Ok(header)
1645    }
1646}
1647
1648/// ERROR header.
1649///
1650/// Legal only on the reply half of a bidirectional stream: an ERROR is the
1651/// alternative to a reply, so it needs no reference to what it answers.
1652#[derive(Clone, Debug, PartialEq, Eq)]
1653pub struct ErrorHeader {
1654    /// Raw error code.
1655    pub code: u64,
1656    /// Human-readable detail; never machine-interpreted.
1657    pub message: Option<String>,
1658}
1659
1660impl ErrorHeader {
1661    /// Builds a header for a known error code.
1662    pub fn new(code: weida_core::ErrorCode) -> ErrorHeader {
1663        ErrorHeader {
1664            code: code.to_wire(),
1665            message: None,
1666        }
1667    }
1668
1669    /// The error code, or `None` for an unknown one.
1670    pub fn error_code(&self) -> Option<weida_core::ErrorCode> {
1671        weida_core::ErrorCode::from_wire(self.code)
1672    }
1673
1674    /// Encodes the header.
1675    pub fn encode(&self) -> Vec<u8> {
1676        let mut out = Vec::new();
1677        self.encode_into(&mut out);
1678        out
1679    }
1680
1681    /// Appends the encoded header to `out`, for a send path that reuses a
1682    /// buffer (B-250). The canonical form has one implementation and this is
1683    /// it; [`Self::encode`] is a wrapper.
1684    pub fn encode_into(&self, out: &mut Vec<u8>) {
1685        let count = 1 + u64::from(self.message.is_some());
1686        encode_into_with(out, |e| {
1687            e.map(count)?;
1688            e.u64(error_key::CODE)?.u64(self.code)?;
1689            if let Some(msg) = &self.message {
1690                e.u64(error_key::MESSAGE)?.str(msg)?;
1691            }
1692            Ok(())
1693        })
1694    }
1695
1696    /// Decodes the header.
1697    pub fn decode(bytes: &[u8]) -> Result<ErrorHeader, HeaderError> {
1698        let mut d = Decoder::new(bytes);
1699        let mut code = 0;
1700        let mut message = None;
1701        {
1702            let mut m = MapReader::new(&mut d)?;
1703            while let Some(key) = m.next_key()? {
1704                match key {
1705                    error_key::CODE => code = m.u64()?,
1706                    error_key::MESSAGE => message = Some(m.text(key, limits::MAX_MESSAGE_BYTES)?),
1707                    _ => m.skip()?,
1708                }
1709            }
1710            m.require(error_key::CODE)?;
1711        }
1712        finish(&d)?;
1713        Ok(ErrorHeader { code, message })
1714    }
1715}
1716
1717/// SUBSCRIBE and UNSUBSCRIBE header.
1718///
1719/// Both frames carry the same two keys: the publisher path to (un)subscribe on
1720/// and the topic filter. The filter is a segmented pattern, not a byte prefix
1721/// ([`filter`]): the empty filter matches every topic, `*` matches one whole
1722/// segment and a trailing `#` matches zero or more.
1723#[derive(Clone, Debug, PartialEq, Eq)]
1724pub struct SubscriptionHeader {
1725    /// Publisher endpoint path.
1726    pub endpoint: String,
1727    /// Topic filter; the empty string matches everything. A decoded header's
1728    /// filter has passed [`filter::validate`].
1729    pub filter: String,
1730    /// The dish's latency budget in milliseconds (key `2`), meaningful on a
1731    /// RADIO path only
1732    /// ([decisions/0034](../../../docs/decisions/0034-late-is-lost.md)
1733    /// §4.6). Encoded only when present.
1734    pub max_age_ms: Option<u64>,
1735}
1736
1737impl SubscriptionHeader {
1738    /// A header for `endpoint` and `filter`.
1739    pub fn new(endpoint: impl Into<String>, filter: impl Into<String>) -> SubscriptionHeader {
1740        SubscriptionHeader {
1741            endpoint: endpoint.into(),
1742            filter: filter.into(),
1743            max_age_ms: None,
1744        }
1745    }
1746
1747    /// Encodes the header.
1748    pub fn encode(&self) -> Vec<u8> {
1749        let mut out = Vec::new();
1750        self.encode_into(&mut out);
1751        out
1752    }
1753
1754    /// Appends the encoded header to `out`, for a send path that reuses a
1755    /// buffer (B-250). The canonical form has one implementation and this is
1756    /// it; [`Self::encode`] is a wrapper.
1757    pub fn encode_into(&self, out: &mut Vec<u8>) {
1758        // Keys 0 and 1 are required, so neither is elided: an absent filter
1759        // and an empty filter would otherwise be indistinguishable on the
1760        // wire, and the empty filter is the "everything" subscription. Key 2
1761        // is optional and written only when set.
1762        encode_into_with(out, |e| {
1763            e.map(2 + u64::from(self.max_age_ms.is_some()))?;
1764            e.u64(subscription_key::ENDPOINT)?.str(&self.endpoint)?;
1765            e.u64(subscription_key::FILTER)?.str(&self.filter)?;
1766            if let Some(max_age_ms) = self.max_age_ms {
1767                e.u64(subscription_key::MAX_AGE_MS)?.u64(max_age_ms)?;
1768            }
1769            Ok(())
1770        })
1771    }
1772
1773    /// Decodes the header.
1774    pub fn decode(bytes: &[u8]) -> Result<SubscriptionHeader, HeaderError> {
1775        let mut d = Decoder::new(bytes);
1776        let mut endpoint = None;
1777        let mut filter = None;
1778        let mut max_age_ms = None;
1779        {
1780            let mut m = MapReader::new(&mut d)?;
1781            while let Some(key) = m.next_key()? {
1782                match key {
1783                    subscription_key::ENDPOINT => {
1784                        endpoint = Some(m.text(key, limits::MAX_ENDPOINT_BYTES)?)
1785                    }
1786                    subscription_key::FILTER => {
1787                        filter = Some(m.text(key, limits::MAX_FILTER_BYTES)?)
1788                    }
1789                    subscription_key::MAX_AGE_MS => max_age_ms = Some(m.u64()?),
1790                    _ => m.skip()?,
1791                }
1792            }
1793            m.require(subscription_key::ENDPOINT)?;
1794            m.require(subscription_key::FILTER)?;
1795        }
1796        // The grammar is checked here, at the codec boundary, so no matcher
1797        // ever sees a filter it would have to interpret twice; an illegal one
1798        // closes the connection with `PROTOCOL_VIOLATION`
1799        // (`docs/PROTOCOL.md` §6.4).
1800        let filter = filter.expect("presence checked above");
1801        filter::validate(&filter)?;
1802        finish(&d)?;
1803        Ok(SubscriptionHeader {
1804            endpoint: endpoint.expect("presence checked above"),
1805            filter,
1806            max_age_ms,
1807        })
1808    }
1809}
1810
1811/// FLOW header (kind `7`).
1812///
1813/// The registration of a datagram flow: the stream it opens is the flow's
1814/// lifetime, and the flow id prefixes every datagram the flow carries
1815/// ([decisions/0034](../../../docs/decisions/0034-late-is-lost.md) §4.2).
1816/// The optional keys reuse DATA's meaning and caps.
1817#[derive(Clone, Debug, PartialEq, Eq)]
1818pub struct FlowHeader {
1819    /// Endpoint path the flow is addressed to (key `0`, required).
1820    pub endpoint: String,
1821    /// Flow id, chosen by the sender and unique per connection and
1822    /// direction (key `1`, required).
1823    pub flow: u64,
1824    /// Opaque media type label (key `2`).
1825    pub content_type: Option<String>,
1826    /// W3C Trace Context `traceparent` (key `3`).
1827    pub traceparent: Option<String>,
1828    /// W3C Trace Context `tracestate` (key `4`).
1829    pub tracestate: Option<String>,
1830    /// Topic the flow carries (key `5`).
1831    pub topic: Option<String>,
1832}
1833
1834impl FlowHeader {
1835    /// A header for flow `flow` addressed to `endpoint`, with no optional key.
1836    pub fn new(endpoint: impl Into<String>, flow: u64) -> FlowHeader {
1837        FlowHeader {
1838            endpoint: endpoint.into(),
1839            flow,
1840            content_type: None,
1841            traceparent: None,
1842            tracestate: None,
1843            topic: None,
1844        }
1845    }
1846
1847    /// Encodes the header.
1848    pub fn encode(&self) -> Vec<u8> {
1849        let mut out = Vec::new();
1850        self.encode_into(&mut out);
1851        out
1852    }
1853
1854    /// Appends the encoded header to `out`. The canonical form has one
1855    /// implementation and this is it; [`Self::encode`] is a wrapper.
1856    pub fn encode_into(&self, out: &mut Vec<u8>) {
1857        let count = 2
1858            + u64::from(self.content_type.is_some())
1859            + u64::from(self.traceparent.is_some())
1860            + u64::from(self.tracestate.is_some())
1861            + u64::from(self.topic.is_some());
1862        encode_into_with(out, |e| {
1863            e.map(count)?;
1864            e.u64(flow_key::ENDPOINT)?.str(&self.endpoint)?;
1865            e.u64(flow_key::FLOW)?.u64(self.flow)?;
1866            if let Some(ct) = &self.content_type {
1867                e.u64(flow_key::CONTENT_TYPE)?.str(ct)?;
1868            }
1869            if let Some(tp) = &self.traceparent {
1870                e.u64(flow_key::TRACEPARENT)?.str(tp)?;
1871            }
1872            if let Some(ts) = &self.tracestate {
1873                e.u64(flow_key::TRACESTATE)?.str(ts)?;
1874            }
1875            if let Some(topic) = &self.topic {
1876                e.u64(flow_key::TOPIC)?.str(topic)?;
1877            }
1878            Ok(())
1879        })
1880    }
1881
1882    /// Decodes the header.
1883    pub fn decode(bytes: &[u8]) -> Result<FlowHeader, HeaderError> {
1884        let mut d = Decoder::new(bytes);
1885        let mut endpoint = None;
1886        let mut flow = None;
1887        let mut content_type = None;
1888        let mut traceparent = None;
1889        let mut tracestate = None;
1890        let mut topic = None;
1891        {
1892            let mut m = MapReader::new(&mut d)?;
1893            while let Some(key) = m.next_key()? {
1894                match key {
1895                    flow_key::ENDPOINT => endpoint = Some(m.text(key, limits::MAX_ENDPOINT_BYTES)?),
1896                    flow_key::FLOW => flow = Some(m.u64()?),
1897                    flow_key::CONTENT_TYPE => {
1898                        content_type = Some(m.text(key, limits::MAX_CONTENT_TYPE_BYTES)?)
1899                    }
1900                    flow_key::TRACEPARENT => {
1901                        traceparent = Some(m.text(key, limits::MAX_TRACEPARENT_BYTES)?)
1902                    }
1903                    flow_key::TRACESTATE => {
1904                        tracestate = Some(m.text(key, limits::MAX_TRACESTATE_BYTES)?)
1905                    }
1906                    flow_key::TOPIC => topic = Some(m.text(key, limits::MAX_TOPIC_BYTES)?),
1907                    _ => m.skip()?,
1908                }
1909            }
1910            m.require(flow_key::ENDPOINT)?;
1911            m.require(flow_key::FLOW)?;
1912        }
1913        finish(&d)?;
1914        Ok(FlowHeader {
1915            endpoint: endpoint.expect("presence checked above"),
1916            flow: flow.expect("presence checked above"),
1917            content_type,
1918            traceparent,
1919            tracestate,
1920            topic,
1921        })
1922    }
1923}
1924
1925/// CREDIT header (kind `5`).
1926///
1927/// The L2 credit of
1928/// [decisions/0003](../../../docs/decisions/0003-credit-unit.md) §4.2-§4.3:
1929/// which subscription, and how many messages that subscription will accept in
1930/// total. Three keys, all required — a subscription is `(endpoint, filter)`
1931/// on the connection the frame arrives on, and an absent limit would be
1932/// indistinguishable from a limit of zero, which is the pause.
1933///
1934/// **The limit is absolute and cumulative, not a delta.** It counts messages
1935/// delivered on that subscription since it was created, so a lost frame costs
1936/// nothing and a duplicated one changes nothing. It is also **monotone at the
1937/// receiver**: a broker keeps the highest limit it has seen, which is what
1938/// makes a reordered frame harmless on a transport that does not order the
1939/// streams control frames ride. Monotone is the whole rule: a receiver
1940/// ignores any limit that is not strictly greater than the standing one, so
1941/// restating a number already delivered changes nothing unless the
1942/// subscription had exhausted its credit anyway. **v0 offers no way to lower
1943/// a standing limit.** The only pause is the `0` a fresh subscription starts
1944/// at, so a consumer that wants to stay in control grants in increments it
1945/// is willing to receive. AMQP 1.0 can shrink `link-credit` against an
1946/// absolute baseline [amqp10 §5.1]; this frame cannot, and a peer that reads
1947/// it as if it could would wait for a stop no broker can deliver.
1948#[derive(Clone, Debug, PartialEq, Eq)]
1949pub struct CreditHeader {
1950    /// Endpoint path of the queue the subscription is on.
1951    pub endpoint: String,
1952    /// Topic filter of the subscription. A decoded header's filter has passed
1953    /// [`filter::validate`].
1954    pub filter: String,
1955    /// Messages this subscription will accept in total, counted from its
1956    /// creation.
1957    pub limit: u64,
1958}
1959
1960impl CreditHeader {
1961    /// A header granting `limit` to the subscription `(endpoint, filter)`.
1962    pub fn new(endpoint: impl Into<String>, filter: impl Into<String>, limit: u64) -> CreditHeader {
1963        CreditHeader {
1964            endpoint: endpoint.into(),
1965            filter: filter.into(),
1966            limit,
1967        }
1968    }
1969
1970    /// Encodes the header.
1971    pub fn encode(&self) -> Vec<u8> {
1972        let mut out = Vec::new();
1973        self.encode_into(&mut out);
1974        out
1975    }
1976
1977    /// Appends the encoded header to `out`, for a send path that reuses a
1978    /// buffer (B-250). The canonical form has one implementation and this is
1979    /// it; [`Self::encode`] is a wrapper.
1980    pub fn encode_into(&self, out: &mut Vec<u8>) {
1981        encode_into_with(out, |e| {
1982            e.map(3)?;
1983            e.u64(credit_key::ENDPOINT)?.str(&self.endpoint)?;
1984            e.u64(credit_key::FILTER)?.str(&self.filter)?;
1985            e.u64(credit_key::LIMIT)?.u64(self.limit)?;
1986            Ok(())
1987        })
1988    }
1989
1990    /// Decodes the header.
1991    pub fn decode(bytes: &[u8]) -> Result<CreditHeader, HeaderError> {
1992        let mut d = Decoder::new(bytes);
1993        let mut endpoint = None;
1994        let mut filter = None;
1995        let mut limit = None;
1996        {
1997            let mut m = MapReader::new(&mut d)?;
1998            while let Some(key) = m.next_key()? {
1999                match key {
2000                    credit_key::ENDPOINT => {
2001                        endpoint = Some(m.text(key, limits::MAX_ENDPOINT_BYTES)?)
2002                    }
2003                    credit_key::FILTER => filter = Some(m.text(key, limits::MAX_FILTER_BYTES)?),
2004                    credit_key::LIMIT => limit = Some(m.u64()?),
2005                    _ => m.skip()?,
2006                }
2007            }
2008            m.require(credit_key::ENDPOINT)?;
2009            m.require(credit_key::FILTER)?;
2010            m.require(credit_key::LIMIT)?;
2011        }
2012        // Same boundary as SUBSCRIBE: a filter that does not name a
2013        // subscription cannot grant credit to one.
2014        let filter = filter.expect("presence checked above");
2015        filter::validate(&filter)?;
2016        finish(&d)?;
2017        Ok(CreditHeader {
2018            endpoint: endpoint.expect("presence checked above"),
2019            filter,
2020            limit: limit.expect("presence checked above"),
2021        })
2022    }
2023}
2024
2025/// CURSOR head frame (kind `6`).
2026///
2027/// One key, required: which report this stream carries. The stream then
2028/// carries `(level, offset)` records until FIN and **never any payload**
2029/// ([decisions/0024](../../../docs/decisions/0024-three-families-one-back-channel.md)
2030/// §4.4) — which is why the head frame needs no length, no endpoint and no
2031/// correlation beyond the id the DATA header allocated.
2032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2033pub struct CursorHeader {
2034    /// The `report_id` of the DATA header that ordered this report.
2035    pub report_id: u64,
2036}
2037
2038impl CursorHeader {
2039    /// Encodes the header.
2040    pub fn encode(&self) -> Vec<u8> {
2041        let mut out = Vec::new();
2042        self.encode_into(&mut out);
2043        out
2044    }
2045
2046    /// Appends the encoded header to `out`, for a send path that reuses a
2047    /// buffer (B-250). The canonical form has one implementation and this is
2048    /// it; [`Self::encode`] is a wrapper.
2049    pub fn encode_into(&self, out: &mut Vec<u8>) {
2050        encode_into_with(out, |e| {
2051            e.map(1)?;
2052            e.u64(cursor_key::REPORT_ID)?.u64(self.report_id)?;
2053            Ok(())
2054        })
2055    }
2056
2057    /// Decodes the header.
2058    pub fn decode(bytes: &[u8]) -> Result<CursorHeader, HeaderError> {
2059        let mut d = Decoder::new(bytes);
2060        let mut report_id = None;
2061        {
2062            let mut m = MapReader::new(&mut d)?;
2063            while let Some(key) = m.next_key()? {
2064                match key {
2065                    cursor_key::REPORT_ID => report_id = Some(m.u64()?),
2066                    _ => m.skip()?,
2067                }
2068            }
2069            m.require(cursor_key::REPORT_ID)?;
2070        }
2071        finish(&d)?;
2072        Ok(CursorHeader {
2073            report_id: report_id.expect("presence checked above"),
2074        })
2075    }
2076}
2077
2078/// Longest possible cursor record: two 8-byte QUIC varints.
2079pub const MAX_CURSOR_RECORD_LEN: usize = 2 * crate::varint::MAX_ENCODED_LEN;
2080
2081/// Appends one `(level, offset)` record to `out`.
2082///
2083/// Records are QUIC varint pairs rather than CBOR: a record is hot-path and
2084/// self-delimiting, and a CBOR map per record would cost a map header per
2085/// reported byte range for no gain — the head frame already carries every
2086/// field a record could need to name.
2087pub fn encode_cursor_record(
2088    level: CursorLevel,
2089    offset: u64,
2090    out: &mut Vec<u8>,
2091) -> Result<(), VarintError> {
2092    encode_varint(level.to_wire(), out)?;
2093    encode_varint(offset, out)
2094}
2095
2096/// Decodes one record from the front of `input`.
2097///
2098/// `Ok(None)` means the input ends inside a record: read more bytes and
2099/// retry. That is **not** a violation — a reader sees whatever slice the
2100/// transport handed it, and a record is at most
2101/// [`MAX_CURSOR_RECORD_LEN`] bytes, so the retry is bounded. An undefined
2102/// level in the reserved range *is* a violation: the value decides what the
2103/// receiver believes about its own transfer.
2104pub fn decode_cursor_record(
2105    input: &[u8],
2106) -> Result<Option<(CursorLevel, u64, usize)>, HeaderError> {
2107    // `decode_varint` accepts the whole representable range, so its only
2108    // failure is a value the input ended inside of.
2109    let Ok((raw_level, level_len)) = decode_varint(input) else {
2110        return Ok(None);
2111    };
2112    let Ok((offset, offset_len)) = decode_varint(&input[level_len..]) else {
2113        return Ok(None);
2114    };
2115    let level = CursorLevel::from_wire(raw_level).ok_or(HeaderError::UnknownLevel {
2116        dimension: "cursor",
2117        value: raw_level,
2118    })?;
2119    Ok(Some((level, offset, level_len + offset_len)))
2120}
2121
2122#[cfg(test)]
2123mod tests {
2124    /// The tests build headers by hand; production code goes through each
2125    /// type's `encode_into`.
2126    fn encode_with(
2127        f: impl FnOnce(
2128            &mut minicbor::Encoder<Vec<u8>>,
2129        ) -> Result<(), minicbor::encode::Error<std::convert::Infallible>>,
2130    ) -> Vec<u8> {
2131        let mut out = Vec::new();
2132        super::encode_into_with(&mut out, f);
2133        out
2134    }
2135
2136    use super::*;
2137    use weida_core::ErrorCode;
2138
2139    // --- golden vectors, docs/PROTOCOL.md §8 ------------------------------
2140
2141    #[test]
2142    fn golden_data_request_header() {
2143        let h = DataHeader::addressed("/t");
2144        let bytes = h.encode();
2145        assert_eq!(bytes, vec![0xA1, 0x00, 0x62, 0x2F, 0x74]);
2146        assert_eq!(bytes.len(), 0x05);
2147        assert_eq!(DataHeader::decode(&bytes).unwrap(), h);
2148    }
2149
2150    #[test]
2151    fn golden_data_reply_header() {
2152        // The stream is the correlation, so a reply header is an empty map.
2153        let h = DataHeader::reply();
2154        let bytes = h.encode();
2155        assert_eq!(bytes, vec![0xA0]);
2156        assert_eq!(bytes.len(), 0x01);
2157        assert_eq!(DataHeader::decode(&bytes).unwrap(), h);
2158    }
2159
2160    #[test]
2161    fn golden_hello_header() {
2162        let h = Hello::v0(16384, 1024);
2163        let bytes = h.encode();
2164        assert_eq!(
2165            bytes,
2166            vec![
2167                0xA5, 0x00, 0x81, 0x00, 0x01, 0x19, 0x40, 0x00, 0x02, 0x19, 0x04, 0x00, 0x03, 0x80,
2168                0x04, 0x80
2169            ]
2170        );
2171        assert_eq!(bytes.len(), 0x10);
2172        assert_eq!(Hello::decode(&bytes).unwrap(), h);
2173    }
2174
2175    #[test]
2176    fn golden_error_header() {
2177        let h = ErrorHeader::new(ErrorCode::NoReply);
2178        let bytes = h.encode();
2179        assert_eq!(bytes, vec![0xA1, 0x00, 0x05]);
2180        assert_eq!(bytes.len(), 0x03);
2181        assert_eq!(ErrorHeader::decode(&bytes).unwrap(), h);
2182    }
2183
2184    #[test]
2185    fn golden_pub_copy_data_header() {
2186        let mut h = DataHeader::addressed("/md");
2187        h.topic = Some("px.eur".into());
2188        let bytes = h.encode();
2189        assert_eq!(
2190            bytes,
2191            vec![
2192                0xA2, 0x00, 0x63, 0x2F, 0x6D, 0x64, 0x05, 0x66, 0x70, 0x78, 0x2E, 0x65, 0x75, 0x72
2193            ]
2194        );
2195        assert_eq!(bytes.len(), 0x0E);
2196        assert_eq!(DataHeader::decode(&bytes).unwrap(), h);
2197    }
2198
2199    /// The digest of the §8 vectors: SHA-256 of `"test"`, the value the
2200    /// address examples in `docs/PROTOCOL.md` already use.
2201    const VECTOR_PRODUCER: [u8; limits::PRODUCER_BYTES] = [
2202        0x9F, 0x86, 0xD0, 0x81, 0x88, 0x4C, 0x7D, 0x65, 0x9A, 0x2F, 0xEA, 0xA0, 0xC5, 0x5A, 0xD0,
2203        0x15, 0xA3, 0xBF, 0x4F, 0x1B, 0x2B, 0x0B, 0x82, 0x2C, 0xD1, 0x5D, 0x6C, 0x15, 0xB0, 0xF0,
2204        0x0A, 0x08,
2205    ];
2206
2207    #[test]
2208    fn golden_sequenced_data_header() {
2209        let mut h = DataHeader::addressed("/t");
2210        h.sequence = Some(1);
2211        let bytes = h.encode();
2212        assert_eq!(bytes, vec![0xA2, 0x00, 0x62, 0x2F, 0x74, 0x06, 0x01]);
2213        assert_eq!(bytes.len(), 0x07);
2214        assert_eq!(DataHeader::decode(&bytes).unwrap(), h);
2215    }
2216
2217    #[test]
2218    fn golden_relayed_data_header() {
2219        let mut h = DataHeader::addressed("/t");
2220        h.sequence = Some(1);
2221        h.producer = Some(VECTOR_PRODUCER);
2222        let bytes = h.encode();
2223        let mut expected = vec![0xA3, 0x00, 0x62, 0x2F, 0x74, 0x06, 0x01, 0x07, 0x58, 0x20];
2224        expected.extend_from_slice(&VECTOR_PRODUCER);
2225        assert_eq!(bytes, expected);
2226        assert_eq!(bytes.len(), 0x2A);
2227        assert_eq!(DataHeader::decode(&bytes).unwrap(), h);
2228    }
2229
2230    #[test]
2231    fn a_producer_longer_than_the_cap_is_rejected() {
2232        let bytes = encode_with(|e| {
2233            e.map(1)?;
2234            e.u64(data_key::PRODUCER)?
2235                .bytes(&[0u8; limits::PRODUCER_BYTES + 1])?;
2236            Ok(())
2237        });
2238        assert_eq!(
2239            DataHeader::decode(&bytes),
2240            Err(HeaderError::StringTooLong {
2241                key: data_key::PRODUCER,
2242                len: limits::PRODUCER_BYTES + 1,
2243                max: limits::PRODUCER_BYTES,
2244            })
2245        );
2246    }
2247
2248    #[test]
2249    fn a_producer_shorter_than_a_digest_is_rejected() {
2250        // Half a digest identifies nobody, so it is a framing violation
2251        // rather than a value to carry (`docs/PROTOCOL.md` §6.2).
2252        let bytes = encode_with(|e| {
2253            e.map(1)?;
2254            e.u64(data_key::PRODUCER)?.bytes(&[0u8; 16])?;
2255            Ok(())
2256        });
2257        assert!(matches!(
2258            DataHeader::decode(&bytes),
2259            Err(HeaderError::Malformed(_))
2260        ));
2261    }
2262
2263    #[test]
2264    fn the_new_keys_reject_the_wrong_cbor_type() {
2265        let sequence_as_text = encode_with(|e| {
2266            e.map(1)?;
2267            e.u64(data_key::SEQUENCE)?.str("7")?;
2268            Ok(())
2269        });
2270        assert!(DataHeader::decode(&sequence_as_text).is_err());
2271
2272        let producer_as_text = encode_with(|e| {
2273            e.map(1)?;
2274            e.u64(data_key::PRODUCER)?.str("sha256:…")?;
2275            Ok(())
2276        });
2277        assert!(DataHeader::decode(&producer_as_text).is_err());
2278    }
2279
2280    #[test]
2281    fn a_v0_header_carries_neither_new_key() {
2282        // What the runtime writes on a `core` connection: neither key 6 —
2283        // which needs negotiated `PerProducer` ordering — nor key 7, which
2284        // nothing in this repository sets.
2285        let mut h = DataHeader::addressed("/t");
2286        h.traceparent = Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".into());
2287        let bytes = h.encode();
2288        let mut d = Decoder::new(&bytes);
2289        let pairs = d.map().unwrap().unwrap();
2290        let keys: Vec<u64> = (0..pairs)
2291            .map(|_| {
2292                let key = d.u64().unwrap();
2293                d.skip().unwrap();
2294                key
2295            })
2296            .collect();
2297        assert_eq!(keys, vec![data_key::ENDPOINT, data_key::TRACEPARENT]);
2298    }
2299
2300    #[test]
2301    fn golden_subscription_headers() {
2302        let h = SubscriptionHeader::new("/md", "px.");
2303        let bytes = h.encode();
2304        assert_eq!(
2305            bytes,
2306            vec![
2307                0xA2, 0x00, 0x63, 0x2F, 0x6D, 0x64, 0x01, 0x63, 0x70, 0x78, 0x2E
2308            ]
2309        );
2310        assert_eq!(bytes.len(), 0x0B);
2311        // One header layout serves both kinds; only the kind byte differs, and
2312        // that byte belongs to the preamble (see `tests/golden_vectors.rs`).
2313        assert_eq!(SubscriptionHeader::decode(&bytes).unwrap(), h);
2314    }
2315
2316    // --- roundtrips -------------------------------------------------------
2317
2318    #[test]
2319    fn data_header_roundtrip_with_every_field() {
2320        let h = DataHeader {
2321            endpoint: Some("/transform".into()),
2322            content_len: Some(1 << 40),
2323            content_type: Some("application/octet-stream".into()),
2324            traceparent: Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".into()),
2325            tracestate: Some("vendor=value".into()),
2326            topic: Some("px.eur".into()),
2327            sequence: Some(u64::MAX),
2328            producer: Some([0x5A; limits::PRODUCER_BYTES]),
2329            achieved: Some(Acknowledgement::Processed),
2330            report_id: Some(7),
2331            report: vec![
2332                CursorLevel::Known(Acknowledgement::Accepted),
2333                CursorLevel::Known(Acknowledgement::Processed),
2334                CursorLevel::Application(CursorLevel::APPLICATION_FLOOR),
2335            ],
2336            report_mode: ReportMode::FinalOnly,
2337            segment: Some(u64::MAX),
2338        };
2339        assert_eq!(DataHeader::decode(&h.encode()).unwrap(), h);
2340    }
2341
2342    #[test]
2343    fn error_header_roundtrip_with_and_without_message() {
2344        let bare = ErrorHeader::new(ErrorCode::UnknownEndpoint);
2345        assert_eq!(ErrorHeader::decode(&bare.encode()).unwrap(), bare);
2346        assert_eq!(bare.error_code(), Some(ErrorCode::UnknownEndpoint));
2347
2348        let with_msg = ErrorHeader {
2349            code: 4,
2350            message: Some("handler panicked".into()),
2351        };
2352        assert_eq!(ErrorHeader::decode(&with_msg.encode()).unwrap(), with_msg);
2353    }
2354
2355    #[test]
2356    fn keys_are_emitted_in_ascending_order() {
2357        let h = DataHeader {
2358            endpoint: Some("/x".into()),
2359            content_len: Some(1),
2360            content_type: Some("t".into()),
2361            traceparent: Some("p".into()),
2362            tracestate: Some("s".into()),
2363            topic: Some("k".into()),
2364            sequence: Some(9),
2365            producer: Some([0u8; limits::PRODUCER_BYTES]),
2366            achieved: Some(Acknowledgement::Accepted),
2367            report_id: Some(1),
2368            report: vec![CursorLevel::Known(Acknowledgement::Stored)],
2369            report_mode: ReportMode::FinalOnly,
2370            segment: Some(3),
2371        };
2372        let bytes = h.encode();
2373        let mut d = Decoder::new(&bytes);
2374        let n = d.map().unwrap().unwrap();
2375        let mut last = None;
2376        for _ in 0..n {
2377            let key = d.u64().unwrap();
2378            if let Some(prev) = last {
2379                assert!(key > prev, "keys must ascend: {prev} then {key}");
2380            }
2381            last = Some(key);
2382            d.skip().unwrap();
2383        }
2384    }
2385
2386    #[test]
2387    fn an_unsorted_report_with_repeats_is_emitted_as_the_canonical_ascending_set() {
2388        // A caller hands over the levels in the order it thought of them.
2389        // Decoding is the proof: the decoder refuses a non-ascending or
2390        // repeated array, so a header that survives its own encoder was
2391        // canonicalized on the way out.
2392        let h = DataHeader {
2393            report_id: Some(1),
2394            report: vec![
2395                CursorLevel::Application(CursorLevel::APPLICATION_FLOOR),
2396                CursorLevel::Known(Acknowledgement::Stored),
2397                CursorLevel::Application(CursorLevel::APPLICATION_FLOOR),
2398                CursorLevel::Known(Acknowledgement::Accepted),
2399                CursorLevel::Known(Acknowledgement::Stored),
2400            ],
2401            ..DataHeader::reply()
2402        };
2403        let decoded = DataHeader::decode(&h.encode()).expect("encoder emits the canonical form");
2404        assert_eq!(
2405            decoded.report,
2406            vec![
2407                CursorLevel::Known(Acknowledgement::Accepted),
2408                CursorLevel::Known(Acknowledgement::Stored),
2409                CursorLevel::Application(CursorLevel::APPLICATION_FLOOR),
2410            ]
2411        );
2412    }
2413
2414    // --- optional fields --------------------------------------------------
2415
2416    #[test]
2417    fn every_data_field_is_optional_at_the_decoder() {
2418        // The endpoint requirement lives in dispatch, not here: a reply half
2419        // legitimately carries none, and the decoder cannot tell the halves
2420        // apart.
2421        assert_eq!(DataHeader::decode(&[0xA0]).unwrap(), DataHeader::default());
2422
2423        let only_topic = encode_with(|e| {
2424            e.map(1)?;
2425            e.u64(data_key::TOPIC)?.str("px.eur")?;
2426            Ok(())
2427        });
2428        let h = DataHeader::decode(&only_topic).unwrap();
2429        assert_eq!(h.topic.as_deref(), Some("px.eur"));
2430        assert_eq!(h.endpoint, None);
2431    }
2432
2433    #[test]
2434    fn absent_fields_are_omitted_by_the_encoder() {
2435        let h = DataHeader::addressed("/t");
2436        assert_eq!(h.encode(), vec![0xA1, 0x00, 0x62, 0x2F, 0x74]);
2437    }
2438
2439    // --- forward compatibility -------------------------------------------
2440
2441    #[test]
2442    fn unknown_keys_are_skipped() {
2443        // Re-encode the golden DATA header with an extra key 63 holding a
2444        // nested structure, and check it still decodes to the same value.
2445        let h = DataHeader::addressed("/t");
2446        let extended = encode_with(|e| {
2447            e.map(2)?;
2448            e.u64(0)?.str("/t")?;
2449            e.u64(63)?.array(2)?.u64(7)?.map(1)?.u64(1)?.bool(true)?;
2450            Ok(())
2451        });
2452        assert_eq!(DataHeader::decode(&extended).unwrap(), h);
2453    }
2454
2455    #[test]
2456    fn unknown_keys_above_the_reserved_range_are_skipped() {
2457        let extended = encode_with(|e| {
2458            e.map(2)?;
2459            e.u64(1)?.u64(5)?;
2460            e.u64(1000)?.str("future")?;
2461            Ok(())
2462        });
2463        let h = DataHeader::decode(&extended).unwrap();
2464        assert_eq!(h.content_len, Some(5));
2465    }
2466
2467    #[test]
2468    fn skipping_tolerates_nesting_up_to_the_depth_limit() {
2469        for depth in [1usize, limits::MAX_SKIP_DEPTH] {
2470            let bytes = encode_with(|e| {
2471                e.map(2)?;
2472                e.u64(data_key::CONTENT_LEN)?.u64(1)?;
2473                e.u64(50)?;
2474                for _ in 0..depth {
2475                    e.array(1)?;
2476                }
2477                e.u64(1)?;
2478                Ok(())
2479            });
2480            let h = DataHeader::decode(&bytes).unwrap_or_else(|e| panic!("depth {depth}: {e}"));
2481            assert_eq!(h.content_len, Some(1), "depth {depth}");
2482        }
2483    }
2484
2485    #[test]
2486    fn skipping_rejects_nesting_beyond_the_depth_limit() {
2487        let bytes = encode_with(|e| {
2488            e.map(1)?;
2489            e.u64(50)?;
2490            for _ in 0..(limits::MAX_SKIP_DEPTH + 1) {
2491                e.array(1)?;
2492            }
2493            e.u64(1)?;
2494            Ok(())
2495        });
2496        assert_eq!(
2497            DataHeader::decode(&bytes).unwrap_err(),
2498            HeaderError::DepthExceeded
2499        );
2500    }
2501
2502    #[test]
2503    fn skipping_a_wide_shallow_structure_is_fine() {
2504        let bytes = encode_with(|e| {
2505            e.map(2)?;
2506            e.u64(data_key::CONTENT_LEN)?.u64(1)?;
2507            e.u64(40)?.array(64)?;
2508            for i in 0..64u64 {
2509                e.u64(i)?;
2510            }
2511            Ok(())
2512        });
2513        assert_eq!(DataHeader::decode(&bytes).unwrap().content_len, Some(1));
2514    }
2515
2516    // --- strictness -------------------------------------------------------
2517
2518    #[test]
2519    fn duplicate_keys_are_rejected() {
2520        let bytes = encode_with(|e| {
2521            e.map(2)?;
2522            e.u64(1)?.u64(1)?;
2523            e.u64(1)?.u64(2)?;
2524            Ok(())
2525        });
2526        assert_eq!(
2527            DataHeader::decode(&bytes).unwrap_err(),
2528            HeaderError::DuplicateKey(1)
2529        );
2530    }
2531
2532    #[test]
2533    fn non_uint_keys_are_rejected() {
2534        let bytes = encode_with(|e| {
2535            e.map(1)?;
2536            e.str("endpoint")?.str("/t")?;
2537            Ok(())
2538        });
2539        assert_eq!(
2540            DataHeader::decode(&bytes).unwrap_err(),
2541            HeaderError::NonUintKey
2542        );
2543
2544        let negative = encode_with(|e| {
2545            e.map(1)?;
2546            e.i64(-1)?.u64(1)?;
2547            Ok(())
2548        });
2549        assert_eq!(
2550            DataHeader::decode(&negative).unwrap_err(),
2551            HeaderError::NonUintKey
2552        );
2553    }
2554
2555    #[test]
2556    fn indefinite_maps_are_rejected() {
2557        let bytes = encode_with(|e| {
2558            e.begin_map()?;
2559            e.u64(1)?.u64(1)?;
2560            e.end()?;
2561            Ok(())
2562        });
2563        assert_eq!(
2564            DataHeader::decode(&bytes).unwrap_err(),
2565            HeaderError::Indefinite
2566        );
2567    }
2568
2569    #[test]
2570    fn indefinite_arrays_are_rejected() {
2571        let bytes = encode_with(|e| {
2572            e.map(5)?;
2573            e.u64(0)?.begin_array()?.u64(0)?.end()?;
2574            e.u64(1)?.u64(1)?;
2575            e.u64(2)?.u64(1)?;
2576            e.u64(3)?.array(0)?;
2577            e.u64(4)?.array(0)?;
2578            Ok(())
2579        });
2580        assert_eq!(Hello::decode(&bytes).unwrap_err(), HeaderError::Indefinite);
2581    }
2582
2583    #[test]
2584    fn value_type_mismatches_are_rejected() {
2585        let bytes = encode_with(|e| {
2586            e.map(1)?;
2587            e.u64(data_key::CONTENT_LEN)?.str("not a number")?;
2588            Ok(())
2589        });
2590        assert!(matches!(
2591            DataHeader::decode(&bytes).unwrap_err(),
2592            HeaderError::Malformed(_)
2593        ));
2594    }
2595
2596    #[test]
2597    fn missing_required_keys_are_rejected() {
2598        // ERROR without a code.
2599        let bytes = encode_with(|e| {
2600            e.map(1)?;
2601            e.u64(error_key::MESSAGE)?.str("why")?;
2602            Ok(())
2603        });
2604        assert_eq!(
2605            ErrorHeader::decode(&bytes).unwrap_err(),
2606            HeaderError::MissingKey(error_key::CODE)
2607        );
2608
2609        // HELLO missing capabilities.
2610        let bytes = encode_with(|e| {
2611            e.map(4)?;
2612            e.u64(0)?.array(1)?.u64(0)?;
2613            e.u64(1)?.u64(16384)?;
2614            e.u64(2)?.u64(16)?;
2615            e.u64(4)?.array(0)?;
2616            Ok(())
2617        });
2618        assert_eq!(
2619            Hello::decode(&bytes).unwrap_err(),
2620            HeaderError::MissingKey(hello_key::CAPABILITIES)
2621        );
2622    }
2623
2624    // --- subscription headers ---------------------------------------------
2625
2626    #[test]
2627    fn an_empty_filter_is_legal_and_survives_the_roundtrip() {
2628        let h = SubscriptionHeader::new("/md", "");
2629        let bytes = h.encode();
2630        assert_eq!(SubscriptionHeader::decode(&bytes).unwrap(), h);
2631        // The key is written even though the value is empty: absent and empty
2632        // must stay distinguishable, and empty means "every topic".
2633        assert!(
2634            bytes.contains(&0x60),
2635            "the empty filter is encoded: {bytes:?}"
2636        );
2637    }
2638
2639    #[test]
2640    fn the_filter_grammar_accepts_what_docs_protocol_6_4_permits() {
2641        for ok in [
2642            "",
2643            "#",
2644            "px",
2645            "px.eur",
2646            "px.*",
2647            "*.eur",
2648            "sensors.*.temp",
2649            "px.#",
2650            "px.",
2651            "a..b",
2652        ] {
2653            assert_eq!(filter::validate(ok), Ok(()), "{ok:?} must be legal");
2654        }
2655    }
2656
2657    #[test]
2658    fn the_filter_grammar_rejects_partial_and_misplaced_wildcards() {
2659        for bad in [
2660            "px*", "*px", "p*x.eur", "px.e*ur", "#.px", "px.#.eur", "px#",
2661        ] {
2662            assert!(
2663                matches!(filter::validate(bad), Err(HeaderError::InvalidFilter(_))),
2664                "{bad:?} must be rejected"
2665            );
2666        }
2667    }
2668
2669    #[test]
2670    fn an_illegal_filter_is_rejected_at_the_codec_boundary() {
2671        // Encoding does not validate — a test may build any bytes — but
2672        // decoding does, which is what makes the grammar enforceable against
2673        // a peer (`docs/PROTOCOL.md` §6.4).
2674        let bytes = SubscriptionHeader::new("/md", "px.#.eur").encode();
2675        assert!(matches!(
2676            SubscriptionHeader::decode(&bytes),
2677            Err(HeaderError::InvalidFilter(_))
2678        ));
2679        let e: Error = HeaderError::InvalidFilter("`#` must be the final segment").into();
2680        assert!(e.to_string().contains("invalid topic filter"));
2681    }
2682
2683    #[test]
2684    fn subscription_strings_are_capped() {
2685        for (key, max) in [
2686            (subscription_key::ENDPOINT, limits::MAX_ENDPOINT_BYTES),
2687            (subscription_key::FILTER, limits::MAX_FILTER_BYTES),
2688        ] {
2689            let build = |len: usize| {
2690                let text = "a".repeat(len);
2691                let mut h = SubscriptionHeader::new("/md", "px.");
2692                if key == subscription_key::ENDPOINT {
2693                    h.endpoint = text;
2694                } else {
2695                    h.filter = text;
2696                }
2697                h.encode()
2698            };
2699            assert_eq!(
2700                SubscriptionHeader::decode(&build(max + 1)).unwrap_err(),
2701                HeaderError::StringTooLong {
2702                    key,
2703                    len: max + 1,
2704                    max
2705                },
2706                "key {key}"
2707            );
2708            assert!(
2709                SubscriptionHeader::decode(&build(max)).is_ok(),
2710                "key {key} at cap"
2711            );
2712        }
2713    }
2714
2715    #[test]
2716    fn subscription_headers_require_both_keys() {
2717        let only = |key: u64| {
2718            encode_with(|e| {
2719                e.map(1)?;
2720                e.u64(key)?.str("/md")?;
2721                Ok(())
2722            })
2723        };
2724        assert_eq!(
2725            SubscriptionHeader::decode(&only(subscription_key::ENDPOINT)).unwrap_err(),
2726            HeaderError::MissingKey(subscription_key::FILTER)
2727        );
2728        assert_eq!(
2729            SubscriptionHeader::decode(&only(subscription_key::FILTER)).unwrap_err(),
2730            HeaderError::MissingKey(subscription_key::ENDPOINT)
2731        );
2732    }
2733
2734    #[test]
2735    fn subscription_headers_reject_malformed_input() {
2736        assert!(SubscriptionHeader::decode(&[]).is_err());
2737        // Trailing bytes.
2738        let mut bytes = SubscriptionHeader::new("/md", "px.").encode();
2739        bytes.push(0xff);
2740        assert_eq!(
2741            SubscriptionHeader::decode(&bytes).unwrap_err(),
2742            HeaderError::TrailingBytes
2743        );
2744        // Unknown keys are skipped, like every other header.
2745        let extended = encode_with(|e| {
2746            e.map(3)?;
2747            e.u64(0)?.str("/md")?;
2748            e.u64(1)?.str("px.")?;
2749            e.u64(40)?.array(2)?.u64(1)?.u64(2)?;
2750            Ok(())
2751        });
2752        assert_eq!(
2753            SubscriptionHeader::decode(&extended).unwrap(),
2754            SubscriptionHeader::new("/md", "px.")
2755        );
2756    }
2757
2758    #[test]
2759    fn oversized_strings_are_rejected_per_field() {
2760        // Built through the encoder, which emits keys in ascending order.
2761        let with_text = |key: u64, text: String| -> Vec<u8> {
2762            let mut h = DataHeader::reply();
2763            match key {
2764                data_key::ENDPOINT => h.endpoint = Some(text),
2765                data_key::CONTENT_TYPE => h.content_type = Some(text),
2766                data_key::TRACEPARENT => h.traceparent = Some(text),
2767                data_key::TRACESTATE => h.tracestate = Some(text),
2768                data_key::TOPIC => h.topic = Some(text),
2769                other => panic!("key {other} is not a text field"),
2770            }
2771            h.encode()
2772        };
2773        let cases: [(u64, usize); 5] = [
2774            (data_key::ENDPOINT, limits::MAX_ENDPOINT_BYTES),
2775            (data_key::CONTENT_TYPE, limits::MAX_CONTENT_TYPE_BYTES),
2776            (data_key::TRACEPARENT, limits::MAX_TRACEPARENT_BYTES),
2777            (data_key::TRACESTATE, limits::MAX_TRACESTATE_BYTES),
2778            (data_key::TOPIC, limits::MAX_TOPIC_BYTES),
2779        ];
2780        for (key, max) in cases {
2781            assert_eq!(
2782                DataHeader::decode(&with_text(key, "a".repeat(max + 1))).unwrap_err(),
2783                HeaderError::StringTooLong {
2784                    key,
2785                    len: max + 1,
2786                    max
2787                },
2788                "key {key}"
2789            );
2790            assert!(
2791                DataHeader::decode(&with_text(key, "a".repeat(max))).is_ok(),
2792                "key {key} at cap"
2793            );
2794        }
2795    }
2796
2797    #[test]
2798    fn unordered_keys_are_rejected() {
2799        // Descending keys break the ascending-order rule, which is what makes
2800        // duplicate detection complete for extension keys.
2801        let bytes = encode_with(|e| {
2802            e.map(3)?;
2803            e.u64(2)?.str("t")?;
2804            e.u64(1)?.u64(1)?;
2805            e.u64(3)?.str("p")?;
2806            Ok(())
2807        });
2808        assert_eq!(
2809            DataHeader::decode(&bytes).unwrap_err(),
2810            HeaderError::UnorderedKey(1)
2811        );
2812    }
2813
2814    #[test]
2815    fn duplicate_extension_keys_are_rejected() {
2816        let bytes = encode_with(|e| {
2817            e.map(3)?;
2818            e.u64(1)?.u64(1)?;
2819            e.u64(1000)?.u64(1)?;
2820            e.u64(1000)?.u64(2)?;
2821            Ok(())
2822        });
2823        assert_eq!(
2824            DataHeader::decode(&bytes).unwrap_err(),
2825            HeaderError::DuplicateKey(1000)
2826        );
2827    }
2828
2829    #[test]
2830    fn oversized_error_messages_are_rejected() {
2831        let big = "m".repeat(limits::MAX_MESSAGE_BYTES + 1);
2832        let bytes = encode_with(|e| {
2833            e.map(2)?;
2834            e.u64(error_key::CODE)?.u64(2)?;
2835            e.u64(error_key::MESSAGE)?.str(&big)?;
2836            Ok(())
2837        });
2838        assert_eq!(
2839            ErrorHeader::decode(&bytes).unwrap_err(),
2840            HeaderError::StringTooLong {
2841                key: error_key::MESSAGE,
2842                len: limits::MAX_MESSAGE_BYTES + 1,
2843                max: limits::MAX_MESSAGE_BYTES
2844            }
2845        );
2846    }
2847
2848    #[test]
2849    fn oversized_lists_are_rejected_without_allocating() {
2850        // A one-byte array header claiming 2^32 items must not reserve memory.
2851        let bytes = encode_with(|e| {
2852            e.map(1)?;
2853            e.u64(0)?.array(u64::from(u32::MAX))?;
2854            Ok(())
2855        });
2856        assert_eq!(
2857            Hello::decode(&bytes).unwrap_err(),
2858            HeaderError::ListTooLong {
2859                key: hello_key::VERSIONS,
2860                len: u64::from(u32::MAX),
2861                max: limits::MAX_LIST_ITEMS
2862            }
2863        );
2864    }
2865
2866    #[test]
2867    fn lists_exactly_at_the_cap_are_accepted() {
2868        let bytes = encode_with(|e| {
2869            e.map(5)?;
2870            e.u64(0)?.array(limits::MAX_LIST_ITEMS as u64)?;
2871            for i in 0..limits::MAX_LIST_ITEMS as u64 {
2872                e.u64(i)?;
2873            }
2874            e.u64(1)?.u64(16384)?;
2875            e.u64(2)?.u64(16)?;
2876            e.u64(3)?.array(0)?;
2877            e.u64(4)?.array(0)?;
2878            Ok(())
2879        });
2880        assert_eq!(
2881            Hello::decode(&bytes).unwrap().versions.len(),
2882            limits::MAX_LIST_ITEMS
2883        );
2884    }
2885
2886    #[test]
2887    fn trailing_bytes_are_rejected() {
2888        let mut bytes = ErrorHeader::new(ErrorCode::Rejected).encode();
2889        bytes.push(0xff);
2890        assert_eq!(
2891            ErrorHeader::decode(&bytes).unwrap_err(),
2892            HeaderError::TrailingBytes
2893        );
2894    }
2895
2896    #[test]
2897    fn truncated_headers_are_rejected() {
2898        let full = DataHeader::addressed("/t").encode();
2899        for cut in 0..full.len() {
2900            assert!(
2901                DataHeader::decode(&full[..cut]).is_err(),
2902                "prefix of {cut} bytes must not decode"
2903            );
2904        }
2905    }
2906
2907    #[test]
2908    fn empty_input_is_rejected_for_every_header() {
2909        assert!(Hello::decode(&[]).is_err());
2910        assert!(DataHeader::decode(&[]).is_err());
2911        assert!(ErrorHeader::decode(&[]).is_err());
2912        assert!(SubscriptionHeader::decode(&[]).is_err());
2913    }
2914
2915    #[test]
2916    fn tags_are_rejected() {
2917        // Key 50 is unknown, so the value goes through `skip_value`, which is
2918        // where the tag rule lives.
2919        let bytes = encode_with(|e| {
2920            e.map(1)?;
2921            e.u64(50)?.tag(minicbor::data::IanaTag::Cbor)?.u64(1)?;
2922            Ok(())
2923        });
2924        assert_eq!(
2925            DataHeader::decode(&bytes).unwrap_err(),
2926            HeaderError::Malformed("tags are not allowed")
2927        );
2928    }
2929
2930    // --- reserved value passthrough ---------------------------------------
2931
2932    #[test]
2933    fn unknown_error_codes_survive_decoding() {
2934        let err = ErrorHeader {
2935            code: 99,
2936            message: None,
2937        };
2938        let decoded = ErrorHeader::decode(&err.encode()).unwrap();
2939        assert_eq!(decoded.code, 99);
2940        assert_eq!(decoded.error_code(), None);
2941    }
2942
2943    #[test]
2944    fn header_errors_become_protocol_errors() {
2945        let e: Error = HeaderError::DuplicateKey(3).into();
2946        assert!(matches!(e, Error::Protocol(_)));
2947        assert!(e.to_string().contains("duplicate header key 3"));
2948    }
2949}