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