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.
8
9use crate::error::{Error, Result};
10use crate::tag::Tag;
11use crate::value::Value;
12use crate::{element_type as et, tag_control as tc};
13
14/// Which kind of TLV container a [`ContainerStart`](Element::ContainerStart)
15/// announces.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum ContainerKind {
19    /// `Value::Structure` — children carry their own (typically context-tagged) tags.
20    Structure,
21    /// `Value::Array` — children carry anonymous tags only.
22    Array,
23    /// `Value::List` — children may carry any tag form.
24    List,
25}
26
27/// One step of the streaming reader.
28#[derive(Debug, Clone, PartialEq)]
29#[non_exhaustive]
30pub enum Element {
31    /// A complete scalar (or string/bytes) element.
32    Scalar {
33        /// The tag that identifies this element within its enclosing context.
34        tag: Tag,
35        /// The decoded scalar value.
36        value: Value,
37    },
38
39    /// A container has just been opened. Subsequent `next()` calls
40    /// return the container's children until a matching
41    /// [`ContainerEnd`](Element::ContainerEnd).
42    ContainerStart {
43        /// The tag that identifies this container within its enclosing context.
44        tag: Tag,
45        /// Which container kind was opened.
46        kind: ContainerKind,
47    },
48
49    /// The most recently-opened container has been closed.
50    ContainerEnd,
51}
52
53/// Maximum container nesting depth the reader accepts. The Matter spec
54/// recommends a 32-level limit to prevent stack blow-up on adversarial
55/// input.
56pub const MAX_DEPTH: usize = 32;
57
58/// Default ceiling on the total number of [`Value`] elements a single
59/// [`TlvReader::read_value`] call may materialise.
60///
61/// Tree-builder decoding allocates one `Value` (and, inside containers, one
62/// `(Tag, Value)` pair) per element. A tiny scalar such as a boolean costs a
63/// single wire byte but expands to a heap-resident `Value`, so an input packed
64/// with millions of one-byte scalars can amplify into a large allocation. This
65/// budget bounds that amplification: a decode that would produce more than this
66/// many elements fails with [`Error::ElementBudgetExceeded`].
67///
68/// The default is deliberately generous (1,048,576 elements) — far above any
69/// legitimate Matter payload, which is itself bounded by the protocol's
70/// message-size limits — so it only ever trips on adversarial input. Callers
71/// that need a tighter or looser bound can set their own via
72/// [`TlvReader::with_element_budget`].
73pub const DEFAULT_ELEMENT_BUDGET: usize = 1 << 20;
74
75/// A streaming TLV decoder over a borrowed byte slice.
76pub struct TlvReader<'a> {
77    bytes: &'a [u8],
78    pos: usize,
79    depth: usize,
80    /// Remaining element budget for a tree-builder decode. Decremented once
81    /// per materialised [`Value`] in [`Self::read_container_body`] /
82    /// [`Self::read_value`] — but only on *charged* decodes, i.e. when the
83    /// remaining input is larger than this budget; a smaller input provably
84    /// cannot exceed it (each element consumes ≥ 1 input byte), so the fast
85    /// path skips accounting without weakening the bound (see
86    /// [`Self::read_value`]). The streaming [`Self::next`] path does not
87    /// touch it because it allocates nothing per element.
88    element_budget: usize,
89}
90
91impl<'a> TlvReader<'a> {
92    /// Construct a reader that walks `bytes` from the start, using the
93    /// [`DEFAULT_ELEMENT_BUDGET`] for tree-builder decodes.
94    pub fn new(bytes: &'a [u8]) -> Self {
95        Self {
96            bytes,
97            pos: 0,
98            depth: 0,
99            element_budget: DEFAULT_ELEMENT_BUDGET,
100        }
101    }
102
103    /// Construct a reader with a custom total-element budget for tree-builder
104    /// decoding (see [`DEFAULT_ELEMENT_BUDGET`]).
105    ///
106    /// A [`Self::read_value`] call that would materialise more than `budget`
107    /// [`Value`] elements fails with [`Error::ElementBudgetExceeded`]. The
108    /// budget only affects the tree-builder path; the streaming [`Self::next`]
109    /// API is unaffected because it allocates nothing per element.
110    pub fn with_element_budget(bytes: &'a [u8], budget: usize) -> Self {
111        Self {
112            bytes,
113            pos: 0,
114            depth: 0,
115            element_budget: budget,
116        }
117    }
118
119    /// Whether there is no more input to consume.
120    pub fn is_empty(&self) -> bool {
121        self.pos >= self.bytes.len()
122    }
123
124    /// Advance one TLV element. Returns `Ok(None)` at end of input.
125    ///
126    /// # Errors
127    ///
128    /// Returns `Err` if the input is malformed:
129    ///
130    /// - [`Error::InvalidTagControl`] — unrecognised tag-control byte form.
131    /// - [`Error::InvalidElementType`] — unknown element-type code.
132    /// - [`Error::UnexpectedEof`] — truncated payload bytes.
133    /// - [`Error::UnexpectedEndOfContainer`] — end-of-container marker (`0x18`)
134    ///   at the top level, with no container open.
135    /// - [`Error::ContainerTooDeep`] — a container open would exceed
136    ///   [`MAX_DEPTH`] nesting levels.
137    ///
138    /// # Note on naming
139    ///
140    /// This method is deliberately named `next` to match the streaming-reader
141    /// idiom established by e.g. `serde`'s `Deserializer`. It returns
142    /// `Result<Option<T>>` rather than `Option<Result<T>>` so that callers
143    /// use `?` naturally. Implementing `std::iter::Iterator` is deferred to
144    /// a later phase when a fallible-iterator adapter is available.
145    #[allow(clippy::should_implement_trait)] // See note above; Iterator requires Option<Item>, not Result<Option<Item>>.
146    pub fn next(&mut self) -> Result<Option<Element>> {
147        if self.is_empty() {
148            return Ok(None);
149        }
150        let control = self.next_byte()?;
151        let elem_type = control & et::ELEMENT_TYPE_MASK;
152
153        // End-of-container is always emitted as anonymous tag form.
154        if elem_type == et::END_OF_CONTAINER {
155            if control & tc::TAG_CONTROL_MASK != tc::ANONYMOUS {
156                return Err(Error::InvalidTagControl(control & tc::TAG_CONTROL_MASK));
157            }
158            if self.depth == 0 {
159                return Err(Error::UnexpectedEndOfContainer);
160            }
161            self.depth -= 1;
162            return Ok(Some(Element::ContainerEnd));
163        }
164
165        let tag = self.read_tag(control)?;
166
167        // Container opens — record the kind and bump depth.
168        let kind = match elem_type {
169            et::STRUCTURE => Some(ContainerKind::Structure),
170            et::ARRAY => Some(ContainerKind::Array),
171            et::LIST => Some(ContainerKind::List),
172            _ => None,
173        };
174        if let Some(kind) = kind {
175            if self.depth >= MAX_DEPTH {
176                return Err(Error::ContainerTooDeep);
177            }
178            self.depth += 1;
179            return Ok(Some(Element::ContainerStart { tag, kind }));
180        }
181
182        let value = self.read_value_body(elem_type)?;
183        Ok(Some(Element::Scalar { tag, value }))
184    }
185
186    /// Skip the remaining body of the container whose
187    /// [`ContainerStart`](Element::ContainerStart) was just returned by
188    /// [`Self::next`], consuming through its matching
189    /// [`ContainerEnd`](Element::ContainerEnd).
190    ///
191    /// Call this immediately after `next()` yields a `ContainerStart` you
192    /// want to discard — for example an unknown field carried by a struct
193    /// from a newer Matter revision. On return the reader is positioned at
194    /// the first element *after* the skipped container. Scalars inside the
195    /// container are walked but not materialised, so cost is bounded by the
196    /// input size and nesting by [`MAX_DEPTH`] (both enforced by `next()`).
197    ///
198    /// # Errors
199    ///
200    /// - [`Error::UnclosedContainer`] — end of input before the container's
201    ///   closing marker.
202    /// - Any error returned by [`Self::next`] (malformed body, over-deep
203    ///   nesting, or element-budget exhaustion).
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter};
209    /// let mut buf = Vec::new();
210    /// let mut w = TlvWriter::new(&mut buf);
211    /// w.start_structure(Tag::Anonymous)?;
212    /// w.start_structure(Tag::Context(9))?; // an unknown nested field
213    /// w.end_container()?;
214    /// w.put_uint(Tag::Context(1), 42)?;
215    /// w.end_container()?;
216    ///
217    /// let mut r = TlvReader::new(&buf);
218    /// r.next()?; // open the outer struct
219    /// // next() returns the nested ctx9 ContainerStart we want to discard:
220    /// assert!(matches!(
221    ///     r.next()?,
222    ///     Some(Element::ContainerStart { kind: ContainerKind::Structure, .. })
223    /// ));
224    /// r.skip_container()?; // drain the nested struct
225    /// // the field after the unknown container is still readable:
226    /// assert!(matches!(r.next()?, Some(Element::Scalar { tag: Tag::Context(1), .. })));
227    /// # Ok::<(), matter_codec::Error>(())
228    /// ```
229    pub fn skip_container(&mut self) -> Result<()> {
230        let mut depth = 1usize;
231        while depth > 0 {
232            match self.next()? {
233                Some(Element::ContainerStart { .. }) => depth += 1,
234                Some(Element::ContainerEnd) => depth -= 1,
235                Some(Element::Scalar { .. }) => {}
236                None => return Err(Error::UnclosedContainer),
237            }
238        }
239        Ok(())
240    }
241
242    /// Materialise one full TLV element as a `(Tag, Value)`. Scalars are
243    /// returned directly; containers are read recursively up to
244    /// [`MAX_DEPTH`] levels (enforced by [`Self::next`]'s depth counter).
245    ///
246    /// # Errors
247    ///
248    /// - [`Error::UnexpectedEof`] — the input is empty.
249    /// - [`Error::UnexpectedEndOfContainer`] — the first element is a stray
250    ///   end-of-container marker.
251    /// - [`Error::UnclosedContainer`] — end of input was reached before the
252    ///   container's closing marker.
253    /// - [`Error::NonAnonymousArrayTag`] — an array child carried a
254    ///   non-anonymous tag (the spec requires array elements to be anonymous).
255    /// - [`Error::ElementBudgetExceeded`] — the decode would materialise more
256    ///   than the configured element budget (see [`DEFAULT_ELEMENT_BUDGET`]).
257    /// - Any error returned by [`Self::next`].
258    pub fn read_value(&mut self) -> Result<(Tag, Value)> {
259        // Budget fast path: every materialised element consumes at least one
260        // input byte (a null is one control byte; a container is a control
261        // byte plus its end marker), so when the remaining input is no larger
262        // than the remaining budget this decode CANNOT exceed it — decode
263        // with per-element accounting compiled out entirely. Real Matter
264        // payloads (≲ 1.5 KiB) against the 2^20 default budget always take
265        // this path; the charged path serves oversized or custom-budget
266        // inputs.
267        //
268        // This is observably equivalent to charging every element: a call is
269        // only admitted uncharged when the byte bound proves it cannot fail,
270        // and from such a call onward every future element from this reader
271        // is covered by the same bound (the remaining input only shrinks), so
272        // sequences of `read_value` calls fail on exactly the same element —
273        // with the same error — as the always-charged implementation. The
274        // lifetime invariant is unchanged: one reader never materialises more
275        // than its configured budget's worth of live `Value` elements.
276        let remaining_input = self.bytes.len().saturating_sub(self.pos);
277        if remaining_input <= self.element_budget {
278            self.read_value_inner::<false>()
279        } else {
280            self.read_value_inner::<true>()
281        }
282    }
283
284    /// Tree-builder body of [`Self::read_value`], monomorphised over whether
285    /// per-element budget accounting is active (see the fast-path comment
286    /// there — `CHARGE == false` is only reachable when the byte bound proves
287    /// the budget cannot be exceeded).
288    fn read_value_inner<const CHARGE: bool>(&mut self) -> Result<(Tag, Value)> {
289        match self.next()? {
290            Some(Element::Scalar { tag, value }) => {
291                if CHARGE {
292                    self.charge_element()?;
293                }
294                Ok((tag, value))
295            }
296            Some(Element::ContainerStart { tag, kind }) => {
297                if CHARGE {
298                    self.charge_element()?;
299                }
300                let value = self.read_container_body::<CHARGE>(kind)?;
301                Ok((tag, value))
302            }
303            Some(Element::ContainerEnd) => Err(Error::UnexpectedEndOfContainer),
304            None => Err(Error::UnexpectedEof),
305        }
306    }
307
308    /// Charge one element against the tree-builder budget. Returns
309    /// [`Error::ElementBudgetExceeded`] once the budget is exhausted. Only
310    /// called on charged decodes ([`Self::read_value`]'s slow path); the
311    /// container loops mirror the same arithmetic in a local for speed (see
312    /// [`Self::read_container_body`]).
313    fn charge_element(&mut self) -> Result<()> {
314        self.element_budget = self
315            .element_budget
316            .checked_sub(1)
317            .ok_or(Error::ElementBudgetExceeded)?;
318        Ok(())
319    }
320
321    /// Decode a container's children into a [`Value`] tree.
322    ///
323    /// # Element-budget accounting (denial-of-service bound)
324    ///
325    /// When `CHARGE` is false (the [`Self::read_value`] fast path: the byte
326    /// bound already proves the budget cannot be exceeded) no accounting code
327    /// is compiled into this copy at all.
328    ///
329    /// When `CHARGE` is true, the check is the hot path of tree-builder
330    /// decoding, so instead of calling [`Self::charge_element`] (a
331    /// read-modify-write of `self.element_budget` that the compiler cannot
332    /// keep in a register across the `self.next()` calls) each loop mirrors
333    /// the remaining budget in a local, charges the local per element, and
334    /// syncs it back to `self.element_budget` around recursion and at
335    /// container close.
336    ///
337    /// The preserved invariant: **a charged decode never materialises more
338    /// than the configured element budget's worth of [`Value`] elements, and
339    /// it fails (`Error::ElementBudgetExceeded`) on exactly the same element
340    /// as a per-element field update would** — every element is still
341    /// individually charged before it is pushed, and the field is up to date
342    /// whenever recursion (the only other consumer) runs. The one observable
343    /// difference is on *error* returns: charges made since the last sync are
344    /// not written back — which cannot weaken the bound, because the failed
345    /// call's partially built tree is dropped with it (the budget bounds live
346    /// memory amplification, and a subsequent `read_value` on the same reader
347    /// starts from a tree of zero live elements).
348    fn read_container_body<const CHARGE: bool>(&mut self, kind: ContainerKind) -> Result<Value> {
349        // Branch on the container kind once, before the loop, so arrays decode
350        // straight into a `Vec<Value>` without first building a `Vec<(Tag,
351        // Value)>` and re-collecting. Structures and lists keep their members'
352        // tags.
353        match kind {
354            ContainerKind::Array => {
355                let mut elements: Vec<Value> = Vec::new();
356                let mut budget = self.element_budget;
357                loop {
358                    match self.next()? {
359                        None => return Err(Error::UnclosedContainer),
360                        Some(Element::ContainerEnd) => break,
361                        Some(Element::Scalar { tag, value }) => {
362                            // Spec: every array element must be anonymous. Fail
363                            // closed on any other tag rather than discarding it.
364                            if tag != Tag::Anonymous {
365                                return Err(Error::NonAnonymousArrayTag);
366                            }
367                            if CHARGE {
368                                budget =
369                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
370                            }
371                            elements.push(value);
372                        }
373                        Some(Element::ContainerStart {
374                            tag,
375                            kind: inner_kind,
376                        }) => {
377                            if tag != Tag::Anonymous {
378                                return Err(Error::NonAnonymousArrayTag);
379                            }
380                            if CHARGE {
381                                budget =
382                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
383                                self.element_budget = budget;
384                            }
385                            elements.push(self.read_container_body::<CHARGE>(inner_kind)?);
386                            if CHARGE {
387                                budget = self.element_budget;
388                            }
389                        }
390                    }
391                }
392                if CHARGE {
393                    self.element_budget = budget;
394                }
395                Ok(Value::Array(elements))
396            }
397            ContainerKind::Structure | ContainerKind::List => {
398                let mut members: Vec<(Tag, Value)> = Vec::new();
399                let mut budget = self.element_budget;
400                loop {
401                    match self.next()? {
402                        None => return Err(Error::UnclosedContainer),
403                        Some(Element::ContainerEnd) => break,
404                        Some(Element::Scalar { tag, value }) => {
405                            if CHARGE {
406                                budget =
407                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
408                            }
409                            members.push((tag, value));
410                        }
411                        Some(Element::ContainerStart {
412                            tag,
413                            kind: inner_kind,
414                        }) => {
415                            if CHARGE {
416                                budget =
417                                    budget.checked_sub(1).ok_or(Error::ElementBudgetExceeded)?;
418                                self.element_budget = budget;
419                            }
420                            let inner = self.read_container_body::<CHARGE>(inner_kind)?;
421                            members.push((tag, inner));
422                            if CHARGE {
423                                budget = self.element_budget;
424                            }
425                        }
426                    }
427                }
428                if CHARGE {
429                    self.element_budget = budget;
430                }
431                Ok(match kind {
432                    ContainerKind::List => Value::List(members),
433                    // The outer match guarantees this arm is Structure.
434                    _ => Value::Structure(members),
435                })
436            }
437        }
438    }
439
440    fn next_byte(&mut self) -> Result<u8> {
441        let b = *self.bytes.get(self.pos).ok_or(Error::UnexpectedEof)?;
442        self.pos += 1;
443        Ok(b)
444    }
445
446    fn next_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
447        let end = self.pos.checked_add(n).ok_or(Error::LengthOverflow)?;
448        let slice = self.bytes.get(self.pos..end).ok_or(Error::UnexpectedEof)?;
449        self.pos = end;
450        Ok(slice)
451    }
452
453    fn read_tag(&mut self, control: u8) -> Result<Tag> {
454        match control & tc::TAG_CONTROL_MASK {
455            tc::ANONYMOUS => Ok(Tag::Anonymous),
456            tc::CONTEXT => {
457                let n = self.next_byte()?;
458                Ok(Tag::Context(n))
459            }
460            tc::COMMON_PROFILE_2 => {
461                let raw: [u8; 2] = self
462                    .next_bytes(2)?
463                    .try_into()
464                    .map_err(|_| Error::InternalSliceConversion)?;
465                Ok(Tag::CommonProfile(u32::from(u16::from_le_bytes(raw))))
466            }
467            tc::COMMON_PROFILE_4 => {
468                let raw: [u8; 4] = self
469                    .next_bytes(4)?
470                    .try_into()
471                    .map_err(|_| Error::InternalSliceConversion)?;
472                Ok(Tag::CommonProfile(u32::from_le_bytes(raw)))
473            }
474            tc::IMPLICIT_PROFILE_2 => {
475                let raw: [u8; 2] = self
476                    .next_bytes(2)?
477                    .try_into()
478                    .map_err(|_| Error::InternalSliceConversion)?;
479                Ok(Tag::ImplicitProfile(u32::from(u16::from_le_bytes(raw))))
480            }
481            tc::IMPLICIT_PROFILE_4 => {
482                let raw: [u8; 4] = self
483                    .next_bytes(4)?
484                    .try_into()
485                    .map_err(|_| Error::InternalSliceConversion)?;
486                Ok(Tag::ImplicitProfile(u32::from_le_bytes(raw)))
487            }
488            tc::FULLY_QUALIFIED_6 => {
489                let vendor = self.read_u16_le()?;
490                let profile = self.read_u16_le()?;
491                let tag = u32::from(self.read_u16_le()?);
492                Ok(Tag::FullyQualified {
493                    vendor,
494                    profile,
495                    tag,
496                })
497            }
498            tc::FULLY_QUALIFIED_8 => {
499                let vendor = self.read_u16_le()?;
500                let profile = self.read_u16_le()?;
501                let tag = self.read_u32_le()?;
502                Ok(Tag::FullyQualified {
503                    vendor,
504                    profile,
505                    tag,
506                })
507            }
508            // The 3-bit tag-control field has only 8 possible values, and
509            // we have arms for all 8. This arm is unreachable in practice
510            // but rustc cannot prove that statically.
511            other => Err(Error::InvalidTagControl(other)),
512        }
513    }
514
515    fn read_u16_le(&mut self) -> Result<u16> {
516        let raw: [u8; 2] = self
517            .next_bytes(2)?
518            .try_into()
519            .map_err(|_| Error::InternalSliceConversion)?;
520        Ok(u16::from_le_bytes(raw))
521    }
522
523    fn read_u32_le(&mut self) -> Result<u32> {
524        let raw: [u8; 4] = self
525            .next_bytes(4)?
526            .try_into()
527            .map_err(|_| Error::InternalSliceConversion)?;
528        Ok(u32::from_le_bytes(raw))
529    }
530
531    #[allow(clippy::cast_possible_wrap)] // `b as i8`: reinterprets the byte pattern as signed, not truncation.
532    fn read_value_body(&mut self, elem_type: u8) -> Result<Value> {
533        match elem_type {
534            et::BOOL_FALSE => Ok(Value::Bool(false)),
535            et::BOOL_TRUE => Ok(Value::Bool(true)),
536            et::NULL => Ok(Value::Null),
537            et::UINT8 => Ok(Value::Uint(u64::from(self.next_byte()?))),
538            et::UINT16 => {
539                let raw: [u8; 2] = self
540                    .next_bytes(2)?
541                    .try_into()
542                    .map_err(|_| Error::InternalSliceConversion)?;
543                Ok(Value::Uint(u64::from(u16::from_le_bytes(raw))))
544            }
545            et::UINT32 => {
546                let raw: [u8; 4] = self
547                    .next_bytes(4)?
548                    .try_into()
549                    .map_err(|_| Error::InternalSliceConversion)?;
550                Ok(Value::Uint(u64::from(u32::from_le_bytes(raw))))
551            }
552            et::UINT64 => {
553                let raw: [u8; 8] = self
554                    .next_bytes(8)?
555                    .try_into()
556                    .map_err(|_| Error::InternalSliceConversion)?;
557                Ok(Value::Uint(u64::from_le_bytes(raw)))
558            }
559            et::INT8 => {
560                let b = self.next_byte()?;
561                Ok(Value::Int(i64::from(b as i8)))
562            }
563            et::INT16 => {
564                let raw: [u8; 2] = self
565                    .next_bytes(2)?
566                    .try_into()
567                    .map_err(|_| Error::InternalSliceConversion)?;
568                Ok(Value::Int(i64::from(i16::from_le_bytes(raw))))
569            }
570            et::INT32 => {
571                let raw: [u8; 4] = self
572                    .next_bytes(4)?
573                    .try_into()
574                    .map_err(|_| Error::InternalSliceConversion)?;
575                Ok(Value::Int(i64::from(i32::from_le_bytes(raw))))
576            }
577            et::INT64 => {
578                let raw: [u8; 8] = self
579                    .next_bytes(8)?
580                    .try_into()
581                    .map_err(|_| Error::InternalSliceConversion)?;
582                Ok(Value::Int(i64::from_le_bytes(raw)))
583            }
584            et::FLOAT32 => {
585                let raw: [u8; 4] = self
586                    .next_bytes(4)?
587                    .try_into()
588                    .map_err(|_| Error::InternalSliceConversion)?;
589                Ok(Value::Float(f32::from_le_bytes(raw)))
590            }
591            et::FLOAT64 => {
592                let raw: [u8; 8] = self
593                    .next_bytes(8)?
594                    .try_into()
595                    .map_err(|_| Error::InternalSliceConversion)?;
596                Ok(Value::Double(f64::from_le_bytes(raw)))
597            }
598            et::UTF8_LEN8 | et::UTF8_LEN16 | et::UTF8_LEN32 | et::UTF8_LEN64 => {
599                let len = self.read_payload_len(elem_type)?;
600                self.read_utf8(len)
601            }
602            et::BYTES_LEN8 | et::BYTES_LEN16 | et::BYTES_LEN32 | et::BYTES_LEN64 => {
603                let len = self.read_payload_len(elem_type)?;
604                self.read_bytes(len)
605            }
606            other => Err(Error::InvalidElementType(other)),
607        }
608    }
609
610    /// Read the variable-width length field that precedes utf8 and bytes
611    /// payloads. The two low bits of the element type encode the width:
612    /// `0b00` = 1 byte, `0b01` = 2 bytes, `0b10` = 4 bytes, `0b11` = 8 bytes.
613    fn read_payload_len(&mut self, elem_type: u8) -> Result<usize> {
614        match elem_type & 0b11 {
615            0b00 => Ok(usize::from(self.next_byte()?)),
616            0b01 => Ok(usize::from(self.read_u16_le()?)),
617            0b10 => usize::try_from(self.read_u32_le()?).map_err(|_| Error::LengthOverflow),
618            _ => usize::try_from(self.read_u64_le()?).map_err(|_| Error::LengthOverflow),
619        }
620    }
621
622    fn read_u64_le(&mut self) -> Result<u64> {
623        let raw: [u8; 8] = self
624            .next_bytes(8)?
625            .try_into()
626            .map_err(|_| Error::InternalSliceConversion)?;
627        Ok(u64::from_le_bytes(raw))
628    }
629
630    fn read_utf8(&mut self, len: usize) -> Result<Value> {
631        let bytes = self.next_bytes(len)?;
632        // Validate UTF-8 across the ENTIRE payload (a malformed suffix still
633        // fails), then present only the text before the first IS1 (0x1F)
634        // separator. Matter uses IS1 to separate a char string's text from an
635        // optional localized-string language suffix (`"Kitchen\u{1F}0409"`);
636        // chip's `TLVReader::Get(CharSpan&)` and matter.js both return only the
637        // text (chip TestTLV.cpp CheckTLVCharSpan). Returning the whole payload
638        // surfaced the raw separator+suffix on reads of localized labels
639        // (BasicInformation NodeLabel, UserLabel) — CODEC-1. The raw suffix
640        // (LSID) is not yet exposed; that is a separate additive follow-up.
641        let s = core::str::from_utf8(bytes)?;
642        // IS1 (0x1F) is single-byte ASCII, so the split index is a valid char
643        // boundary.
644        let text = match s.find('\u{1F}') {
645            Some(i) => &s[..i],
646            None => s,
647        };
648        Ok(Value::Utf8(String::from(text)))
649    }
650
651    fn read_bytes(&mut self, len: usize) -> Result<Value> {
652        let bytes = self.next_bytes(len)?;
653        Ok(Value::Bytes(bytes.to_vec()))
654    }
655}
656
657#[cfg(test)]
658#[allow(clippy::unwrap_used)] // Test code: CLAUDE.md allows unwrap with a documented justification.
659mod tests {
660    use super::*;
661
662    #[test]
663    fn next_returns_none_on_empty_input() {
664        let mut r = TlvReader::new(&[]);
665        assert!(r.is_empty());
666        assert_eq!(r.next().unwrap(), None);
667    }
668
669    #[test]
670    fn next_decodes_bool_true_anonymous_vector_0001() {
671        let mut r = TlvReader::new(&[0x09]);
672        let el = r.next().unwrap().unwrap();
673        assert_eq!(
674            el,
675            Element::Scalar {
676                tag: Tag::Anonymous,
677                value: Value::Bool(true)
678            }
679        );
680        assert!(r.is_empty());
681    }
682
683    #[test]
684    fn next_decodes_bool_false() {
685        let mut r = TlvReader::new(&[0x08]);
686        let el = r.next().unwrap().unwrap();
687        assert_eq!(
688            el,
689            Element::Scalar {
690                tag: Tag::Anonymous,
691                value: Value::Bool(false)
692            }
693        );
694    }
695
696    #[test]
697    fn next_decodes_null_vector_implied() {
698        let mut r = TlvReader::new(&[0x14]);
699        let el = r.next().unwrap().unwrap();
700        assert_eq!(
701            el,
702            Element::Scalar {
703                tag: Tag::Anonymous,
704                value: Value::Null
705            }
706        );
707    }
708
709    #[test]
710    fn next_decodes_uint8_42_vector_0003() {
711        let mut r = TlvReader::new(&[0x04, 0x2A]);
712        let el = r.next().unwrap().unwrap();
713        assert_eq!(
714            el,
715            Element::Scalar {
716                tag: Tag::Anonymous,
717                value: Value::Uint(42)
718            }
719        );
720    }
721
722    #[test]
723    fn next_decodes_uint16_0x1234() {
724        let mut r = TlvReader::new(&[0x05, 0x34, 0x12]);
725        let el = r.next().unwrap().unwrap();
726        assert_eq!(
727            el,
728            Element::Scalar {
729                tag: Tag::Anonymous,
730                value: Value::Uint(0x1234)
731            }
732        );
733    }
734
735    #[test]
736    fn next_decodes_uint32_0xcafebabe() {
737        let mut r = TlvReader::new(&[0x06, 0xBE, 0xBA, 0xFE, 0xCA]);
738        let el = r.next().unwrap().unwrap();
739        assert_eq!(
740            el,
741            Element::Scalar {
742                tag: Tag::Anonymous,
743                value: Value::Uint(0xCAFE_BABE)
744            }
745        );
746    }
747
748    #[test]
749    fn next_decodes_uint64_big() {
750        let bytes = [0x07, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
751        let mut r = TlvReader::new(&bytes);
752        let el = r.next().unwrap().unwrap();
753        assert_eq!(
754            el,
755            Element::Scalar {
756                tag: Tag::Anonymous,
757                value: Value::Uint(0x0123_4567_89AB_CDEF),
758            }
759        );
760    }
761
762    #[test]
763    fn next_decodes_int8_neg17_vector_0008() {
764        let mut r = TlvReader::new(&[0x00, 0xEF]);
765        let el = r.next().unwrap().unwrap();
766        assert_eq!(
767            el,
768            Element::Scalar {
769                tag: Tag::Anonymous,
770                value: Value::Int(-17)
771            }
772        );
773    }
774
775    #[test]
776    fn next_decodes_int16_neg129() {
777        let mut r = TlvReader::new(&[0x01, 0x7F, 0xFF]);
778        let el = r.next().unwrap().unwrap();
779        assert_eq!(
780            el,
781            Element::Scalar {
782                tag: Tag::Anonymous,
783                value: Value::Int(-129)
784            }
785        );
786    }
787
788    #[test]
789    fn next_decodes_int32_min() {
790        let mut r = TlvReader::new(&[0x02, 0x00, 0x00, 0x00, 0x80]);
791        let el = r.next().unwrap().unwrap();
792        assert_eq!(
793            el,
794            Element::Scalar {
795                tag: Tag::Anonymous,
796                value: Value::Int(i64::from(i32::MIN))
797            }
798        );
799    }
800
801    #[test]
802    fn next_decodes_int64_min() {
803        let bytes = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80];
804        let mut r = TlvReader::new(&bytes);
805        let el = r.next().unwrap().unwrap();
806        assert_eq!(
807            el,
808            Element::Scalar {
809                tag: Tag::Anonymous,
810                value: Value::Int(i64::MIN)
811            }
812        );
813    }
814
815    #[test]
816    fn next_decodes_float32_zero_vector_0013() {
817        let mut r = TlvReader::new(&[0x0A, 0x00, 0x00, 0x00, 0x00]);
818        let el = r.next().unwrap().unwrap();
819        assert_eq!(
820            el,
821            Element::Scalar {
822                tag: Tag::Anonymous,
823                value: Value::Float(0.0)
824            }
825        );
826    }
827
828    #[test]
829    fn next_decodes_float64_zero_vector_0014() {
830        let bytes = [0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
831        let mut r = TlvReader::new(&bytes);
832        let el = r.next().unwrap().unwrap();
833        assert_eq!(
834            el,
835            Element::Scalar {
836                tag: Tag::Anonymous,
837                value: Value::Double(0.0)
838            }
839        );
840    }
841
842    #[test]
843    fn next_decodes_uint_with_context_tag_5() {
844        let mut r = TlvReader::new(&[0x24, 0x05, 0x2A]);
845        let el = r.next().unwrap().unwrap();
846        assert_eq!(
847            el,
848            Element::Scalar {
849                tag: Tag::Context(5),
850                value: Value::Uint(42)
851            }
852        );
853    }
854
855    #[test]
856    fn next_errors_on_unexpected_eof_in_payload() {
857        let mut r = TlvReader::new(&[0x05]); // uint16 with no payload
858        assert!(matches!(r.next(), Err(Error::UnexpectedEof)));
859    }
860
861    #[test]
862    fn next_decodes_uint_with_common_profile_2_byte_tag() {
863        let mut r = TlvReader::new(&[0x44, 0x07, 0x00, 0x2A]);
864        let el = r.next().unwrap().unwrap();
865        assert_eq!(
866            el,
867            Element::Scalar {
868                tag: Tag::CommonProfile(7),
869                value: Value::Uint(42)
870            }
871        );
872    }
873
874    #[test]
875    fn next_decodes_uint_with_common_profile_4_byte_tag() {
876        let mut r = TlvReader::new(&[0x64, 0x45, 0x23, 0x01, 0x00, 0x2A]);
877        let el = r.next().unwrap().unwrap();
878        assert_eq!(
879            el,
880            Element::Scalar {
881                tag: Tag::CommonProfile(0x0001_2345),
882                value: Value::Uint(42)
883            }
884        );
885    }
886
887    #[test]
888    fn next_decodes_uint_with_implicit_profile_2_byte_tag() {
889        let mut r = TlvReader::new(&[0x84, 0x07, 0x00, 0x2A]);
890        let el = r.next().unwrap().unwrap();
891        assert_eq!(
892            el,
893            Element::Scalar {
894                tag: Tag::ImplicitProfile(7),
895                value: Value::Uint(42)
896            }
897        );
898    }
899
900    #[test]
901    fn next_decodes_uint_with_implicit_profile_4_byte_tag() {
902        let mut r = TlvReader::new(&[0xA4, 0x45, 0x23, 0x01, 0x00, 0x2A]);
903        let el = r.next().unwrap().unwrap();
904        assert_eq!(
905            el,
906            Element::Scalar {
907                tag: Tag::ImplicitProfile(0x0001_2345),
908                value: Value::Uint(42)
909            }
910        );
911    }
912
913    #[test]
914    fn next_decodes_uint_with_fully_qualified_6_byte() {
915        let mut r = TlvReader::new(&[0xC4, 0xF1, 0xFF, 0x06, 0x00, 0x05, 0x00, 0x2A]);
916        let el = r.next().unwrap().unwrap();
917        assert_eq!(
918            el,
919            Element::Scalar {
920                tag: Tag::FullyQualified {
921                    vendor: 0xFFF1,
922                    profile: 0x0006,
923                    tag: 5
924                },
925                value: Value::Uint(42),
926            }
927        );
928    }
929
930    #[test]
931    fn next_decodes_uint_with_fully_qualified_8_byte() {
932        let mut r = TlvReader::new(&[0xE4, 0xF1, 0xFF, 0x06, 0x00, 0x45, 0x23, 0x01, 0x00, 0x2A]);
933        let el = r.next().unwrap().unwrap();
934        assert_eq!(
935            el,
936            Element::Scalar {
937                tag: Tag::FullyQualified {
938                    vendor: 0xFFF1,
939                    profile: 0x0006,
940                    tag: 0x0001_2345
941                },
942                value: Value::Uint(42),
943            }
944        );
945    }
946
947    #[test]
948    fn read_value_returns_tag_and_value_for_scalar() {
949        let mut r = TlvReader::new(&[0x24, 0x05, 0x2A]);
950        let (tag, value) = r.read_value().unwrap();
951        assert_eq!(tag, Tag::Context(5));
952        assert_eq!(value, Value::Uint(42));
953    }
954
955    #[test]
956    fn read_value_errors_on_empty_input() {
957        let mut r = TlvReader::new(&[]);
958        assert!(matches!(r.read_value(), Err(Error::UnexpectedEof)));
959    }
960
961    #[test]
962    fn next_decodes_utf8_hello_vector_0015() {
963        let bytes = [0x0C, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21];
964        let mut r = TlvReader::new(&bytes);
965        let el = r.next().unwrap().unwrap();
966        assert_eq!(
967            el,
968            Element::Scalar {
969                tag: Tag::Anonymous,
970                value: Value::Utf8(String::from("Hello!")),
971            }
972        );
973    }
974
975    #[test]
976    fn next_decodes_utf8_empty_vector_0016() {
977        let bytes = [0x0C, 0x00];
978        let mut r = TlvReader::new(&bytes);
979        let el = r.next().unwrap().unwrap();
980        assert_eq!(
981            el,
982            Element::Scalar {
983                tag: Tag::Anonymous,
984                value: Value::Utf8(String::new()),
985            }
986        );
987    }
988
989    #[test]
990    fn next_decodes_utf8_len16_path() {
991        let mut bytes = vec![0x0D, 0x00, 0x01]; // UTF8_LEN16, length 256 LE
992        bytes.extend(std::iter::repeat_n(b'a', 256));
993        let mut r = TlvReader::new(&bytes);
994        let el = r.next().unwrap().unwrap();
995        let Element::Scalar {
996            value: Value::Utf8(s),
997            ..
998        } = el
999        else {
1000            panic!("wrong variant")
1001        };
1002        assert_eq!(s.len(), 256);
1003        assert!(s.bytes().all(|b| b == b'a'));
1004    }
1005
1006    #[test]
1007    fn next_decodes_bytes_five_bytes_vector_0017() {
1008        let bytes = [0x10, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04];
1009        let mut r = TlvReader::new(&bytes);
1010        let el = r.next().unwrap().unwrap();
1011        assert_eq!(
1012            el,
1013            Element::Scalar {
1014                tag: Tag::Anonymous,
1015                value: Value::Bytes(vec![0x00, 0x01, 0x02, 0x03, 0x04]),
1016            }
1017        );
1018    }
1019
1020    #[test]
1021    fn next_decodes_bytes_empty_vector_0018() {
1022        let bytes = [0x10, 0x00];
1023        let mut r = TlvReader::new(&bytes);
1024        let el = r.next().unwrap().unwrap();
1025        assert_eq!(
1026            el,
1027            Element::Scalar {
1028                tag: Tag::Anonymous,
1029                value: Value::Bytes(Vec::new()),
1030            }
1031        );
1032    }
1033
1034    #[test]
1035    fn next_errors_on_invalid_utf8() {
1036        let bytes = [0x0C, 0x01, 0xFF];
1037        let mut r = TlvReader::new(&bytes);
1038        assert!(matches!(r.next(), Err(Error::InvalidUtf8(_))));
1039    }
1040
1041    #[test]
1042    fn next_errors_on_truncated_utf8_payload() {
1043        let bytes = [0x0C, 0x05, b'H', b'i']; // claims 5 bytes, has only 2
1044        let mut r = TlvReader::new(&bytes);
1045        assert!(matches!(r.next(), Err(Error::UnexpectedEof)));
1046    }
1047
1048    // --- Task 4: container event tests ---
1049
1050    #[test]
1051    fn next_decodes_structure_start_and_end_vector_0019() {
1052        let mut r = TlvReader::new(&[0x15, 0x18]);
1053        let el = r.next().unwrap().unwrap();
1054        assert_eq!(
1055            el,
1056            Element::ContainerStart {
1057                tag: Tag::Anonymous,
1058                kind: ContainerKind::Structure,
1059            }
1060        );
1061        let el = r.next().unwrap().unwrap();
1062        assert_eq!(el, Element::ContainerEnd);
1063        assert!(r.next().unwrap().is_none());
1064    }
1065
1066    #[test]
1067    fn next_decodes_array_start_and_end_vector_0020() {
1068        let mut r = TlvReader::new(&[0x16, 0x18]);
1069        assert_eq!(
1070            r.next().unwrap().unwrap(),
1071            Element::ContainerStart {
1072                tag: Tag::Anonymous,
1073                kind: ContainerKind::Array,
1074            }
1075        );
1076        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1077    }
1078
1079    #[test]
1080    fn next_decodes_list_start_and_end() {
1081        let mut r = TlvReader::new(&[0x17, 0x18]);
1082        assert_eq!(
1083            r.next().unwrap().unwrap(),
1084            Element::ContainerStart {
1085                tag: Tag::Anonymous,
1086                kind: ContainerKind::List,
1087            }
1088        );
1089        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1090    }
1091
1092    #[test]
1093    fn next_decodes_structure_with_child_streaming() {
1094        let mut r = TlvReader::new(&[0x15, 0x24, 0x00, 0x2A, 0x18]);
1095        assert_eq!(
1096            r.next().unwrap().unwrap(),
1097            Element::ContainerStart {
1098                tag: Tag::Anonymous,
1099                kind: ContainerKind::Structure,
1100            }
1101        );
1102        assert_eq!(
1103            r.next().unwrap().unwrap(),
1104            Element::Scalar {
1105                tag: Tag::Context(0),
1106                value: Value::Uint(42),
1107            }
1108        );
1109        assert_eq!(r.next().unwrap().unwrap(), Element::ContainerEnd);
1110        assert!(r.next().unwrap().is_none());
1111    }
1112
1113    #[test]
1114    fn next_errors_on_end_of_container_at_top_level() {
1115        let mut r = TlvReader::new(&[0x18]);
1116        assert!(matches!(r.next(), Err(Error::UnexpectedEndOfContainer)));
1117    }
1118
1119    #[test]
1120    fn next_errors_on_end_of_container_with_non_anonymous_tag_form() {
1121        // 0x38 = context-tag form (0b001) | END_OF_CONTAINER (0x18).
1122        let mut r = TlvReader::new(&[0x38, 0x05]);
1123        assert!(matches!(r.next(), Err(Error::InvalidTagControl(_))));
1124    }
1125
1126    #[test]
1127    fn next_errors_on_excessive_nesting() {
1128        let bytes: Vec<u8> = std::iter::repeat_n(0x15u8, 33).collect();
1129        let mut r = TlvReader::new(&bytes);
1130        for _ in 0..32 {
1131            assert!(matches!(
1132                r.next().unwrap().unwrap(),
1133                Element::ContainerStart {
1134                    kind: ContainerKind::Structure,
1135                    ..
1136                },
1137            ));
1138        }
1139        assert!(matches!(r.next(), Err(Error::ContainerTooDeep)));
1140    }
1141
1142    #[test]
1143    fn depth_returns_to_zero_after_balanced_close() {
1144        // Two readers — one balanced (depth returns to 0), then check that
1145        // a fresh reader sees 0x18 at top level as UnexpectedEndOfContainer.
1146        {
1147            let mut r = TlvReader::new(&[0x15, 0x18]);
1148            let _ = r.next(); // ContainerStart → depth = 1
1149            let _ = r.next(); // ContainerEnd → depth = 0
1150        }
1151        let mut r2 = TlvReader::new(&[0x18]);
1152        assert!(matches!(r2.next(), Err(Error::UnexpectedEndOfContainer)));
1153    }
1154
1155    // --- Task 5: read_value tree builder tests ---
1156
1157    #[test]
1158    fn read_value_returns_empty_structure_vector_0019() {
1159        let mut r = TlvReader::new(&[0x15, 0x18]);
1160        let (tag, value) = r.read_value().unwrap();
1161        assert_eq!(tag, Tag::Anonymous);
1162        assert_eq!(value, Value::Structure(Vec::new()));
1163    }
1164
1165    #[test]
1166    fn read_value_returns_empty_array_vector_0020() {
1167        let mut r = TlvReader::new(&[0x16, 0x18]);
1168        let (tag, value) = r.read_value().unwrap();
1169        assert_eq!(tag, Tag::Anonymous);
1170        assert_eq!(value, Value::Array(Vec::new()));
1171    }
1172
1173    #[test]
1174    fn read_value_returns_structure_with_ctx_member_vector_0021() {
1175        let mut r = TlvReader::new(&[0x15, 0x24, 0x00, 0x2A, 0x18]);
1176        let (tag, value) = r.read_value().unwrap();
1177        assert_eq!(tag, Tag::Anonymous);
1178        assert_eq!(
1179            value,
1180            Value::Structure(vec![(Tag::Context(0), Value::Uint(42))])
1181        );
1182    }
1183
1184    #[test]
1185    fn read_value_returns_array_of_three_uint8_vector_0022() {
1186        let mut r = TlvReader::new(&[0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18]);
1187        let (tag, value) = r.read_value().unwrap();
1188        assert_eq!(tag, Tag::Anonymous);
1189        assert_eq!(
1190            value,
1191            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1192        );
1193    }
1194
1195    #[test]
1196    fn read_value_returns_structure_with_bool_at_ctx7_vector_0023() {
1197        let mut r = TlvReader::new(&[0x15, 0x29, 0x07, 0x18]);
1198        let (tag, value) = r.read_value().unwrap();
1199        assert_eq!(tag, Tag::Anonymous);
1200        assert_eq!(
1201            value,
1202            Value::Structure(vec![(Tag::Context(7), Value::Bool(true))])
1203        );
1204    }
1205
1206    #[test]
1207    fn read_value_returns_empty_list() {
1208        let mut r = TlvReader::new(&[0x17, 0x18]);
1209        let (tag, value) = r.read_value().unwrap();
1210        assert_eq!(tag, Tag::Anonymous);
1211        assert_eq!(value, Value::List(Vec::new()));
1212    }
1213
1214    #[test]
1215    fn read_value_handles_nested_structure() {
1216        let mut r = TlvReader::new(&[0x15, 0x35, 0x00, 0x24, 0x00, 0x2A, 0x18, 0x18]);
1217        let (tag, value) = r.read_value().unwrap();
1218        assert_eq!(tag, Tag::Anonymous);
1219        let inner = Value::Structure(vec![(Tag::Context(0), Value::Uint(42))]);
1220        let outer = Value::Structure(vec![(Tag::Context(0), inner)]);
1221        assert_eq!(value, outer);
1222    }
1223
1224    #[test]
1225    fn read_value_errors_on_unclosed_container() {
1226        let mut r = TlvReader::new(&[0x15]);
1227        assert!(matches!(r.read_value(), Err(Error::UnclosedContainer)));
1228    }
1229
1230    #[test]
1231    fn read_value_errors_on_dangling_end_of_container() {
1232        let mut r = TlvReader::new(&[0x18]);
1233        assert!(matches!(
1234            r.read_value(),
1235            Err(Error::UnexpectedEndOfContainer)
1236        ));
1237    }
1238
1239    // --- Task 19: fail-closed array tags, budget, conversion error ---
1240
1241    #[test]
1242    fn read_value_rejects_array_with_context_tagged_child() {
1243        // 0x16 array-start, 0x24 0x00 0x2A = ctx(0) uint8=42, 0x18 end.
1244        // The child carries a context tag, which the spec forbids inside an
1245        // array. Pre-fix the decoder silently dropped the tag; now it errors.
1246        let mut r = TlvReader::new(&[0x16, 0x24, 0x00, 0x2A, 0x18]);
1247        assert!(matches!(r.read_value(), Err(Error::NonAnonymousArrayTag)));
1248    }
1249
1250    #[test]
1251    fn read_value_rejects_array_with_context_tagged_container_child() {
1252        // 0x16 array-start, 0x35 0x00 = ctx(0) struct-start, 0x18 inner end,
1253        // 0x18 outer end. The nested container child is context-tagged.
1254        let mut r = TlvReader::new(&[0x16, 0x35, 0x00, 0x18, 0x18]);
1255        assert!(matches!(r.read_value(), Err(Error::NonAnonymousArrayTag)));
1256    }
1257
1258    #[test]
1259    fn read_value_accepts_array_with_anonymous_children() {
1260        // Sanity: a well-formed array still decodes (regression guard for the
1261        // fail-closed change).
1262        let mut r = TlvReader::new(&[0x16, 0x04, 0x01, 0x04, 0x02, 0x18]);
1263        let (tag, value) = r.read_value().unwrap();
1264        assert_eq!(tag, Tag::Anonymous);
1265        assert_eq!(value, Value::Array(vec![Value::Uint(1), Value::Uint(2)]));
1266    }
1267
1268    #[test]
1269    fn read_value_errors_when_element_budget_is_exceeded() {
1270        // Array of three uint8 children = 4 elements total (array + 3 scalars).
1271        // A budget of 3 cannot fit them. The 8-byte input is larger than the
1272        // budget, so this takes the CHARGED path (the fast path is provably
1273        // unreachable for a violating input: more elements than budget implies
1274        // more input bytes than budget).
1275        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1276        let mut r = TlvReader::with_element_budget(&bytes, 3);
1277        assert!(matches!(r.read_value(), Err(Error::ElementBudgetExceeded)));
1278    }
1279
1280    #[test]
1281    fn read_value_fast_path_at_budget_equal_to_input_len() {
1282        // Boundary of the uncharged fast path: remaining input (8 bytes) equal
1283        // to the budget — the byte bound proves the 4 materialised elements
1284        // cannot exceed it, so the decode succeeds without accounting.
1285        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1286        let mut r = TlvReader::with_element_budget(&bytes, bytes.len());
1287        let (_, value) = r.read_value().unwrap();
1288        assert_eq!(
1289            value,
1290            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1291        );
1292    }
1293
1294    #[test]
1295    fn read_value_charged_path_at_budget_one_below_input_len() {
1296        // One below the fast-path boundary: input (8 bytes) > budget (7) takes
1297        // the charged path, which still admits the 4-element tree.
1298        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1299        let mut r = TlvReader::with_element_budget(&bytes, 7);
1300        let (_, value) = r.read_value().unwrap();
1301        assert_eq!(
1302            value,
1303            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1304        );
1305    }
1306
1307    #[test]
1308    fn read_value_succeeds_at_exactly_the_element_budget() {
1309        // Same input, budget of exactly 4, decodes fine.
1310        let bytes = [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18];
1311        let mut r = TlvReader::with_element_budget(&bytes, 4);
1312        let (_, value) = r.read_value().unwrap();
1313        assert_eq!(
1314            value,
1315            Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)])
1316        );
1317    }
1318
1319    #[test]
1320    fn read_value_budget_counts_a_single_scalar() {
1321        // A lone scalar costs one element; a zero budget rejects it.
1322        let mut r = TlvReader::with_element_budget(&[0x04, 0x2A], 0);
1323        assert!(matches!(r.read_value(), Err(Error::ElementBudgetExceeded)));
1324        let mut r = TlvReader::with_element_budget(&[0x04, 0x2A], 1);
1325        assert_eq!(r.read_value().unwrap(), (Tag::Anonymous, Value::Uint(42)));
1326    }
1327
1328    #[test]
1329    fn fixed_width_int_decode_still_works() {
1330        // Guard that the InternalSliceConversion relabel did not change the
1331        // happy path for a fixed-width int. The conversion branch itself is
1332        // unreachable: `next_bytes(N)` returns exactly N bytes or `UnexpectedEof`
1333        // first, so the `try_into::<[u8; N]>` can never fail.
1334        let mut r = TlvReader::new(&[0x05, 0x34, 0x12]);
1335        assert_eq!(
1336            r.next().unwrap().unwrap(),
1337            Element::Scalar {
1338                tag: Tag::Anonymous,
1339                value: Value::Uint(0x1234),
1340            }
1341        );
1342    }
1343
1344    // --- skip_container ----------------------------------------------------
1345
1346    /// Build an anonymous structure: ctx0=u(7), then a nested ctx9 struct
1347    /// {ctx0=u(1)}, then ctx1=u(42). Returns the encoded bytes.
1348    fn struct_with_nested() -> Vec<u8> {
1349        let mut buf = Vec::new();
1350        let mut w = crate::writer::TlvWriter::new(&mut buf);
1351        w.start_structure(Tag::Anonymous).unwrap();
1352        w.put_uint(Tag::Context(0), 7).unwrap();
1353        w.start_structure(Tag::Context(9)).unwrap();
1354        w.put_uint(Tag::Context(0), 1).unwrap();
1355        w.end_container().unwrap();
1356        w.put_uint(Tag::Context(1), 42).unwrap();
1357        w.end_container().unwrap();
1358        buf
1359    }
1360
1361    #[test]
1362    fn read_utf8_truncates_at_is1_separator() {
1363        // CODEC-1 / chip TestTLV.cpp CheckTLVCharSpan: a Matter char string is
1364        // presented as only the text before the first IS1 (0x1F) localized-
1365        // string separator. The writer keeps the raw bytes on the wire.
1366        fn decode_str(s: &str) -> String {
1367            let mut buf = Vec::new();
1368            let mut w = crate::writer::TlvWriter::new(&mut buf);
1369            w.put_utf8(Tag::Anonymous, s).unwrap();
1370            match TlvReader::new(&buf).next().unwrap().unwrap() {
1371                Element::Scalar {
1372                    value: Value::Utf8(t),
1373                    ..
1374                } => t,
1375                other => panic!("expected Utf8 scalar, got {other:?}"),
1376            }
1377        }
1378        // chip's two vectors: text before the separator is returned; a string
1379        // that STARTS with the separator presents as empty.
1380        assert_eq!(
1381            decode_str("This is a test case #1\u{1F}suffix"),
1382            "This is a test case #1"
1383        );
1384        assert_eq!(decode_str("\u{1F} abc \u{1F} def"), "");
1385        // No separator → unchanged; a real localized-label shape → just the text.
1386        assert_eq!(decode_str("Kitchen"), "Kitchen");
1387        assert_eq!(decode_str("Kitchen\u{1F}0409"), "Kitchen");
1388    }
1389
1390    #[test]
1391    fn skip_container_drains_nested_struct_and_positions_after() {
1392        let buf = struct_with_nested();
1393        let mut r = TlvReader::new(&buf);
1394        // open the outer struct
1395        assert!(matches!(
1396            r.next().unwrap(),
1397            Some(Element::ContainerStart {
1398                kind: ContainerKind::Structure,
1399                ..
1400            })
1401        ));
1402        // consume ctx0=7
1403        assert!(matches!(r.next().unwrap(), Some(Element::Scalar { .. })));
1404        // the next element is the nested ctx9 struct — open then skip it
1405        assert!(matches!(
1406            r.next().unwrap(),
1407            Some(Element::ContainerStart {
1408                kind: ContainerKind::Structure,
1409                ..
1410            })
1411        ));
1412        r.skip_container().unwrap();
1413        // reader must now be positioned at ctx1=42, NOT at the outer end
1414        match r.next().unwrap() {
1415            Some(Element::Scalar {
1416                tag: Tag::Context(1),
1417                value: Value::Uint(v),
1418            }) => {
1419                assert_eq!(v, 42);
1420            }
1421            other => panic!("expected ctx1=42 after skip, got {other:?}"),
1422        }
1423        // then the outer ContainerEnd, then None
1424        assert!(matches!(r.next().unwrap(), Some(Element::ContainerEnd)));
1425        assert!(r.next().unwrap().is_none());
1426    }
1427
1428    #[test]
1429    fn skip_container_handles_array_and_list_and_empty() {
1430        for kind_byte in ["array", "list", "empty"] {
1431            let mut buf = Vec::new();
1432            let mut w = crate::writer::TlvWriter::new(&mut buf);
1433            w.start_structure(Tag::Anonymous).unwrap();
1434            match kind_byte {
1435                "array" => {
1436                    w.start_array(Tag::Context(0)).unwrap();
1437                    w.put_uint(Tag::Anonymous, 1).unwrap();
1438                    w.put_uint(Tag::Anonymous, 2).unwrap();
1439                    w.end_container().unwrap();
1440                }
1441                "list" => {
1442                    w.start_list(Tag::Context(0)).unwrap();
1443                    w.put_uint(Tag::Context(5), 9).unwrap();
1444                    w.end_container().unwrap();
1445                }
1446                _ => {
1447                    w.start_structure(Tag::Context(0)).unwrap();
1448                    w.end_container().unwrap();
1449                }
1450            }
1451            w.put_uint(Tag::Context(1), 99).unwrap();
1452            w.end_container().unwrap();
1453
1454            let mut r = TlvReader::new(&buf);
1455            assert!(matches!(
1456                r.next().unwrap(),
1457                Some(Element::ContainerStart { .. })
1458            ));
1459            assert!(matches!(
1460                r.next().unwrap(),
1461                Some(Element::ContainerStart { .. })
1462            ));
1463            r.skip_container().unwrap();
1464            match r.next().unwrap() {
1465                Some(Element::Scalar {
1466                    tag: Tag::Context(1),
1467                    value: Value::Uint(v),
1468                }) => {
1469                    assert_eq!(v, 99, "kind {kind_byte}");
1470                }
1471                other => panic!("kind {kind_byte}: expected ctx1=99, got {other:?}"),
1472            }
1473        }
1474    }
1475
1476    #[test]
1477    fn skip_container_unclosed_is_error() {
1478        // outer struct opened, nested struct opened but never closed (truncated)
1479        let mut buf = Vec::new();
1480        {
1481            let mut w = crate::writer::TlvWriter::new(&mut buf);
1482            w.start_structure(Tag::Anonymous).unwrap();
1483            w.start_structure(Tag::Context(0)).unwrap();
1484            w.put_uint(Tag::Anonymous, 1).unwrap();
1485            // deliberately do NOT close either container
1486        }
1487        let mut r = TlvReader::new(&buf);
1488        assert!(matches!(
1489            r.next().unwrap(),
1490            Some(Element::ContainerStart { .. })
1491        ));
1492        assert!(matches!(
1493            r.next().unwrap(),
1494            Some(Element::ContainerStart { .. })
1495        ));
1496        assert!(matches!(r.skip_container(), Err(Error::UnclosedContainer)));
1497    }
1498}