Skip to main content

hap_tlv8/
reader.rs

1//! TLV8 decoder.
2//!
3//! [`Tlv8Reader::parse`] walks a byte slice and returns the items it contains,
4//! reassembling fragmented values: consecutive items of the same type are
5//! concatenated while the run stays open (each fragment exactly 255 bytes means
6//! more follows). The [`crate::SEPARATOR`] (`0xFF`) item is always kept as its
7//! own distinct entry and never reassembled.
8
9use crate::error::{Result, Tlv8Error};
10
11/// Stateless TLV8 decoder entry point.
12pub struct Tlv8Reader;
13
14impl Tlv8Reader {
15    /// Parse a TLV8 byte stream into reassembled `(type, value)` items.
16    ///
17    /// Consecutive items of the same type are concatenated into one logical
18    /// value while the run stays open (each fragment exactly 255 bytes means
19    /// more follows); a sub-255 fragment closes the run. The
20    /// [`crate::SEPARATOR`] (`0xFF`) item is kept as its own `(0xFF, vec![])`
21    /// entry and never reassembled.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`Tlv8Error::UnexpectedEof`] if an item declares a length that
26    /// runs past the end of the input.
27    pub fn parse(bytes: &[u8]) -> Result<Vec<(u8, Vec<u8>)>> {
28        let mut items: Vec<(u8, Vec<u8>)> = Vec::new();
29        // The run currently being accumulated, and whether it is still "open"
30        // (i.e. its last fragment was exactly 255 bytes, so more may follow).
31        let mut open_run: Option<(u8, bool)> = None;
32        let mut pos = 0;
33        while pos < bytes.len() {
34            let ty = bytes[pos];
35            let len = *bytes.get(pos + 1).ok_or(Tlv8Error::UnexpectedEof)? as usize;
36            let start = pos + 2;
37            let end = start.checked_add(len).ok_or(Tlv8Error::UnexpectedEof)?;
38            let value = bytes.get(start..end).ok_or(Tlv8Error::UnexpectedEof)?;
39            pos = end;
40
41            // The separator is never reassembled; it ends any open run and is
42            // pushed as its own item.
43            if ty == crate::SEPARATOR {
44                open_run = None;
45                items.push((ty, value.to_vec()));
46                continue;
47            }
48
49            let continues = matches!(open_run, Some((run_ty, true)) if run_ty == ty);
50            if continues {
51                // Append to the last item's value.
52                if let Some(last) = items.last_mut() {
53                    last.1.extend_from_slice(value);
54                }
55            } else {
56                items.push((ty, value.to_vec()));
57            }
58            // The run stays open only while fragments are full-width (255).
59            open_run = Some((ty, len == 255));
60        }
61        Ok(items)
62    }
63}
64
65#[cfg(test)]
66// CLAUDE.md test-code carve-out: unwrap/expect allowed with documented reason.
67#[allow(clippy::unwrap_used, clippy::expect_used)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn parse_empty_input_yields_no_items() {
73        assert_eq!(Tlv8Reader::parse(&[]).unwrap(), vec![]);
74    }
75
76    #[test]
77    fn parse_single_short_item() {
78        let items = Tlv8Reader::parse(&[0x01, 0x02, 0xAB, 0xCD]).unwrap();
79        assert_eq!(items, vec![(0x01, vec![0xAB, 0xCD])]);
80    }
81
82    #[test]
83    fn parse_zero_length_item() {
84        let items = Tlv8Reader::parse(&[0x06, 0x00]).unwrap();
85        assert_eq!(items, vec![(0x06, vec![])]);
86    }
87
88    #[test]
89    fn parse_two_distinct_types() {
90        let items = Tlv8Reader::parse(&[0x01, 0x01, 0xAA, 0x02, 0x01, 0xBB]).unwrap();
91        assert_eq!(items, vec![(0x01, vec![0xAA]), (0x02, vec![0xBB])]);
92    }
93
94    #[test]
95    fn parse_truncated_value_errors() {
96        // declares length 2 but only one value byte present
97        let err = Tlv8Reader::parse(&[0x01, 0x02, 0xAA]).unwrap_err();
98        assert_eq!(err, Tlv8Error::UnexpectedEof);
99    }
100
101    #[test]
102    fn parse_missing_length_byte_errors() {
103        let err = Tlv8Reader::parse(&[0x01]).unwrap_err();
104        assert_eq!(err, Tlv8Error::UnexpectedEof);
105    }
106
107    #[test]
108    fn parse_reassembles_256_byte_value() {
109        // Build the writer-side framing for a 256-byte value of type 0x09.
110        let mut stream = vec![0x09, 0xFF];
111        stream.extend((0u8..=254).collect::<Vec<u8>>()); // 255 bytes
112        stream.extend([0x09, 0x01, 0xFF]); // continuation: 1 byte (0xFF)
113        let items = Tlv8Reader::parse(&stream).unwrap();
114        assert_eq!(items.len(), 1);
115        assert_eq!(items[0].0, 0x09);
116        assert_eq!(items[0].1.len(), 256);
117        assert_eq!(items[0].1, (0..=u8::MAX).collect::<Vec<u8>>());
118    }
119
120    #[test]
121    fn parse_reassembles_exact_255_with_terminator() {
122        let mut stream = vec![0x09, 0xFF];
123        stream.extend(vec![0x42_u8; 255]);
124        stream.extend([0x09, 0x00]); // terminating zero-length item
125        let items = Tlv8Reader::parse(&stream).unwrap();
126        assert_eq!(items.len(), 1);
127        assert_eq!(items[0].0, 0x09);
128        assert_eq!(items[0].1, vec![0x42; 255]);
129    }
130
131    #[test]
132    fn parse_reassembles_510_byte_value() {
133        let mut stream = vec![0x09, 0xFF];
134        stream.extend(vec![0xAB_u8; 255]);
135        stream.extend([0x09, 0xFF]);
136        stream.extend(vec![0xAB_u8; 255]);
137        stream.extend([0x09, 0x00]); // terminator
138        let items = Tlv8Reader::parse(&stream).unwrap();
139        assert_eq!(items.len(), 1);
140        assert_eq!(items[0].1, vec![0xAB; 510]);
141    }
142
143    #[test]
144    fn parse_does_not_merge_two_short_items_of_same_type() {
145        // A 1-byte item is < 255, so the run ends. A following same-type item
146        // is a distinct logical value, not a continuation.
147        let items = Tlv8Reader::parse(&[0x09, 0x01, 0xAA, 0x09, 0x01, 0xBB]).unwrap();
148        assert_eq!(items, vec![(0x09, vec![0xAA]), (0x09, vec![0xBB])]);
149    }
150
151    #[test]
152    fn parse_keeps_separator_as_distinct_item() {
153        // value(type 1) sep value(type 1) — two pairings delimited by 0xFF.
154        let stream = [0x01, 0x01, 0xAA, 0xFF, 0x00, 0x01, 0x01, 0xBB];
155        let items = Tlv8Reader::parse(&stream).unwrap();
156        assert_eq!(
157            items,
158            vec![(0x01, vec![0xAA]), (0xFF, vec![]), (0x01, vec![0xBB])]
159        );
160    }
161}