Skip to main content

matter_codec/
reader.rs

1//! Streaming TLV decoder.
2//!
3//! [`TlvReader::next`] walks the input one element at a time. Scalars are
4//! returned as [`Element::Scalar`]; containers emit a [`Element::ContainerStart`]
5//! immediately followed by the children's elements and then a matching
6//! [`Element::ContainerEnd`]. Use [`TlvReader::read_value`] to materialise an
7//! entire element tree in one call. [`TlvReader::next_ref`] is the zero-copy
8//! sibling of [`TlvReader::next`]: it borrows string/bytes payloads straight
9//! from the input instead of allocating, and is the decode core `next` itself
10//! is implemented over.
11
12use crate::error::{Error, Result};
13use crate::tag::Tag;
14use crate::value::{Value, ValueRef};
15use crate::{element_type as et, tag_control as tc};
16
17/// Which kind of TLV container a [`ContainerStart`](Element::ContainerStart)
18/// announces.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum ContainerKind {
22    /// `Value::Structure` — children carry their own (typically context-tagged) tags.
23    Structure,
24    /// `Value::Array` — children carry anonymous tags only.
25    Array,
26    /// `Value::List` — children may carry any tag form.
27    List,
28}
29
30/// One step of the streaming reader.
31#[derive(Debug, Clone, PartialEq)]
32#[non_exhaustive]
33pub enum Element {
34    /// A complete scalar (or string/bytes) element.
35    Scalar {
36        /// The tag that identifies this element within its enclosing context.
37        tag: Tag,
38        /// The decoded scalar value.
39        value: Value,
40    },
41
42    /// A container has just been opened. Subsequent `next()` calls
43    /// return the container's children until a matching
44    /// [`ContainerEnd`](Element::ContainerEnd).
45    ContainerStart {
46        /// The tag that identifies this container within its enclosing context.
47        tag: Tag,
48        /// Which container kind was opened.
49        kind: ContainerKind,
50    },
51
52    /// The most recently-opened container has been closed.
53    ContainerEnd,
54}
55
56/// One step of the streaming reader, borrowing string/bytes payloads from
57/// the input — the zero-copy sibling of [`Element`]. Produced by
58/// [`TlvReader::next_ref`]; convert with `Element::from` when ownership is
59/// needed.
60#[derive(Debug, Clone, Copy, PartialEq)]
61#[non_exhaustive]
62pub enum ElementRef<'a> {
63    /// A complete scalar (or string/bytes) element.
64    Scalar {
65        /// The tag that identifies this element within its enclosing context.
66        tag: Tag,
67        /// The decoded value, borrowing from the reader's input.
68        value: ValueRef<'a>,
69    },
70    /// A container has just been opened (see [`Element::ContainerStart`]).
71    ContainerStart {
72        /// The tag that identifies this container within its enclosing context.
73        tag: Tag,
74        /// Which container kind was opened.
75        kind: ContainerKind,
76    },
77    /// The most recently-opened container has been closed.
78    ContainerEnd,
79}
80
81impl From<ElementRef<'_>> for Element {
82    #[inline]
83    fn from(e: ElementRef<'_>) -> Self {
84        match e {
85            ElementRef::Scalar { tag, value } => Element::Scalar {
86                tag,
87                value: Value::from(value),
88            },
89            ElementRef::ContainerStart { tag, kind } => Element::ContainerStart { tag, kind },
90            ElementRef::ContainerEnd => Element::ContainerEnd,
91        }
92    }
93}
94
95/// Maximum container nesting depth the reader accepts. The Matter spec
96/// recommends a 32-level limit to prevent stack blow-up on adversarial
97/// input.
98pub const MAX_DEPTH: usize = 32;
99
100/// Default ceiling on the total number of [`Value`] elements a single
101/// [`TlvReader::read_value`] call may materialise.
102///
103/// Tree-builder decoding allocates one `Value` (and, inside containers, one
104/// `(Tag, Value)` pair) per element. A tiny scalar such as a boolean costs a
105/// single wire byte but expands to a heap-resident `Value`, so an input packed
106/// with millions of one-byte scalars can amplify into a large allocation. This
107/// budget bounds that amplification: a decode that would produce more than this
108/// many elements fails with [`Error::ElementBudgetExceeded`].
109///
110/// The default is deliberately generous (1,048,576 elements) — far above any
111/// legitimate Matter payload, which is itself bounded by the protocol's
112/// message-size limits — so it only ever trips on adversarial input. Callers
113/// that need a tighter or looser bound can set their own via
114/// [`TlvReader::with_element_budget`].
115pub const DEFAULT_ELEMENT_BUDGET: usize = 1 << 20;
116
117/// Byte offsets of one element within a [`TlvReader`]'s input, produced by
118/// [`TlvReader::element_span`] / [`TlvReader::skip_container_span`].
119///
120/// Two-form contract:
121/// - **Scalar span** — after `next()`/`next_ref()` returns a `Scalar`, the
122///   span covers the complete element; [`body`](Self::body) starts after the
123///   tag bytes (for strings it includes the length field, whose width form
124///   lives in the element type).
125/// - **Container span** — after `next()` returns `ContainerStart` the span
126///   covers only the container header; call
127///   [`skip_container_span`](TlvReader::skip_container_span) for the full
128///   container, whose [`body`](Self::body) covers the children plus the
129///   end-of-container marker and EXCLUDES the original control/tag bytes.
130/// - **`ContainerEnd` span** — after `next()` returns `ContainerEnd`, the span
131///   covers the 1-byte end-of-container marker and [`body`](Self::body) is empty.
132///
133/// Resolve ranges against the input with [`TlvReader::span_bytes`].
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct ElementSpan {
136    start: usize,
137    body_start: usize,
138    end: usize,
139}
140
141impl ElementSpan {
142    /// The complete element: control octet through the last body byte.
143    #[must_use]
144    pub fn full(&self) -> core::ops::Range<usize> {
145        self.start..self.end
146    }
147
148    /// The element body: everything after the control octet and tag bytes.
149    /// For a container span from `skip_container_span`, this is the children
150    /// plus the end-of-container marker — the exact bytes retag re-emission
151    /// copies under a fresh header.
152    #[must_use]
153    pub fn body(&self) -> core::ops::Range<usize> {
154        self.body_start..self.end
155    }
156}
157
158/// A streaming TLV decoder over a borrowed byte slice.
159pub struct TlvReader<'a> {
160    bytes: &'a [u8],
161    pos: usize,
162    depth: usize,
163    /// Remaining element budget for a tree-builder decode. Decremented once
164    /// per materialised [`Value`] in [`Self::read_container_body`] /
165    /// [`Self::read_value`] — but only on *charged* decodes, i.e. when the
166    /// remaining input is larger than this budget; a smaller input provably
167    /// cannot exceed it (each element consumes ≥ 1 input byte), so the fast
168    /// path skips accounting without weakening the bound (see
169    /// [`Self::read_value`]). The streaming [`Self::next`] path does not
170    /// touch it because it allocates nothing per element.
171    element_budget: usize,
172    /// Span of the element most recently returned by [`Self::next`] /
173    /// [`Self::next_ref`] (or the whole container after a `skip_container*`
174    /// call). `None` before the first element.
175    last_span: Option<ElementSpan>,
176    /// Whether the element most recently returned by [`Self::next_ref`] (and
177    /// therefore [`Self::next`]) was a `ContainerStart` — the precondition
178    /// [`Self::skip_container_span`] enforces so that a span is only ever
179    /// handed out for a container the caller actually just opened. Set/cleared
180    /// only on a returned element; untouched by `Ok(None)` and by errors, in
181    /// lockstep with [`Self::last_span`].
182    last_was_container_start: bool,
183}
184
185impl<'a> TlvReader<'a> {
186    /// Construct a reader that walks `bytes` from the start, using the
187    /// [`DEFAULT_ELEMENT_BUDGET`] for tree-builder decodes.
188    #[inline]
189    pub fn new(bytes: &'a [u8]) -> Self {
190        Self {
191            bytes,
192            pos: 0,
193            depth: 0,
194            element_budget: DEFAULT_ELEMENT_BUDGET,
195            last_span: None,
196            last_was_container_start: false,
197        }
198    }
199
200    /// Construct a reader with a custom total-element budget for tree-builder
201    /// decoding (see [`DEFAULT_ELEMENT_BUDGET`]).
202    ///
203    /// A [`Self::read_value`] call that would materialise more than `budget`
204    /// [`Value`] elements fails with [`Error::ElementBudgetExceeded`]. The
205    /// budget only affects the tree-builder path; the streaming [`Self::next`]
206    /// API is unaffected because it allocates nothing per element.
207    #[inline]
208    pub fn with_element_budget(bytes: &'a [u8], budget: usize) -> Self {
209        Self {
210            bytes,
211            pos: 0,
212            depth: 0,
213            element_budget: budget,
214            last_span: None,
215            last_was_container_start: false,
216        }
217    }
218
219    /// Whether there is no more input to consume.
220    #[inline]
221    pub fn is_empty(&self) -> bool {
222        self.pos >= self.bytes.len()
223    }
224
225    /// Advance one TLV element, returning an [`Element`] that owns its
226    /// string/bytes payloads. Implemented over [`Self::next_ref`] — the
227    /// borrowed walk IS the decode core, so the two can never disagree.
228    /// Returns `Ok(None)` at end of input. See [`Self::next_ref`] for the
229    /// zero-copy variant, which borrows string/bytes payloads from the
230    /// input instead of allocating.
231    ///
232    /// # Errors
233    ///
234    /// Returns `Err` if the input is malformed:
235    ///
236    /// - [`Error::InvalidTagControl`] — unrecognised tag-control byte form.
237    /// - [`Error::InvalidElementType`] — unknown element-type code.
238    /// - [`Error::UnexpectedEof`] — truncated payload bytes.
239    /// - [`Error::UnexpectedEndOfContainer`] — end-of-container marker (`0x18`)
240    ///   at the top level, with no container open.
241    /// - [`Error::ContainerTooDeep`] — a container open would exceed
242    ///   [`MAX_DEPTH`] nesting levels.
243    ///
244    /// # Note on naming
245    ///
246    /// This method is deliberately named `next` to match the streaming-reader
247    /// idiom established by e.g. `serde`'s `Deserializer`. It returns
248    /// `Result<Option<T>>` rather than `Option<Result<T>>` so that callers
249    /// use `?` naturally. Implementing `std::iter::Iterator` is deferred to
250    /// a later phase when a fallible-iterator adapter is available.
251    #[allow(clippy::should_implement_trait)] // See note above; Iterator requires Option<Item>, not Result<Option<Item>>.
252    #[inline]
253    pub fn next(&mut self) -> Result<Option<Element>> {
254        Ok(self.next_ref()?.map(Element::from))
255    }
256
257    /// Advance one TLV element, returning an [`ElementRef`] whose
258    /// string/bytes payloads borrow directly from the reader's input — the
259    /// zero-copy sibling of [`Self::next`], and the single decode core both
260    /// methods share. Returns `Ok(None)` at end of input.
261    ///
262    /// # Errors
263    ///
264    /// Returns `Err` if the input is malformed:
265    ///
266    /// - [`Error::InvalidTagControl`] — unrecognised tag-control byte form.
267    /// - [`Error::InvalidElementType`] — unknown element-type code.
268    /// - [`Error::UnexpectedEof`] — truncated payload bytes.
269    /// - [`Error::UnexpectedEndOfContainer`] — end-of-container marker (`0x18`)
270    ///   at the top level, with no container open.
271    /// - [`Error::ContainerTooDeep`] — a container open would exceed
272    ///   [`MAX_DEPTH`] nesting levels.
273    #[inline]
274    pub fn next_ref(&mut self) -> Result<Option<ElementRef<'a>>> {
275        if self.is_empty() {
276            return Ok(None);
277        }
278        let start = self.pos;
279        let control = self.next_byte()?;
280        let elem_type = control & et::ELEMENT_TYPE_MASK;
281
282        // End-of-container is always emitted as anonymous tag form.
283        if elem_type == et::END_OF_CONTAINER {
284            if control & tc::TAG_CONTROL_MASK != tc::ANONYMOUS {
285                return Err(Error::InvalidTagControl(control & tc::TAG_CONTROL_MASK));
286            }
287            if self.depth == 0 {
288                return Err(Error::UnexpectedEndOfContainer);
289            }
290            self.depth -= 1;
291            self.last_span = Some(ElementSpan {
292                start,
293                body_start: self.pos,
294                end: self.pos,
295            });
296            self.last_was_container_start = false;
297            return Ok(Some(ElementRef::ContainerEnd));
298        }
299
300        let tag = self.read_tag(control)?;
301        let body_start = self.pos;
302
303        // Container opens — record the kind and bump depth.
304        let kind = match elem_type {
305            et::STRUCTURE => Some(ContainerKind::Structure),
306            et::ARRAY => Some(ContainerKind::Array),
307            et::LIST => Some(ContainerKind::List),
308            _ => None,
309        };
310        if let Some(kind) = kind {
311            if self.depth >= MAX_DEPTH {
312                return Err(Error::ContainerTooDeep);
313            }
314            self.depth += 1;
315            self.last_span = Some(ElementSpan {
316                start,
317                body_start,
318                end: self.pos,
319            });
320            self.last_was_container_start = true;
321            return Ok(Some(ElementRef::ContainerStart { tag, kind }));
322        }
323
324        let value = self.read_value_body_ref(elem_type)?;
325        self.last_span = Some(ElementSpan {
326            start,
327            body_start,
328            end: self.pos,
329        });
330        self.last_was_container_start = false;
331        Ok(Some(ElementRef::Scalar { tag, value }))
332    }
333
334    /// Skip the remaining body of the container whose
335    /// [`ContainerStart`](Element::ContainerStart) was just returned by
336    /// [`Self::next`], consuming through its matching
337    /// [`ContainerEnd`](Element::ContainerEnd).
338    ///
339    /// Call this immediately after `next()` yields a `ContainerStart` you
340    /// want to discard — for example an unknown field carried by a struct
341    /// from a newer Matter revision. On return the reader is positioned at
342    /// the first element *after* the skipped container. The container body
343    /// is skipped as a raw byte walk: nothing is materialised, and string
344    /// payloads inside the skipped (unobserved) region are **not** UTF-8
345    /// validated. Cost is bounded by the input size and nesting by
346    /// [`MAX_DEPTH`].
347    ///
348    /// # Errors
349    ///
350    /// - [`Error::UnclosedContainer`] — end of input before the container's
351    ///   closing marker.
352    /// - [`Error::InvalidTagControl`] — unrecognised tag-control byte form.
353    /// - [`Error::InvalidElementType`] — unknown element-type code.
354    /// - [`Error::UnexpectedEof`] — truncated payload bytes or a truncated
355    ///   length field.
356    /// - [`Error::ContainerTooDeep`] — a container open inside the skipped
357    ///   region would exceed [`MAX_DEPTH`] nesting levels.
358    /// - [`Error::UnexpectedEndOfContainer`] — called with no container open
359    ///   on this reader.
360    ///
361    /// After a successful skip, [`Self::element_span`] reports the whole
362    /// skipped container (header through end-of-container marker) — same as
363    /// calling [`Self::skip_container_span`] and discarding the return value.
364    ///
365    /// # Examples
366    ///
367    /// ```
368    /// use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter};
369    /// let mut buf = Vec::new();
370    /// let mut w = TlvWriter::new(&mut buf);
371    /// w.start_structure(Tag::Anonymous)?;
372    /// w.start_structure(Tag::Context(9))?; // an unknown nested field
373    /// w.end_container()?;
374    /// w.put_uint(Tag::Context(1), 42)?;
375    /// w.end_container()?;
376    ///
377    /// let mut r = TlvReader::new(&buf);
378    /// r.next()?; // open the outer struct
379    /// // next() returns the nested ctx9 ContainerStart we want to discard:
380    /// assert!(matches!(
381    ///     r.next()?,
382    ///     Some(Element::ContainerStart { kind: ContainerKind::Structure, .. })
383    /// ));
384    /// r.skip_container()?; // drain the nested struct
385    /// // the field after the unknown container is still readable:
386    /// assert!(matches!(r.next()?, Some(Element::Scalar { tag: Tag::Context(1), .. })));
387    /// # Ok::<(), matter_codec::Error>(())
388    /// ```
389    ///
390    /// After an `Err` from this method, the reader's position and depth are
391    /// unspecified — discard the reader rather than continuing to iterate.
392    pub fn skip_container(&mut self) -> Result<()> {
393        self.skip_container_body()?;
394        if let Some(h) = self.last_span {
395            self.last_span = Some(ElementSpan {
396                start: h.start,
397                body_start: h.body_start,
398                end: self.pos,
399            });
400        }
401        // The container that was open has been consumed: a following
402        // `skip_container_span` must not treat this as a fresh ContainerStart.
403        self.last_was_container_start = false;
404        Ok(())
405    }
406
407    /// Like [`Self::skip_container`], but returns the skipped container's
408    /// [`ElementSpan`] (marked at the `ContainerStart` just returned by
409    /// `next()`; ended at the read position after the raw skip).
410    ///
411    /// This method has a precondition: the immediately preceding
412    /// [`Self::next`] / [`Self::next_ref`] call must have returned a
413    /// `ContainerStart`. Anywhere else — after a scalar, after an
414    /// end-of-container, after a [`Self::read_value`] that walked a whole
415    /// tree, after another skip, or before any element at all — it consumes
416    /// nothing and returns [`Error::UnexpectedEndOfContainer`], rather than
417    /// handing back a span over unrelated bytes that a retag caller would
418    /// re-emit as malformed TLV.
419    ///
420    /// # Errors
421    ///
422    /// [`Error::UnexpectedEndOfContainer`] if the precondition above does not
423    /// hold, plus every error [`Self::skip_container`] can return. Misuse is
424    /// always an error — never a panic, and never a meaningless span.
425    ///
426    /// After an `Err` from this method, the reader's position and depth are
427    /// unspecified — discard the reader rather than continuing to iterate.
428    pub fn skip_container_span(&mut self) -> Result<ElementSpan> {
429        if !self.last_was_container_start {
430            return Err(Error::UnexpectedEndOfContainer);
431        }
432        let header = self.last_span.ok_or(Error::UnexpectedEndOfContainer)?;
433        self.skip_container_body()?;
434        let span = ElementSpan {
435            start: header.start,
436            body_start: header.body_start,
437            end: self.pos,
438        };
439        self.last_span = Some(span);
440        self.last_was_container_start = false;
441        Ok(span)
442    }
443
444    /// Span of the element most recently returned by [`Self::next`] /
445    /// [`Self::next_ref`] (or the whole container after a
446    /// `skip_container*` call). `None` before the first element; unchanged
447    /// by calls that return `Ok(None)` or an error.
448    ///
449    /// Tree-builder reads drive the same core: [`Self::read_value`] walks its
450    /// element via [`Self::next_ref`] internally, so afterwards the span
451    /// refers to the last *interior* element it consumed, not the tree as a
452    /// whole. Read the span only immediately after the `next` / `next_ref`
453    /// call whose element you care about.
454    #[inline]
455    #[must_use]
456    pub fn element_span(&self) -> Option<ElementSpan> {
457        self.last_span
458    }
459
460    /// Resolve a range produced by this reader's span APIs against the
461    /// reader's input. Returns an empty slice for a range that does not lie
462    /// within the input (only possible with a span from a different reader).
463    #[inline]
464    #[must_use]
465    pub fn span_bytes(&self, range: core::ops::Range<usize>) -> &'a [u8] {
466        self.bytes.get(range).unwrap_or(&[])
467    }
468
469    /// Raw byte walk: control byte → tag skip → body skip. Nothing is
470    /// materialised and string payloads are advanced over without UTF-8
471    /// validation (deliberate: skipped data is unobserved; 2026-08-09
472    /// perf spec §4.1). Structural validation (tag forms, element types,
473    /// bounds, nesting depth) is identical to `next()`.
474    fn skip_container_body(&mut self) -> Result<()> {
475        let mut depth = 1usize;
476        while depth > 0 {
477            if self.is_empty() {
478                return Err(Error::UnclosedContainer);
479            }
480            let control = self.next_byte()?;
481            let elem_type = control & et::ELEMENT_TYPE_MASK;
482            if elem_type == et::END_OF_CONTAINER {
483                if control & tc::TAG_CONTROL_MASK != tc::ANONYMOUS {
484                    return Err(Error::InvalidTagControl(control & tc::TAG_CONTROL_MASK));
485                }
486                if depth == 1 && self.depth == 0 {
487                    // No container is actually open on this reader — the
488                    // same misuse `next()` reports for a stray end marker.
489                    return Err(Error::UnexpectedEndOfContainer);
490                }
491                depth -= 1;
492                continue;
493            }
494            // Parse-and-discard the tag: allocation-free, and keeps tag-form
495            // validation identical to `next()`.
496            let _ = self.read_tag(control)?;
497            match elem_type {
498                et::STRUCTURE | et::ARRAY | et::LIST => {
499                    // `next()` would error when opening a child at
500                    // self.depth >= MAX_DEPTH; during a skip that effective
501                    // depth is reader depth + local nesting - 1, so this is
502                    // `self.depth + depth - 1 >= MAX_DEPTH` rearranged to
503                    // avoid clippy::int_plus_one.
504                    if self.depth + depth > MAX_DEPTH {
505                        return Err(Error::ContainerTooDeep);
506                    }
507                    depth += 1;
508                }
509                _ => self.skip_value_body(elem_type)?,
510            }
511        }
512        // The skipped container's ContainerStart incremented self.depth in
513        // next(); its end marker was consumed here rather than via next(),
514        // so balance the counter. Cannot underflow: reaching this point
515        // required consuming an end marker at local depth 1, which the
516        // misuse guard above rejects when self.depth == 0.
517        self.depth -= 1;
518        Ok(())
519    }
520
521    /// Advance past a scalar body during a raw skip, without materialising
522    /// it. String/bytes payloads are bounds-checked and skipped whole; no
523    /// UTF-8 validation is performed on skipped data.
524    fn skip_value_body(&mut self, elem_type: u8) -> Result<()> {
525        let n = match elem_type {
526            et::BOOL_FALSE | et::BOOL_TRUE | et::NULL => 0,
527            et::UINT8 | et::INT8 => 1,
528            et::UINT16 | et::INT16 => 2,
529            et::UINT32 | et::INT32 | et::FLOAT32 => 4,
530            et::UINT64 | et::INT64 | et::FLOAT64 => 8,
531            et::UTF8_LEN8
532            | et::UTF8_LEN16
533            | et::UTF8_LEN32
534            | et::UTF8_LEN64
535            | et::BYTES_LEN8
536            | et::BYTES_LEN16
537            | et::BYTES_LEN32
538            | et::BYTES_LEN64 => {
539                let len = self.read_payload_len(elem_type)?;
540                let _ = self.next_bytes(len)?;
541                return Ok(());
542            }
543            other => return Err(Error::InvalidElementType(other)),
544        };
545        let _ = self.next_bytes(n)?;
546        Ok(())
547    }
548
549    /// Materialise one full TLV element as a `(Tag, Value)`. Scalars are
550    /// returned directly; containers are read recursively up to
551    /// [`MAX_DEPTH`] levels (enforced by [`Self::next`]'s depth counter).
552    ///
553    /// # Errors
554    ///
555    /// - [`Error::UnexpectedEof`] — the input is empty.
556    /// - [`Error::UnexpectedEndOfContainer`] — the first element is a stray
557    ///   end-of-container marker.
558    /// - [`Error::UnclosedContainer`] — end of input was reached before the
559    ///   container's closing marker.
560    /// - [`Error::NonAnonymousArrayTag`] — an array child carried a
561    ///   non-anonymous tag (the spec requires array elements to be anonymous).
562    /// - [`Error::ElementBudgetExceeded`] — the decode would materialise more
563    ///   than the configured element budget (see [`DEFAULT_ELEMENT_BUDGET`]).
564    /// - Any error returned by [`Self::next`].
565    pub fn read_value(&mut self) -> Result<(Tag, Value)> {
566        // Budget fast path: every materialised element consumes at least one
567        // input byte (a null is one control byte; a container is a control
568        // byte plus its end marker), so when the remaining input is no larger
569        // than the remaining budget this decode CANNOT exceed it — decode
570        // with per-element accounting compiled out entirely. Real Matter
571        // payloads (≲ 1.5 KiB) against the 2^20 default budget always take
572        // this path; the charged path serves oversized or custom-budget
573        // inputs.
574        //
575        // This is observably equivalent to charging every element: a call is
576        // only admitted uncharged when the byte bound proves it cannot fail,
577        // and from such a call onward every future element from this reader
578        // is covered by the same bound (the remaining input only shrinks), so
579        // sequences of `read_value` calls fail on exactly the same element —
580        // with the same error — as the always-charged implementation. The
581        // lifetime invariant is unchanged: one reader never materialises more
582        // than its configured budget's worth of live `Value` elements.
583        let remaining_input = self.bytes.len().saturating_sub(self.pos);
584        if remaining_input <= self.element_budget {
585            self.read_value_inner::<false>()
586        } else {
587            self.read_value_inner::<true>()
588        }
589    }
590
591    /// Tree-builder body of [`Self::read_value`], monomorphised over whether
592    /// per-element budget accounting is active (see the fast-path comment
593    /// there — `CHARGE == false` is only reachable when the byte bound proves
594    /// the budget cannot be exceeded).
595    ///
596    /// Drives [`Self::next_ref`] rather than [`Self::next`]: the owned
597    /// [`Element`] `next()` would build is an intermediate this path
598    /// immediately destructures again, and materialising it per element cost
599    /// ~2.5x on the decode benchmarks (2026-08-10 phase-4 regression). The
600    /// borrowed element is destructured here and only its payload is taken
601    /// into ownership, at the point where it is pushed into the tree.
602    fn read_value_inner<const CHARGE: bool>(&mut self) -> Result<(Tag, Value)> {
603        match self.next_ref()? {
604            Some(ElementRef::Scalar { tag, value }) => {
605                if CHARGE {
606                    self.charge_element()?;
607                }
608                Ok((tag, Value::from(value)))
609            }
610            Some(ElementRef::ContainerStart { tag, kind }) => {
611                if CHARGE {
612                    self.charge_element()?;
613                }
614                let value = self.read_container_body::<CHARGE>(kind)?;
615                Ok((tag, value))
616            }
617            Some(ElementRef::ContainerEnd) => Err(Error::UnexpectedEndOfContainer),
618            None => Err(Error::UnexpectedEof),
619        }
620    }
621
622    /// Charge one element against the tree-builder budget. Returns
623    /// [`Error::ElementBudgetExceeded`] once the budget is exhausted. Only
624    /// called on charged decodes ([`Self::read_value`]'s slow path); the
625    /// container loops mirror the same arithmetic in a local for speed (see
626    /// [`Self::read_container_body`]).
627    fn charge_element(&mut self) -> Result<()> {
628        self.element_budget = self
629            .element_budget
630            .checked_sub(1)
631            .ok_or(Error::ElementBudgetExceeded)?;
632        Ok(())
633    }
634
635    /// Decode a container's children into a [`Value`] tree.
636    ///
637    /// # Element-budget accounting (denial-of-service bound)
638    ///
639    /// When `CHARGE` is false (the [`Self::read_value`] fast path: the byte
640    /// bound already proves the budget cannot be exceeded) no accounting code
641    /// is compiled into this copy at all.
642    ///
643    /// When `CHARGE` is true, the check is the hot path of tree-builder
644    /// decoding, so instead of calling [`Self::charge_element`] (a
645    /// read-modify-write of `self.element_budget` that the compiler cannot
646    /// keep in a register across the `self.next()` calls) each loop mirrors
647    /// the remaining budget in a local, charges the local per element, and
648    /// syncs it back to `self.element_budget` around recursion and at
649    /// container close.
650    ///
651    /// The preserved invariant: **a charged decode never materialises more
652    /// than the configured element budget's worth of [`Value`] elements, and
653    /// it fails (`Error::ElementBudgetExceeded`) on exactly the same element
654    /// as a per-element field update would** — every element is still
655    /// individually charged before it is pushed, and the field is up to date
656    /// whenever recursion (the only other consumer) runs. The one observable
657    /// difference is on *error* returns: charges made since the last sync are
658    /// not written back — which cannot weaken the bound, because the failed
659    /// call's partially built tree is dropped with it (the budget bounds live
660    /// memory amplification, and a subsequent `read_value` on the same reader
661    /// starts from a tree of zero live elements).
662    fn read_container_body<const CHARGE: bool>(&mut self, kind: ContainerKind) -> Result<Value> {
663        // Both loops walk `next_ref` and convert the borrowed payload at push
664        // time — see `read_value_inner` for why the owned `Element` step is
665        // skipped here.
666        //
667        // Branch on the container kind once, before the loop, so arrays decode
668        // straight into a `Vec<Value>` without first building a `Vec<(Tag,
669        // Value)>` and re-collecting. Structures and lists keep their members'
670        // tags.
671        match kind {
672            ContainerKind::Array => {
673                let mut elements: Vec<Value> = Vec::new();
674                let mut budget = self.element_budget;
675                loop {
676                    match self.next_ref()? {
677                        None => return Err(Error::UnclosedContainer),
678                        Some(ElementRef::ContainerEnd) => break,
679                        Some(ElementRef::Scalar { tag, value }) => {
680                            // Spec: every array element must be anonymous. Fail
681                            // closed on any other tag rather than discarding it.
682                            if tag != Tag::Anonymous {
683                                return Err(Error::NonAnonymousArrayTag);
684                            }
685                            if CHARGE {
686                                budget =
687                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
688                            }
689                            elements.push(Value::from(value));
690                        }
691                        Some(ElementRef::ContainerStart {
692                            tag,
693                            kind: inner_kind,
694                        }) => {
695                            if tag != Tag::Anonymous {
696                                return Err(Error::NonAnonymousArrayTag);
697                            }
698                            if CHARGE {
699                                budget =
700                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
701                                self.element_budget = budget;
702                            }
703                            elements.push(self.read_container_body::<CHARGE>(inner_kind)?);
704                            if CHARGE {
705                                budget = self.element_budget;
706                            }
707                        }
708                    }
709                }
710                if CHARGE {
711                    self.element_budget = budget;
712                }
713                Ok(Value::Array(elements))
714            }
715            ContainerKind::Structure | ContainerKind::List => {
716                let mut members: Vec<(Tag, Value)> = Vec::new();
717                let mut budget = self.element_budget;
718                loop {
719                    match self.next_ref()? {
720                        None => return Err(Error::UnclosedContainer),
721                        Some(ElementRef::ContainerEnd) => break,
722                        Some(ElementRef::Scalar { tag, value }) => {
723                            if CHARGE {
724                                budget =
725                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
726                            }
727                            members.push((tag, Value::from(value)));
728                        }
729                        Some(ElementRef::ContainerStart {
730                            tag,
731                            kind: inner_kind,
732                        }) => {
733                            if CHARGE {
734                                budget =
735                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
736                                self.element_budget = budget;
737                            }
738                            let inner = self.read_container_body::<CHARGE>(inner_kind)?;
739                            members.push((tag, inner));
740                            if CHARGE {
741                                budget = self.element_budget;
742                            }
743                        }
744                    }
745                }
746                if CHARGE {
747                    self.element_budget = budget;
748                }
749                Ok(match kind {
750                    ContainerKind::List => Value::List(members),
751                    // The outer match guarantees this arm is Structure.
752                    _ => Value::Structure(members),
753                })
754            }
755        }
756    }
757
758    #[inline]
759    fn next_byte(&mut self) -> Result<u8> {
760        let b = *self.bytes.get(self.pos).ok_or(Error::UnexpectedEof)?;
761        self.pos += 1;
762        Ok(b)
763    }
764
765    #[inline]
766    fn next_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
767        let end = self.pos.checked_add(n).ok_or(Error::LengthOverflow)?;
768        let slice = self.bytes.get(self.pos..end).ok_or(Error::UnexpectedEof)?;
769        self.pos = end;
770        Ok(slice)
771    }
772
773    // The decode helpers below (`read_tag`, `read_value_body_ref`, the
774    // fixed-width and length-field readers) are each one small step of a
775    // single element's decode. Until phase 4 they lived inside one monolithic
776    // `next()` and were inlined into it implicitly; once the core moved into
777    // `next_ref` — itself `#[inline]`, so it is inlined into its callers and
778    // then re-optimised there — LLVM stopped inlining them and every element
779    // paid a call per step (measured: +73% on `decode/struct_500_uint`).
780    // Marking them explicitly restores the pre-phase-4 shape.
781    #[inline]
782    fn read_tag(&mut self, control: u8) -> Result<Tag> {
783        match control & tc::TAG_CONTROL_MASK {
784            tc::ANONYMOUS => Ok(Tag::Anonymous),
785            tc::CONTEXT => {
786                let n = self.next_byte()?;
787                Ok(Tag::Context(n))
788            }
789            tc::COMMON_PROFILE_2 => {
790                let raw: [u8; 2] = self
791                    .next_bytes(2)?
792                    .try_into()
793                    .map_err(|_| Error::InternalSliceConversion)?;
794                Ok(Tag::CommonProfile(u32::from(u16::from_le_bytes(raw))))
795            }
796            tc::COMMON_PROFILE_4 => {
797                let raw: [u8; 4] = self
798                    .next_bytes(4)?
799                    .try_into()
800                    .map_err(|_| Error::InternalSliceConversion)?;
801                Ok(Tag::CommonProfile(u32::from_le_bytes(raw)))
802            }
803            tc::IMPLICIT_PROFILE_2 => {
804                let raw: [u8; 2] = self
805                    .next_bytes(2)?
806                    .try_into()
807                    .map_err(|_| Error::InternalSliceConversion)?;
808                Ok(Tag::ImplicitProfile(u32::from(u16::from_le_bytes(raw))))
809            }
810            tc::IMPLICIT_PROFILE_4 => {
811                let raw: [u8; 4] = self
812                    .next_bytes(4)?
813                    .try_into()
814                    .map_err(|_| Error::InternalSliceConversion)?;
815                Ok(Tag::ImplicitProfile(u32::from_le_bytes(raw)))
816            }
817            tc::FULLY_QUALIFIED_6 => {
818                let vendor = self.read_u16_le()?;
819                let profile = self.read_u16_le()?;
820                let tag = u32::from(self.read_u16_le()?);
821                Ok(Tag::FullyQualified {
822                    vendor,
823                    profile,
824                    tag,
825                })
826            }
827            tc::FULLY_QUALIFIED_8 => {
828                let vendor = self.read_u16_le()?;
829                let profile = self.read_u16_le()?;
830                let tag = self.read_u32_le()?;
831                Ok(Tag::FullyQualified {
832                    vendor,
833                    profile,
834                    tag,
835                })
836            }
837            // The 3-bit tag-control field has only 8 possible values, and
838            // we have arms for all 8. This arm is unreachable in practice
839            // but rustc cannot prove that statically.
840            other => Err(Error::InvalidTagControl(other)),
841        }
842    }
843
844    #[inline]
845    fn read_u16_le(&mut self) -> Result<u16> {
846        let raw: [u8; 2] = self
847            .next_bytes(2)?
848            .try_into()
849            .map_err(|_| Error::InternalSliceConversion)?;
850        Ok(u16::from_le_bytes(raw))
851    }
852
853    #[inline]
854    fn read_u32_le(&mut self) -> Result<u32> {
855        let raw: [u8; 4] = self
856            .next_bytes(4)?
857            .try_into()
858            .map_err(|_| Error::InternalSliceConversion)?;
859        Ok(u32::from_le_bytes(raw))
860    }
861
862    #[allow(clippy::cast_possible_wrap)] // `b as i8`: reinterprets the byte pattern as signed, not truncation.
863    #[inline]
864    fn read_value_body_ref(&mut self, elem_type: u8) -> Result<ValueRef<'a>> {
865        match elem_type {
866            et::BOOL_FALSE => Ok(ValueRef::Bool(false)),
867            et::BOOL_TRUE => Ok(ValueRef::Bool(true)),
868            et::NULL => Ok(ValueRef::Null),
869            et::UINT8 => Ok(ValueRef::Uint(u64::from(self.next_byte()?))),
870            et::UINT16 => {
871                let raw: [u8; 2] = self
872                    .next_bytes(2)?
873                    .try_into()
874                    .map_err(|_| Error::InternalSliceConversion)?;
875                Ok(ValueRef::Uint(u64::from(u16::from_le_bytes(raw))))
876            }
877            et::UINT32 => {
878                let raw: [u8; 4] = self
879                    .next_bytes(4)?
880                    .try_into()
881                    .map_err(|_| Error::InternalSliceConversion)?;
882                Ok(ValueRef::Uint(u64::from(u32::from_le_bytes(raw))))
883            }
884            et::UINT64 => {
885                let raw: [u8; 8] = self
886                    .next_bytes(8)?
887                    .try_into()
888                    .map_err(|_| Error::InternalSliceConversion)?;
889                Ok(ValueRef::Uint(u64::from_le_bytes(raw)))
890            }
891            et::INT8 => {
892                let b = self.next_byte()?;
893                Ok(ValueRef::Int(i64::from(b as i8)))
894            }
895            et::INT16 => {
896                let raw: [u8; 2] = self
897                    .next_bytes(2)?
898                    .try_into()
899                    .map_err(|_| Error::InternalSliceConversion)?;
900                Ok(ValueRef::Int(i64::from(i16::from_le_bytes(raw))))
901            }
902            et::INT32 => {
903                let raw: [u8; 4] = self
904                    .next_bytes(4)?
905                    .try_into()
906                    .map_err(|_| Error::InternalSliceConversion)?;
907                Ok(ValueRef::Int(i64::from(i32::from_le_bytes(raw))))
908            }
909            et::INT64 => {
910                let raw: [u8; 8] = self
911                    .next_bytes(8)?
912                    .try_into()
913                    .map_err(|_| Error::InternalSliceConversion)?;
914                Ok(ValueRef::Int(i64::from_le_bytes(raw)))
915            }
916            et::FLOAT32 => {
917                let raw: [u8; 4] = self
918                    .next_bytes(4)?
919                    .try_into()
920                    .map_err(|_| Error::InternalSliceConversion)?;
921                Ok(ValueRef::Float(f32::from_le_bytes(raw)))
922            }
923            et::FLOAT64 => {
924                let raw: [u8; 8] = self
925                    .next_bytes(8)?
926                    .try_into()
927                    .map_err(|_| Error::InternalSliceConversion)?;
928                Ok(ValueRef::Double(f64::from_le_bytes(raw)))
929            }
930            et::UTF8_LEN8 | et::UTF8_LEN16 | et::UTF8_LEN32 | et::UTF8_LEN64 => {
931                let len = self.read_payload_len(elem_type)?;
932                self.read_utf8_ref(len)
933            }
934            et::BYTES_LEN8 | et::BYTES_LEN16 | et::BYTES_LEN32 | et::BYTES_LEN64 => {
935                let len = self.read_payload_len(elem_type)?;
936                self.read_bytes_ref(len)
937            }
938            other => Err(Error::InvalidElementType(other)),
939        }
940    }
941
942    /// Read the variable-width length field that precedes utf8 and bytes
943    /// payloads. The two low bits of the element type encode the width:
944    /// `0b00` = 1 byte, `0b01` = 2 bytes, `0b10` = 4 bytes, `0b11` = 8 bytes.
945    #[inline]
946    fn read_payload_len(&mut self, elem_type: u8) -> Result<usize> {
947        match elem_type & 0b11 {
948            0b00 => Ok(usize::from(self.next_byte()?)),
949            0b01 => Ok(usize::from(self.read_u16_le()?)),
950            0b10 => usize::try_from(self.read_u32_le()?).map_err(|_| Error::LengthOverflow),
951            _ => usize::try_from(self.read_u64_le()?).map_err(|_| Error::LengthOverflow),
952        }
953    }
954
955    #[inline]
956    fn read_u64_le(&mut self) -> Result<u64> {
957        let raw: [u8; 8] = self
958            .next_bytes(8)?
959            .try_into()
960            .map_err(|_| Error::InternalSliceConversion)?;
961        Ok(u64::from_le_bytes(raw))
962    }
963
964    #[inline]
965    fn read_utf8_ref(&mut self, len: usize) -> Result<ValueRef<'a>> {
966        let bytes = self.next_bytes(len)?;
967        // Validate UTF-8 across the ENTIRE payload (a malformed suffix still
968        // fails), then present only the text before the first IS1 (0x1F)
969        // separator. Matter uses IS1 to separate a char string's text from an
970        // optional localized-string language suffix (`"Kitchen\u{1F}0409"`);
971        // chip's `TLVReader::Get(CharSpan&)` and matter.js both return only the
972        // text (chip TestTLV.cpp CheckTLVCharSpan). Returning the whole payload
973        // surfaced the raw separator+suffix on reads of localized labels
974        // (BasicInformation NodeLabel, UserLabel) — CODEC-1. The raw suffix
975        // (LSID) is not yet exposed; that is a separate additive follow-up.
976        let s = core::str::from_utf8(bytes)?;
977        // IS1 (0x1F) is single-byte ASCII, so the split index is a valid char
978        // boundary.
979        let text = match s.find('\u{1F}') {
980            Some(i) => &s[..i],
981            None => s,
982        };
983        Ok(ValueRef::Utf8(text))
984    }
985
986    #[inline]
987    fn read_bytes_ref(&mut self, len: usize) -> Result<ValueRef<'a>> {
988        Ok(ValueRef::Bytes(self.next_bytes(len)?))
989    }
990}
991
992#[cfg(test)]
993#[allow(clippy::unwrap_used)] // Test code: CLAUDE.md allows unwrap with a documented justification.
994mod tests {
995    use super::*;
996
997    #[test]
998    fn next_returns_none_on_empty_input() {
999        let mut r = TlvReader::new(&[]);
1000        assert!(r.is_empty());
1001        assert_eq!(r.next().unwrap(), None);
1002    }
1003
1004    #[test]
1005    fn next_decodes_bool_true_anonymous_vector_0001() {
1006        let mut r = TlvReader::new(&[0x09]);
1007        let el = r.next().unwrap().unwrap();
1008        assert_eq!(
1009            el,
1010            Element::Scalar {
1011                tag: Tag::Anonymous,
1012                value: Value::Bool(true)
1013            }
1014        );
1015        assert!(r.is_empty());
1016    }
1017
1018    #[test]
1019    fn next_decodes_bool_false() {
1020        let mut r = TlvReader::new(&[0x08]);
1021        let el = r.next().unwrap().unwrap();
1022        assert_eq!(
1023            el,
1024            Element::Scalar {
1025                tag: Tag::Anonymous,
1026                value: Value::Bool(false)
1027            }
1028        );
1029    }
1030
1031    #[test]
1032    fn next_decodes_null_vector_implied() {
1033        let mut r = TlvReader::new(&[0x14]);
1034        let el = r.next().unwrap().unwrap();
1035        assert_eq!(
1036            el,
1037            Element::Scalar {
1038                tag: Tag::Anonymous,
1039                value: Value::Null
1040            }
1041        );
1042    }
1043
1044    #[test]
1045    fn next_decodes_uint8_42_vector_0003() {
1046        let mut r = TlvReader::new(&[0x04, 0x2A]);
1047        let el = r.next().unwrap().unwrap();
1048        assert_eq!(
1049            el,
1050            Element::Scalar {
1051                tag: Tag::Anonymous,
1052                value: Value::Uint(42)
1053            }
1054        );
1055    }
1056
1057    #[test]
1058    fn next_decodes_uint16_0x1234() {
1059        let mut r = TlvReader::new(&[0x05, 0x34, 0x12]);
1060        let el = r.next().unwrap().unwrap();
1061        assert_eq!(
1062            el,
1063            Element::Scalar {
1064                tag: Tag::Anonymous,
1065                value: Value::Uint(0x1234)
1066            }
1067        );
1068    }
1069
1070    #[test]
1071    fn next_decodes_uint32_0xcafebabe() {
1072        let mut r = TlvReader::new(&[0x06, 0xBE, 0xBA, 0xFE, 0xCA]);
1073        let el = r.next().unwrap().unwrap();
1074        assert_eq!(
1075            el,
1076            Element::Scalar {
1077                tag: Tag::Anonymous,
1078                value: Value::Uint(0xCAFE_BABE)
1079            }
1080        );
1081    }
1082
1083    #[test]
1084    fn next_decodes_uint64_big() {
1085        let bytes = [0x07, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
1086        let mut r = TlvReader::new(&bytes);
1087        let el = r.next().unwrap().unwrap();
1088        assert_eq!(
1089            el,
1090            Element::Scalar {
1091                tag: Tag::Anonymous,
1092                value: Value::Uint(0x0123_4567_89AB_CDEF),
1093            }
1094        );
1095    }
1096
1097    #[test]
1098    fn next_decodes_int8_neg17_vector_0008() {
1099        let mut r = TlvReader::new(&[0x00, 0xEF]);
1100        let el = r.next().unwrap().unwrap();
1101        assert_eq!(
1102            el,
1103            Element::Scalar {
1104                tag: Tag::Anonymous,
1105                value: Value::Int(-17)
1106            }
1107        );
1108    }
1109
1110    #[test]
1111    fn next_decodes_int16_neg129() {
1112        let mut r = TlvReader::new(&[0x01, 0x7F, 0xFF]);
1113        let el = r.next().unwrap().unwrap();
1114        assert_eq!(
1115            el,
1116            Element::Scalar {
1117                tag: Tag::Anonymous,
1118                value: Value::Int(-129)
1119            }
1120        );
1121    }
1122
1123    #[test]
1124    fn next_decodes_int32_min() {
1125        let mut r = TlvReader::new(&[0x02, 0x00, 0x00, 0x00, 0x80]);
1126        let el = r.next().unwrap().unwrap();
1127        assert_eq!(
1128            el,
1129            Element::Scalar {
1130                tag: Tag::Anonymous,
1131                value: Value::Int(i64::from(i32::MIN))
1132            }
1133        );
1134    }
1135
1136    #[test]
1137    fn next_decodes_int64_min() {
1138        let bytes = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80];
1139        let mut r = TlvReader::new(&bytes);
1140        let el = r.next().unwrap().unwrap();
1141        assert_eq!(
1142            el,
1143            Element::Scalar {
1144                tag: Tag::Anonymous,
1145                value: Value::Int(i64::MIN)
1146            }
1147        );
1148    }
1149
1150    #[test]
1151    fn next_decodes_float32_zero_vector_0013() {
1152        let mut r = TlvReader::new(&[0x0A, 0x00, 0x00, 0x00, 0x00]);
1153        let el = r.next().unwrap().unwrap();
1154        assert_eq!(
1155            el,
1156            Element::Scalar {
1157                tag: Tag::Anonymous,
1158                value: Value::Float(0.0)
1159            }
1160        );
1161    }
1162
1163    #[test]
1164    fn next_decodes_float64_zero_vector_0014() {
1165        let bytes = [0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
1166        let mut r = TlvReader::new(&bytes);
1167        let el = r.next().unwrap().unwrap();
1168        assert_eq!(
1169            el,
1170            Element::Scalar {
1171                tag: Tag::Anonymous,
1172                value: Value::Double(0.0)
1173            }
1174        );
1175    }
1176
1177    #[test]
1178    fn next_decodes_uint_with_context_tag_5() {
1179        let mut r = TlvReader::new(&[0x24, 0x05, 0x2A]);
1180        let el = r.next().unwrap().unwrap();
1181        assert_eq!(
1182            el,
1183            Element::Scalar {
1184                tag: Tag::Context(5),
1185                value: Value::Uint(42)
1186            }
1187        );
1188    }
1189
1190    #[test]
1191    fn next_errors_on_unexpected_eof_in_payload() {
1192        let mut r = TlvReader::new(&[0x05]); // uint16 with no payload
1193        assert!(matches!(r.next(), Err(Error::UnexpectedEof)));
1194    }
1195
1196    #[test]
1197    fn next_decodes_uint_with_common_profile_2_byte_tag() {
1198        let mut r = TlvReader::new(&[0x44, 0x07, 0x00, 0x2A]);
1199        let el = r.next().unwrap().unwrap();
1200        assert_eq!(
1201            el,
1202            Element::Scalar {
1203                tag: Tag::CommonProfile(7),
1204                value: Value::Uint(42)
1205            }
1206        );
1207    }
1208
1209    #[test]
1210    fn next_decodes_uint_with_common_profile_4_byte_tag() {
1211        let mut r = TlvReader::new(&[0x64, 0x45, 0x23, 0x01, 0x00, 0x2A]);
1212        let el = r.next().unwrap().unwrap();
1213        assert_eq!(
1214            el,
1215            Element::Scalar {
1216                tag: Tag::CommonProfile(0x0001_2345),
1217                value: Value::Uint(42)
1218            }
1219        );
1220    }
1221
1222    #[test]
1223    fn next_decodes_uint_with_implicit_profile_2_byte_tag() {
1224        let mut r = TlvReader::new(&[0x84, 0x07, 0x00, 0x2A]);
1225        let el = r.next().unwrap().unwrap();
1226        assert_eq!(
1227            el,
1228            Element::Scalar {
1229                tag: Tag::ImplicitProfile(7),
1230                value: Value::Uint(42)
1231            }
1232        );
1233    }
1234
1235    #[test]
1236    fn next_decodes_uint_with_implicit_profile_4_byte_tag() {
1237        let mut r = TlvReader::new(&[0xA4, 0x45, 0x23, 0x01, 0x00, 0x2A]);
1238        let el = r.next().unwrap().unwrap();
1239        assert_eq!(
1240            el,
1241            Element::Scalar {
1242                tag: Tag::ImplicitProfile(0x0001_2345),
1243                value: Value::Uint(42)
1244            }
1245        );
1246    }
1247
1248    #[test]
1249    fn next_decodes_uint_with_fully_qualified_6_byte() {
1250        let mut r = TlvReader::new(&[0xC4, 0xF1, 0xFF, 0x06, 0x00, 0x05, 0x00, 0x2A]);
1251        let el = r.next().unwrap().unwrap();
1252        assert_eq!(
1253            el,
1254            Element::Scalar {
1255                tag: Tag::FullyQualified {
1256                    vendor: 0xFFF1,
1257                    profile: 0x0006,
1258                    tag: 5
1259                },
1260                value: Value::Uint(42),
1261            }
1262        );
1263    }
1264
1265    #[test]
1266    fn next_decodes_uint_with_fully_qualified_8_byte() {
1267        let mut r = TlvReader::new(&[0xE4, 0xF1, 0xFF, 0x06, 0x00, 0x45, 0x23, 0x01, 0x00, 0x2A]);
1268        let el = r.next().unwrap().unwrap();
1269        assert_eq!(
1270            el,
1271            Element::Scalar {
1272                tag: Tag::FullyQualified {
1273                    vendor: 0xFFF1,
1274                    profile: 0x0006,
1275                    tag: 0x0001_2345
1276                },
1277                value: Value::Uint(42),
1278            }
1279        );
1280    }
1281
1282    #[test]
1283    fn read_value_returns_tag_and_value_for_scalar() {
1284        let mut r = TlvReader::new(&[0x24, 0x05, 0x2A]);
1285        let (tag, value) = r.read_value().unwrap();
1286        assert_eq!(tag, Tag::Context(5));
1287        assert_eq!(value, Value::Uint(42));
1288    }
1289
1290    #[test]
1291    fn read_value_errors_on_empty_input() {
1292        let mut r = TlvReader::new(&[]);
1293        assert!(matches!(r.read_value(), Err(Error::UnexpectedEof)));
1294    }
1295
1296    #[test]
1297    fn next_decodes_utf8_hello_vector_0015() {
1298        let bytes = [0x0C, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21];
1299        let mut r = TlvReader::new(&bytes);
1300        let el = r.next().unwrap().unwrap();
1301        assert_eq!(
1302            el,
1303            Element::Scalar {
1304                tag: Tag::Anonymous,
1305                value: Value::Utf8(String::from("Hello!")),
1306            }
1307        );
1308    }
1309
1310    #[test]
1311    fn next_decodes_utf8_empty_vector_0016() {
1312        let bytes = [0x0C, 0x00];
1313        let mut r = TlvReader::new(&bytes);
1314        let el = r.next().unwrap().unwrap();
1315        assert_eq!(
1316            el,
1317            Element::Scalar {
1318                tag: Tag::Anonymous,
1319                value: Value::Utf8(String::new()),
1320            }
1321        );
1322    }
1323
1324    #[test]
1325    fn next_decodes_utf8_len16_path() {
1326        let mut bytes = vec![0x0D, 0x00, 0x01]; // UTF8_LEN16, length 256 LE
1327        bytes.extend(std::iter::repeat_n(b'a', 256));
1328        let mut r = TlvReader::new(&bytes);
1329        let el = r.next().unwrap().unwrap();
1330        let Element::Scalar {
1331            value: Value::Utf8(s),
1332            ..
1333        } = el
1334        else {
1335            panic!("wrong variant")
1336        };
1337        assert_eq!(s.len(), 256);
1338        assert!(s.bytes().all(|b| b == b'a'));
1339    }
1340
1341    #[test]
1342    fn next_decodes_bytes_five_bytes_vector_0017() {
1343        let bytes = [0x10, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04];
1344        let mut r = TlvReader::new(&bytes);
1345        let el = r.next().unwrap().unwrap();
1346        assert_eq!(
1347            el,
1348            Element::Scalar {
1349                tag: Tag::Anonymous,
1350                value: Value::Bytes(vec![0x00, 0x01, 0x02, 0x03, 0x04]),
1351            }
1352        );
1353    }
1354
1355    #[test]
1356    fn next_decodes_bytes_empty_vector_0018() {
1357        let bytes = [0x10, 0x00];
1358        let mut r = TlvReader::new(&bytes);
1359        let el = r.next().unwrap().unwrap();
1360        assert_eq!(
1361            el,
1362            Element::Scalar {
1363                tag: Tag::Anonymous,
1364                value: Value::Bytes(Vec::new()),
1365            }
1366        );
1367    }
1368
1369    #[test]
1370    fn next_errors_on_invalid_utf8() {
1371        let bytes = [0x0C, 0x01, 0xFF];
1372        let mut r = TlvReader::new(&bytes);
1373        assert!(matches!(r.next(), Err(Error::InvalidUtf8(_))));
1374    }
1375
1376    #[test]
1377    fn next_errors_on_truncated_utf8_payload() {
1378        let bytes = [0x0C, 0x05, b'H', b'i']; // claims 5 bytes, has only 2
1379        let mut r = TlvReader::new(&bytes);
1380        assert!(matches!(r.next(), Err(Error::UnexpectedEof)));
1381    }
1382
1383    // --- Task 4: container event tests ---
1384
1385    #[test]
1386    fn next_decodes_structure_start_and_end_vector_0019() {
1387        let mut r = TlvReader::new(&[0x15, 0x18]);
1388        let el = r.next().unwrap().unwrap();
1389        assert_eq!(
1390            el,
1391            Element::ContainerStart {
1392                tag: Tag::Anonymous,
1393                kind: ContainerKind::Structure,
1394            }
1395        );
1396        let el = r.next().unwrap().unwrap();
1397        assert_eq!(el, Element::ContainerEnd);
1398        assert!(r.next().unwrap().is_none());
1399    }
1400
1401    #[test]
1402    fn next_decodes_array_start_and_end_vector_0020() {
1403        let mut r = TlvReader::new(&[0x16, 0x18]);
1404        assert_eq!(
1405            r.next().unwrap().unwrap(),
1406            Element::ContainerStart {
1407                tag: Tag::Anonymous,
1408                kind: ContainerKind::Array,
1409            }
1410        );
1411        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1412    }
1413
1414    #[test]
1415    fn next_decodes_list_start_and_end() {
1416        let mut r = TlvReader::new(&[0x17, 0x18]);
1417        assert_eq!(
1418            r.next().unwrap().unwrap(),
1419            Element::ContainerStart {
1420                tag: Tag::Anonymous,
1421                kind: ContainerKind::List,
1422            }
1423        );
1424        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1425    }
1426
1427    #[test]
1428    fn next_decodes_structure_with_child_streaming() {
1429        let mut r = TlvReader::new(&[0x15, 0x24, 0x00, 0x2A, 0x18]);
1430        assert_eq!(
1431            r.next().unwrap().unwrap(),
1432            Element::ContainerStart {
1433                tag: Tag::Anonymous,
1434                kind: ContainerKind::Structure,
1435            }
1436        );
1437        assert_eq!(
1438            r.next().unwrap().unwrap(),
1439            Element::Scalar {
1440                tag: Tag::Context(0),
1441                value: Value::Uint(42),
1442            }
1443        );
1444        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1445        assert!(r.next().unwrap().is_none());
1446    }
1447
1448    #[test]
1449    fn next_errors_on_end_of_container_at_top_level() {
1450        let mut r = TlvReader::new(&[0x18]);
1451        assert!(matches!(r.next(), Err(Error::UnexpectedEndOfContainer)));
1452    }
1453
1454    #[test]
1455    fn next_errors_on_end_of_container_with_non_anonymous_tag_form() {
1456        // 0x38 = context-tag form (0b001) | END_OF_CONTAINER (0x18).
1457        let mut r = TlvReader::new(&[0x38, 0x05]);
1458        assert!(matches!(r.next(), Err(Error::InvalidTagControl(_))));
1459    }
1460
1461    #[test]
1462    fn next_errors_on_excessive_nesting() {
1463        let bytes: Vec<u8> = std::iter::repeat_n(0x15u8, 33).collect();
1464        let mut r = TlvReader::new(&bytes);
1465        for _ in 0..32 {
1466            assert!(matches!(
1467                r.next().unwrap().unwrap(),
1468                Element::ContainerStart {
1469                    kind: ContainerKind::Structure,
1470                    ..
1471                },
1472            ));
1473        }
1474        assert!(matches!(r.next(), Err(Error::ContainerTooDeep)));
1475    }
1476
1477    #[test]
1478    fn depth_returns_to_zero_after_balanced_close() {
1479        // Two readers — one balanced (depth returns to 0), then check that
1480        // a fresh reader sees 0x18 at top level as UnexpectedEndOfContainer.
1481        {
1482            let mut r = TlvReader::new(&[0x15, 0x18]);
1483            let _ = r.next(); // ContainerStart → depth = 1
1484            let _ = r.next(); // ContainerEnd → depth = 0
1485        }
1486        let mut r2 = TlvReader::new(&[0x18]);
1487        assert!(matches!(r2.next(), Err(Error::UnexpectedEndOfContainer)));
1488    }
1489
1490    // --- Task 5: read_value tree builder tests ---
1491
1492    #[test]
1493    fn read_value_returns_empty_structure_vector_0019() {
1494        let mut r = TlvReader::new(&[0x15, 0x18]);
1495        let (tag, value) = r.read_value().unwrap();
1496        assert_eq!(tag, Tag::Anonymous);
1497        assert_eq!(value, Value::Structure(Vec::new()));
1498    }
1499
1500    #[test]
1501    fn read_value_returns_empty_array_vector_0020() {
1502        let mut r = TlvReader::new(&[0x16, 0x18]);
1503        let (tag, value) = r.read_value().unwrap();
1504        assert_eq!(tag, Tag::Anonymous);
1505        assert_eq!(value, Value::Array(Vec::new()));
1506    }
1507
1508    #[test]
1509    fn read_value_returns_structure_with_ctx_member_vector_0021() {
1510        let mut r = TlvReader::new(&[0x15, 0x24, 0x00, 0x2A, 0x18]);
1511        let (tag, value) = r.read_value().unwrap();
1512        assert_eq!(tag, Tag::Anonymous);
1513        assert_eq!(
1514            value,
1515            Value::Structure(vec![(Tag::Context(0), Value::Uint(42))])
1516        );
1517    }
1518
1519    #[test]
1520    fn read_value_returns_array_of_three_uint8_vector_0022() {
1521        let mut r = TlvReader::new(&[0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18]);
1522        let (tag, value) = r.read_value().unwrap();
1523        assert_eq!(tag, Tag::Anonymous);
1524        assert_eq!(
1525            value,
1526            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1527        );
1528    }
1529
1530    #[test]
1531    fn read_value_returns_structure_with_bool_at_ctx7_vector_0023() {
1532        let mut r = TlvReader::new(&[0x15, 0x29, 0x07, 0x18]);
1533        let (tag, value) = r.read_value().unwrap();
1534        assert_eq!(tag, Tag::Anonymous);
1535        assert_eq!(
1536            value,
1537            Value::Structure(vec![(Tag::Context(7), Value::Bool(true))])
1538        );
1539    }
1540
1541    #[test]
1542    fn read_value_returns_empty_list() {
1543        let mut r = TlvReader::new(&[0x17, 0x18]);
1544        let (tag, value) = r.read_value().unwrap();
1545        assert_eq!(tag, Tag::Anonymous);
1546        assert_eq!(value, Value::List(Vec::new()));
1547    }
1548
1549    #[test]
1550    fn read_value_handles_nested_structure() {
1551        let mut r = TlvReader::new(&[0x15, 0x35, 0x00, 0x24, 0x00, 0x2A, 0x18, 0x18]);
1552        let (tag, value) = r.read_value().unwrap();
1553        assert_eq!(tag, Tag::Anonymous);
1554        let inner = Value::Structure(vec![(Tag::Context(0), Value::Uint(42))]);
1555        let outer = Value::Structure(vec![(Tag::Context(0), inner)]);
1556        assert_eq!(value, outer);
1557    }
1558
1559    #[test]
1560    fn read_value_errors_on_unclosed_container() {
1561        let mut r = TlvReader::new(&[0x15]);
1562        assert!(matches!(r.read_value(), Err(Error::UnclosedContainer)));
1563    }
1564
1565    #[test]
1566    fn read_value_errors_on_dangling_end_of_container() {
1567        let mut r = TlvReader::new(&[0x18]);
1568        assert!(matches!(
1569            r.read_value(),
1570            Err(Error::UnexpectedEndOfContainer)
1571        ));
1572    }
1573
1574    // --- Task 19: fail-closed array tags, budget, conversion error ---
1575
1576    #[test]
1577    fn read_value_rejects_array_with_context_tagged_child() {
1578        // 0x16 array-start, 0x24 0x00 0x2A = ctx(0) uint8=42, 0x18 end.
1579        // The child carries a context tag, which the spec forbids inside an
1580        // array. Pre-fix the decoder silently dropped the tag; now it errors.
1581        let mut r = TlvReader::new(&[0x16, 0x24, 0x00, 0x2A, 0x18]);
1582        assert!(matches!(r.read_value(), Err(Error::NonAnonymousArrayTag)));
1583    }
1584
1585    #[test]
1586    fn read_value_rejects_array_with_context_tagged_container_child() {
1587        // 0x16 array-start, 0x35 0x00 = ctx(0) struct-start, 0x18 inner end,
1588        // 0x18 outer end. The nested container child is context-tagged.
1589        let mut r = TlvReader::new(&[0x16, 0x35, 0x00, 0x18, 0x18]);
1590        assert!(matches!(r.read_value(), Err(Error::NonAnonymousArrayTag)));
1591    }
1592
1593    #[test]
1594    fn read_value_accepts_array_with_anonymous_children() {
1595        // Sanity: a well-formed array still decodes (regression guard for the
1596        // fail-closed change).
1597        let mut r = TlvReader::new(&[0x16, 0x04, 0x01, 0x04, 0x02, 0x18]);
1598        let (tag, value) = r.read_value().unwrap();
1599        assert_eq!(tag, Tag::Anonymous);
1600        assert_eq!(value, Value::Array(vec![Value::Uint(1), Value::Uint(2)]));
1601    }
1602
1603    #[test]
1604    fn read_value_errors_when_element_budget_is_exceeded() {
1605        // Array of three uint8 children = 4 elements total (array + 3 scalars).
1606        // A budget of 3 cannot fit them. The 8-byte input is larger than the
1607        // budget, so this takes the CHARGED path (the fast path is provably
1608        // unreachable for a violating input: more elements than budget implies
1609        // more input bytes than budget).
1610        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1611        let mut r = TlvReader::with_element_budget(&bytes, 3);
1612        assert!(matches!(r.read_value(), Err(Error::ElementBudgetExceeded)));
1613    }
1614
1615    #[test]
1616    fn read_value_fast_path_at_budget_equal_to_input_len() {
1617        // Boundary of the uncharged fast path: remaining input (8 bytes) equal
1618        // to the budget — the byte bound proves the 4 materialised elements
1619        // cannot exceed it, so the decode succeeds without accounting.
1620        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1621        let mut r = TlvReader::with_element_budget(&bytes, bytes.len());
1622        let (_, value) = r.read_value().unwrap();
1623        assert_eq!(
1624            value,
1625            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1626        );
1627    }
1628
1629    #[test]
1630    fn read_value_charged_path_at_budget_one_below_input_len() {
1631        // One below the fast-path boundary: input (8 bytes) > budget (7) takes
1632        // the charged path, which still admits the 4-element tree.
1633        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1634        let mut r = TlvReader::with_element_budget(&bytes, 7);
1635        let (_, value) = r.read_value().unwrap();
1636        assert_eq!(
1637            value,
1638            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1639        );
1640    }
1641
1642    #[test]
1643    fn read_value_succeeds_at_exactly_the_element_budget() {
1644        // Same input, budget of exactly 4, decodes fine.
1645        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1646        let mut r = TlvReader::with_element_budget(&bytes, 4);
1647        let (_, value) = r.read_value().unwrap();
1648        assert_eq!(
1649            value,
1650            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1651        );
1652    }
1653
1654    #[test]
1655    fn read_value_budget_counts_a_single_scalar() {
1656        // A lone scalar costs one element; a zero budget rejects it.
1657        let mut r = TlvReader::with_element_budget(&[0x04, 0x2A], 0);
1658        assert!(matches!(r.read_value(), Err(Error::ElementBudgetExceeded)));
1659        let mut r = TlvReader::with_element_budget(&[0x04, 0x2A], 1);
1660        assert_eq!(r.read_value().unwrap(), (Tag::Anonymous, Value::Uint(42)));
1661    }
1662
1663    #[test]
1664    fn fixed_width_int_decode_still_works() {
1665        // Guard that the InternalSliceConversion relabel did not change the
1666        // happy path for a fixed-width int. The conversion branch itself is
1667        // unreachable: `next_bytes(N)` returns exactly N bytes or `UnexpectedEof`
1668        // first, so the `try_into::<[u8; N]>` can never fail.
1669        let mut r = TlvReader::new(&[0x05, 0x34, 0x12]);
1670        assert_eq!(
1671            r.next().unwrap().unwrap(),
1672            Element::Scalar {
1673                tag: Tag::Anonymous,
1674                value: Value::Uint(0x1234),
1675            }
1676        );
1677    }
1678
1679    // --- skip_container ----------------------------------------------------
1680
1681    /// Build an anonymous structure: ctx0=u(7), then a nested ctx9 struct
1682    /// {ctx0=u(1)}, then ctx1=u(42). Returns the encoded bytes.
1683    fn struct_with_nested() -> Vec<u8> {
1684        let mut buf = Vec::new();
1685        let mut w = crate::writer::TlvWriter::new(&mut buf);
1686        w.start_structure(Tag::Anonymous).unwrap();
1687        w.put_uint(Tag::Context(0), 7).unwrap();
1688        w.start_structure(Tag::Context(9)).unwrap();
1689        w.put_uint(Tag::Context(0), 1).unwrap();
1690        w.end_container().unwrap();
1691        w.put_uint(Tag::Context(1), 42).unwrap();
1692        w.end_container().unwrap();
1693        buf
1694    }
1695
1696    #[test]
1697    fn read_utf8_truncates_at_is1_separator() {
1698        // CODEC-1 / chip TestTLV.cpp CheckTLVCharSpan: a Matter char string is
1699        // presented as only the text before the first IS1 (0x1F) localized-
1700        // string separator. The writer keeps the raw bytes on the wire.
1701        fn decode_str(s: &str) -> String {
1702            let mut buf = Vec::new();
1703            let mut w = crate::writer::TlvWriter::new(&mut buf);
1704            w.put_utf8(Tag::Anonymous, s).unwrap();
1705            match TlvReader::new(&buf).next().unwrap().unwrap() {
1706                Element::Scalar {
1707                    value: Value::Utf8(t),
1708                    ..
1709                } => t,
1710                other => panic!("expected Utf8 scalar, got {other:?}"),
1711            }
1712        }
1713        // chip's two vectors: text before the separator is returned; a string
1714        // that STARTS with the separator presents as empty.
1715        assert_eq!(
1716            decode_str("This is a test case #1\u{1F}suffix"),
1717            "This is a test case #1"
1718        );
1719        assert_eq!(decode_str("\u{1F} abc \u{1F} def"), "");
1720        // No separator → unchanged; a real localized-label shape → just the text.
1721        assert_eq!(decode_str("Kitchen"), "Kitchen");
1722        assert_eq!(decode_str("Kitchen\u{1F}0409"), "Kitchen");
1723    }
1724
1725    #[test]
1726    fn skip_container_drains_nested_struct_and_positions_after() {
1727        let buf = struct_with_nested();
1728        let mut r = TlvReader::new(&buf);
1729        // open the outer struct
1730        assert!(matches!(
1731            r.next().unwrap(),
1732            Some(Element::ContainerStart {
1733                kind: ContainerKind::Structure,
1734                ..
1735            })
1736        ));
1737        // consume ctx0=7
1738        assert!(matches!(r.next().unwrap(), Some(Element::Scalar { .. })));
1739        // the next element is the nested ctx9 struct — open then skip it
1740        assert!(matches!(
1741            r.next().unwrap(),
1742            Some(Element::ContainerStart {
1743                kind: ContainerKind::Structure,
1744                ..
1745            })
1746        ));
1747        r.skip_container().unwrap();
1748        // reader must now be positioned at ctx1=42, NOT at the outer end
1749        match r.next().unwrap() {
1750            Some(Element::Scalar {
1751                tag: Tag::Context(1),
1752                value: Value::Uint(v),
1753            }) => {
1754                assert_eq!(v, 42);
1755            }
1756            other => panic!("expected ctx1=42 after skip, got {other:?}"),
1757        }
1758        // then the outer ContainerEnd, then None
1759        assert!(matches!(r.next().unwrap(), Some(Element::ContainerEnd)));
1760        assert!(r.next().unwrap().is_none());
1761    }
1762
1763    #[test]
1764    fn skip_container_handles_array_and_list_and_empty() {
1765        for kind_byte in ["array", "list", "empty"] {
1766            let mut buf = Vec::new();
1767            let mut w = crate::writer::TlvWriter::new(&mut buf);
1768            w.start_structure(Tag::Anonymous).unwrap();
1769            match kind_byte {
1770                "array" => {
1771                    w.start_array(Tag::Context(0)).unwrap();
1772                    w.put_uint(Tag::Anonymous, 1).unwrap();
1773                    w.put_uint(Tag::Anonymous, 2).unwrap();
1774                    w.end_container().unwrap();
1775                }
1776                "list" => {
1777                    w.start_list(Tag::Context(0)).unwrap();
1778                    w.put_uint(Tag::Context(5), 9).unwrap();
1779                    w.end_container().unwrap();
1780                }
1781                _ => {
1782                    w.start_structure(Tag::Context(0)).unwrap();
1783                    w.end_container().unwrap();
1784                }
1785            }
1786            w.put_uint(Tag::Context(1), 99).unwrap();
1787            w.end_container().unwrap();
1788
1789            let mut r = TlvReader::new(&buf);
1790            assert!(matches!(
1791                r.next().unwrap(),
1792                Some(Element::ContainerStart { .. })
1793            ));
1794            assert!(matches!(
1795                r.next().unwrap(),
1796                Some(Element::ContainerStart { .. })
1797            ));
1798            r.skip_container().unwrap();
1799            match r.next().unwrap() {
1800                Some(Element::Scalar {
1801                    tag: Tag::Context(1),
1802                    value: Value::Uint(v),
1803                }) => {
1804                    assert_eq!(v, 99, "kind {kind_byte}");
1805                }
1806                other => panic!("kind {kind_byte}: expected ctx1=99, got {other:?}"),
1807            }
1808        }
1809    }
1810
1811    #[test]
1812    fn skip_container_unclosed_is_error() {
1813        // outer struct opened, nested struct opened but never closed (truncated)
1814        let mut buf = Vec::new();
1815        {
1816            let mut w = crate::writer::TlvWriter::new(&mut buf);
1817            w.start_structure(Tag::Anonymous).unwrap();
1818            w.start_structure(Tag::Context(0)).unwrap();
1819            w.put_uint(Tag::Anonymous, 1).unwrap();
1820            // deliberately do NOT close either container
1821        }
1822        let mut r = TlvReader::new(&buf);
1823        assert!(matches!(
1824            r.next().unwrap(),
1825            Some(Element::ContainerStart { .. })
1826        ));
1827        assert!(matches!(
1828            r.next().unwrap(),
1829            Some(Element::ContainerStart { .. })
1830        ));
1831        assert!(matches!(r.skip_container(), Err(Error::UnclosedContainer)));
1832    }
1833
1834    // --- Task 4.1: raw-walk skip_container ---
1835
1836    #[test]
1837    fn skip_container_does_not_validate_skipped_utf8() {
1838        // Outer anon struct { ctx9 struct { utf8 with invalid byte } , ctx1=42 }.
1839        // Hand-assembled because the writer cannot emit invalid UTF-8.
1840        // 0x15                anon struct start
1841        //   0x35 0x09         ctx9 struct start
1842        //     0x0C 0x01 0xFF  utf8 len 1, invalid byte
1843        //   0x18              ctx9 end
1844        //   0x24 0x01 0x2A    ctx1 = uint8 42
1845        // 0x18                outer end
1846        let bytes = [
1847            0x15, 0x35, 0x09, 0x0C, 0x01, 0xFF, 0x18, 0x24, 0x01, 0x2A, 0x18,
1848        ];
1849        let mut r = TlvReader::new(&bytes);
1850        r.next().unwrap(); // outer start
1851        assert!(matches!(
1852            r.next().unwrap(),
1853            Some(Element::ContainerStart { .. })
1854        ));
1855        // Skipped (unobserved) data is NOT UTF-8 validated — deliberate loosening,
1856        // 2026-08-09 perf spec §4.1 (same class as §3.1's non-charged skip).
1857        r.skip_container().unwrap();
1858        assert!(matches!(
1859            r.next().unwrap(),
1860            Some(Element::Scalar {
1861                tag: Tag::Context(1),
1862                value: Value::Uint(42)
1863            })
1864        ));
1865    }
1866
1867    #[test]
1868    fn skip_container_truncated_string_body_is_eof() {
1869        // ctx9 struct containing a utf8 claiming 5 bytes with only 2 present.
1870        let bytes = [0x15, 0x35, 0x09, 0x0C, 0x05, b'H', b'i'];
1871        let mut r = TlvReader::new(&bytes);
1872        r.next().unwrap();
1873        r.next().unwrap();
1874        assert!(matches!(r.skip_container(), Err(Error::UnexpectedEof)));
1875    }
1876
1877    #[test]
1878    fn skip_container_enforces_depth_cap() {
1879        // 33 nested anon struct opens. Open two via next() (reader depth = 2),
1880        // then skip: the 31st open inside the skip sits at effective depth 32
1881        // and must error, exactly as the old next()-driven walk did.
1882        let bytes: Vec<u8> = std::iter::repeat_n(0x15u8, 33).collect();
1883        let mut r = TlvReader::new(&bytes);
1884        r.next().unwrap();
1885        r.next().unwrap();
1886        assert!(matches!(r.skip_container(), Err(Error::ContainerTooDeep)));
1887    }
1888
1889    #[test]
1890    fn skip_container_rejects_tagged_end_marker() {
1891        // 0x38 = context tag-control | END_OF_CONTAINER, invalid per spec.
1892        let bytes = [0x15, 0x35, 0x09, 0x38, 0x05];
1893        let mut r = TlvReader::new(&bytes);
1894        r.next().unwrap();
1895        r.next().unwrap();
1896        assert!(matches!(
1897            r.skip_container(),
1898            Err(Error::InvalidTagControl(_))
1899        ));
1900    }
1901
1902    #[test]
1903    fn skip_container_misuse_at_top_level_errors() {
1904        // No container open: the walk must fail with the same error next() gives
1905        // for a stray end marker, and must not underflow the depth counter.
1906        let mut r = TlvReader::new(&[0x18]);
1907        assert!(matches!(
1908            r.skip_container(),
1909            Err(Error::UnexpectedEndOfContainer)
1910        ));
1911    }
1912
1913    // --- Task 4 (perf phase 4): next_ref zero-copy core ---
1914
1915    #[test]
1916    fn next_ref_borrows_utf8_and_bytes() {
1917        let mut buf = Vec::new();
1918        let mut w = crate::writer::TlvWriter::new(&mut buf);
1919        w.put_utf8(Tag::Context(1), "Kitchen\u{1F}0409").unwrap();
1920        w.put_bytes(Tag::Context(2), &[0xDE, 0xAD]).unwrap();
1921        let mut r = TlvReader::new(&buf);
1922        match r.next_ref().unwrap().unwrap() {
1923            ElementRef::Scalar {
1924                tag: Tag::Context(1),
1925                value: ValueRef::Utf8(s),
1926            } => {
1927                // Same IS1 truncation contract as the owned path.
1928                assert_eq!(s, "Kitchen");
1929            }
1930            other => panic!("expected borrowed Utf8, got {other:?}"),
1931        }
1932        match r.next_ref().unwrap().unwrap() {
1933            ElementRef::Scalar {
1934                tag: Tag::Context(2),
1935                value: ValueRef::Bytes(b),
1936            } => {
1937                assert_eq!(b, &[0xDE, 0xAD]);
1938            }
1939            other => panic!("expected borrowed Bytes, got {other:?}"),
1940        }
1941    }
1942
1943    #[test]
1944    fn value_ref_converts_to_owned_value() {
1945        assert_eq!(
1946            Value::from(ValueRef::Utf8("hi")),
1947            Value::Utf8(String::from("hi"))
1948        );
1949        assert_eq!(
1950            Value::from(ValueRef::Bytes(&[1, 2])),
1951            Value::Bytes(vec![1, 2])
1952        );
1953        assert_eq!(Value::from(ValueRef::Uint(7)), Value::Uint(7));
1954        assert_eq!(Value::from(ValueRef::Null), Value::Null);
1955    }
1956
1957    // --- Task 4.4b: element spans ---
1958
1959    #[test]
1960    fn scalar_element_span_covers_full_element_and_body_excludes_tag() {
1961        // ctx5 uint8=42 → bytes [0x24, 0x05, 0x2A]; body = payload only.
1962        let bytes = [0x24, 0x05, 0x2A];
1963        let mut r = TlvReader::new(&bytes);
1964        assert!(r.element_span().is_none(), "no element returned yet");
1965        r.next().unwrap().unwrap();
1966        let span = r.element_span().unwrap();
1967        assert_eq!(span.full(), 0..3);
1968        assert_eq!(span.body(), 2..3);
1969        assert_eq!(r.span_bytes(span.full()), &bytes[..]);
1970        assert_eq!(r.span_bytes(span.body()), &[0x2A]);
1971    }
1972
1973    #[test]
1974    fn string_span_body_includes_length_field() {
1975        // anon utf8 "Hi" → [0x0C, 0x02, b'H', b'i']; body = len field + payload.
1976        let bytes = [0x0C, 0x02, b'H', b'i'];
1977        let mut r = TlvReader::new(&bytes);
1978        r.next().unwrap().unwrap();
1979        let span = r.element_span().unwrap();
1980        assert_eq!(span.full(), 0..4);
1981        assert_eq!(span.body(), 1..4);
1982    }
1983
1984    #[test]
1985    fn skip_container_span_covers_container_and_body_excludes_header() {
1986        // outer anon struct { ctx9 struct { ctx1: uint8 0x2A } } sentinel.
1987        let buf = {
1988            let mut b = Vec::new();
1989            let mut w = crate::writer::TlvWriter::new(&mut b);
1990            w.start_structure(Tag::Anonymous).unwrap();
1991            w.start_structure(Tag::Context(9)).unwrap();
1992            w.put_uint(Tag::Context(1), 0x2A).unwrap();
1993            w.end_container().unwrap();
1994            w.put_uint(Tag::Context(2), 7).unwrap();
1995            w.end_container().unwrap();
1996            b
1997        };
1998        // bytes: 0x15 | 0x35 0x09 | 0x24 0x01 0x2A | 0x18 | 0x24 0x02 0x07 | 0x18
1999        let mut r = TlvReader::new(&buf);
2000        r.next().unwrap(); // outer start
2001        r.next().unwrap(); // ctx9 start (header span only at this point)
2002        let header = r.element_span().unwrap();
2003        assert_eq!(header.full(), 1..3, "header-only span after ContainerStart");
2004        let span = r.skip_container_span().unwrap();
2005        assert_eq!(span.full(), 1..7, "control+tag+children+end marker");
2006        // body EXCLUDES the container's control/tag bytes, INCLUDES its end.
2007        assert_eq!(r.span_bytes(span.body()), &[0x24, 0x01, 0x2A, 0x18]);
2008        // reader continues at the sentinel
2009        assert!(matches!(
2010            r.next().unwrap(),
2011            Some(Element::Scalar {
2012                tag: Tag::Context(2),
2013                value: Value::Uint(7)
2014            })
2015        ));
2016    }
2017
2018    #[test]
2019    fn retag_reemission_from_span_matches_writer_output() {
2020        // The 4.5 adoption pattern: copy a ctx-tagged container's body under a
2021        // fresh anonymous header. Must equal a writer-built anonymous
2022        // equivalent byte-for-byte (proves the original tag byte 0x09 is
2023        // excluded), and must preserve non-minimal widths in the body.
2024        // Hand-assembled: ctx9 struct { ctx0: uint16-encoded 42 } — the codec
2025        // writer would emit uint8, so bytes are assembled manually.
2026        let bytes = [0x35, 0x09, 0x25, 0x00, 0x2A, 0x00, 0x18];
2027        let mut r = TlvReader::new(&bytes);
2028        assert!(matches!(
2029            r.next().unwrap(),
2030            Some(Element::ContainerStart {
2031                kind: ContainerKind::Structure,
2032                ..
2033            })
2034        ));
2035        let span = r.skip_container_span().unwrap();
2036        let mut out = Vec::new();
2037        {
2038            let mut w = crate::writer::TlvWriter::new(&mut out);
2039            w.start_structure(Tag::Anonymous).unwrap();
2040        }
2041        out.extend_from_slice(r.span_bytes(span.body()));
2042        // Anonymous struct, SAME body bytes: uint16 width preserved, tag gone.
2043        assert_eq!(out, [0x15, 0x25, 0x00, 0x2A, 0x00, 0x18]);
2044    }
2045
2046    #[test]
2047    fn skip_container_span_after_scalar_is_rejected() {
2048        // anon struct { ctx1: uint8 0x2A }: position the reader on the SCALAR,
2049        // then ask for a container span. Without the precondition guard this
2050        // returned Ok with a span over the scalar's bytes, which a retag caller
2051        // would happily re-emit as malformed TLV.
2052        let bytes = [0x15, 0x24, 0x01, 0x2A, 0x18];
2053        let mut r = TlvReader::new(&bytes);
2054        r.next().unwrap(); // ContainerStart
2055        r.next().unwrap(); // Scalar ctx1
2056        assert!(matches!(
2057            r.skip_container_span(),
2058            Err(Error::UnexpectedEndOfContainer)
2059        ));
2060        // Rejected without consuming: the struct's end marker is still there.
2061        assert!(matches!(r.next().unwrap(), Some(Element::ContainerEnd)));
2062    }
2063
2064    #[test]
2065    fn skip_container_span_before_any_element_is_rejected() {
2066        let mut r = TlvReader::new(&[0x15, 0x18]);
2067        assert!(matches!(
2068            r.skip_container_span(),
2069            Err(Error::UnexpectedEndOfContainer)
2070        ));
2071    }
2072
2073    #[test]
2074    fn skip_container_span_twice_is_rejected() {
2075        // A successful skip consumes the container, so the flag must clear:
2076        // a second call has no ContainerStart to span.
2077        let bytes = [0x35, 0x09, 0x24, 0x01, 0x2A, 0x18];
2078        let mut r = TlvReader::new(&bytes);
2079        r.next().unwrap(); // ctx9 ContainerStart
2080        assert!(r.skip_container_span().is_ok());
2081        assert!(matches!(
2082            r.skip_container_span(),
2083            Err(Error::UnexpectedEndOfContainer)
2084        ));
2085    }
2086
2087    #[test]
2088    fn skip_container_span_after_plain_skip_is_rejected() {
2089        // `skip_container` also consumes the open container and so clears the
2090        // flag, even though it keeps its own (unguarded) contract.
2091        let bytes = [0x15, 0x35, 0x09, 0x18, 0x24, 0x02, 0x07, 0x18];
2092        let mut r = TlvReader::new(&bytes);
2093        r.next().unwrap(); // outer ContainerStart
2094        r.next().unwrap(); // ctx9 ContainerStart
2095        r.skip_container().unwrap();
2096        assert!(matches!(
2097            r.skip_container_span(),
2098            Err(Error::UnexpectedEndOfContainer)
2099        ));
2100    }
2101
2102    #[test]
2103    fn span_bytes_out_of_range_returns_empty() {
2104        let r = TlvReader::new(&[0x14]);
2105        assert_eq!(r.span_bytes(5..9), &[] as &[u8]);
2106    }
2107
2108    // --- Phase 5 hygiene: next/next_ref parity, span/depth pins ---
2109
2110    /// `next()` is documented as `next_ref().map(Element::from)`. Pin that the
2111    /// two agree — value AND error — over hostile inputs, element by element.
2112    /// Errors don't all derive `PartialEq`, so compare Debug renderings.
2113    #[test]
2114    fn next_and_next_ref_agree_on_hostile_inputs() {
2115        // 0x0C = anonymous UTF-8 string, 1-byte length.
2116        let cases: &[(&[u8], &str)] = &[
2117            (
2118                &[0x0C, 0x03, 0x1F, 0x61, 0x62],
2119                "IS1 at index 0 -> empty string",
2120            ),
2121            (&[0x0C, 0x01, 0x1F], "IS1 only -> empty string"),
2122            (&[0x0C, 0x03, 0x61, 0x1F, 0xFF], "invalid UTF-8 after IS1"),
2123            (&[0x0C, 0x05, 0x61, 0x62], "truncated string payload"),
2124            (&[0x24, 0x01], "truncated scalar payload"),
2125            (&[0x18], "stray end-of-container"),
2126            (&[0x1F, 0x00], "invalid element type code"),
2127        ];
2128        for (bytes, what) in cases {
2129            let mut a = TlvReader::new(bytes);
2130            let mut b = TlvReader::new(bytes);
2131            loop {
2132                let ra = a.next();
2133                let rb = b.next_ref().map(|o| o.map(Element::from));
2134                assert_eq!(
2135                    format!("{ra:?}"),
2136                    format!("{rb:?}"),
2137                    "next vs next_ref diverged on: {what}"
2138                );
2139                match ra {
2140                    Ok(Some(_)) => {}
2141                    _ => break, // Ok(None) or Err: both readers are done.
2142                }
2143            }
2144        }
2145    }
2146
2147    /// `next_ref`'s payloads borrow from the INPUT (`'a`), not from the
2148    /// reader borrow — two `ElementRef`s from successive calls coexist.
2149    #[test]
2150    fn next_ref_payloads_borrow_from_input_across_calls() {
2151        let mut buf = Vec::new();
2152        {
2153            let mut w = crate::writer::TlvWriter::new(&mut buf);
2154            w.put_utf8(Tag::Context(0), "abc").unwrap();
2155            w.put_bytes(Tag::Context(1), b"xyz").unwrap();
2156        }
2157        let mut r = TlvReader::new(&buf);
2158        let first = r.next_ref().unwrap().unwrap();
2159        let second = r.next_ref().unwrap().unwrap();
2160        // Both alive here: the 'a lifetime outlives the &mut self calls.
2161        let (
2162            ElementRef::Scalar {
2163                value: ValueRef::Utf8(a),
2164                ..
2165            },
2166            ElementRef::Scalar {
2167                value: ValueRef::Bytes(b),
2168                ..
2169            },
2170        ) = (first, second)
2171        else {
2172            panic!("unexpected shapes: {first:?} / {second:?}");
2173        };
2174        assert_eq!((a, b), ("abc", &b"xyz"[..]));
2175    }
2176
2177    /// Plain `skip_container()` (not just the `_span` variant) must update
2178    /// `element_span()` to the WHOLE skipped container.
2179    #[test]
2180    fn plain_skip_container_updates_element_span_to_full_container() {
2181        // struct_with_nested() bytes:
2182        //   0: 0x15            outer struct start
2183        // 1-3: 0x24 0x00 0x07  ctx0 = 7
2184        // 4-5: 0x35 0x09       ctx9 struct start
2185        // 6-8: 0x24 0x00 0x01  inner ctx0 = 1
2186        //   9: 0x18            inner end
2187        // 10-12: 0x24 0x01 0x2A  ctx1 = 42
2188        //  13: 0x18            outer end
2189        let buf = struct_with_nested();
2190        let mut r = TlvReader::new(&buf);
2191        r.next().unwrap(); // outer start
2192        r.next().unwrap(); // ctx0 scalar
2193        r.next().unwrap(); // ctx9 start — header-only span
2194        assert_eq!(r.element_span().unwrap().full(), 4..6);
2195        r.skip_container().unwrap();
2196        let span = r.element_span().unwrap();
2197        assert_eq!(span.full(), 4..10, "whole container incl. end marker");
2198        assert_eq!(r.span_bytes(span.body()), &[0x24, 0x00, 0x01, 0x18]);
2199    }
2200
2201    /// After a successful skip, `self.depth` is rebalanced: the outer close
2202    /// is consumed normally and a STRAY trailing 0x18 errors instead of
2203    /// closing a phantom container.
2204    #[test]
2205    fn depth_rebalanced_after_skip_rejects_stray_end_marker() {
2206        // outer struct { ctx9 struct {} } , outer end, then a stray end.
2207        let bytes = [0x15, 0x35, 0x09, 0x18, 0x18, 0x18];
2208        let mut r = TlvReader::new(&bytes);
2209        r.next().unwrap(); // outer start (depth 1)
2210        r.next().unwrap(); // ctx9 start (depth 2)
2211        r.skip_container().unwrap(); // must leave depth == 1
2212        assert!(matches!(r.next(), Ok(Some(Element::ContainerEnd)))); // outer close
2213        assert!(matches!(r.next(), Err(Error::UnexpectedEndOfContainer)));
2214    }
2215}