Skip to main content

hap_tlv8/
writer.rs

1//! TLV8 encoder appending to a caller-provided `Vec<u8>`.
2//!
3//! [`Tlv8Writer::push`] writes one logical value as one or more TLV8 items,
4//! fragmenting values longer than 255 bytes. The integer helpers
5//! write fixed-width little-endian payloads — HAP integer fields are fixed
6//! width, so there is no minimal-width trimming.
7
8/// A TLV8 encoder that appends to a borrowed `Vec<u8>`.
9///
10/// The writer borrows the output buffer mutably for its lifetime; drop the
11/// writer to release the borrow.
12pub struct Tlv8Writer<'a> {
13    out: &'a mut Vec<u8>,
14}
15
16impl<'a> Tlv8Writer<'a> {
17    /// Construct a writer that appends to `out`.
18    pub fn new(out: &'a mut Vec<u8>) -> Self {
19        Self { out }
20    }
21
22    /// Write a logical value as one or more TLV8 items, fragmenting any value
23    /// longer than 255 bytes into consecutive items of the same type.
24    ///
25    /// A 255-byte item means "more of this type follows", so when the value's
26    /// length is a non-zero exact multiple of 255 a terminating zero-length
27    /// item of the same type is appended to mark the end of the run. The
28    /// matching reader ([`crate::Tlv8Reader::parse`]) reverses this.
29    pub fn push(&mut self, ty: u8, value: &[u8]) {
30        if value.is_empty() {
31            self.out.push(ty);
32            self.out.push(0);
33            return;
34        }
35        let mut last_chunk_len = 0usize;
36        for chunk in value.chunks(255) {
37            self.out.push(ty);
38            // chunk.len() is at most 255, so the cast cannot truncate.
39            #[allow(clippy::cast_possible_truncation)]
40            self.out.push(chunk.len() as u8);
41            self.out.extend_from_slice(chunk);
42            last_chunk_len = chunk.len();
43        }
44        // If the final chunk was exactly 255 bytes, the reader would expect
45        // the run to continue. Append a zero-length terminator of this type.
46        if last_chunk_len == 255 {
47            self.out.push(ty);
48            self.out.push(0);
49        }
50    }
51
52    /// Write an unsigned 8-bit integer as a 1-byte item.
53    pub fn push_u8(&mut self, ty: u8, v: u8) {
54        self.push(ty, &v.to_le_bytes());
55    }
56
57    /// Write an unsigned 16-bit integer as a 2-byte little-endian item.
58    pub fn push_u16(&mut self, ty: u8, v: u16) {
59        self.push(ty, &v.to_le_bytes());
60    }
61
62    /// Write an unsigned 32-bit integer as a 4-byte little-endian item.
63    pub fn push_u32(&mut self, ty: u8, v: u32) {
64        self.push(ty, &v.to_le_bytes());
65    }
66
67    /// Write an unsigned 64-bit integer as an 8-byte little-endian item.
68    pub fn push_u64(&mut self, ty: u8, v: u64) {
69        self.push(ty, &v.to_le_bytes());
70    }
71
72    /// Write a string as its UTF-8 bytes. Long strings fragment via `push`.
73    pub fn push_str(&mut self, ty: u8, v: &str) {
74        self.push(ty, v.as_bytes());
75    }
76
77    /// Write a zero-length separator item ([`crate::SEPARATOR`], `0xFF`) used
78    /// to delimit repeated structures such as a list of pairings.
79    pub fn push_separator(&mut self) {
80        self.out.push(crate::SEPARATOR);
81        self.out.push(0);
82    }
83}
84
85#[cfg(test)]
86// CLAUDE.md test-code carve-out: unwrap/expect allowed with documented reason.
87#[allow(clippy::unwrap_used, clippy::expect_used)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn push_short_value_emits_type_len_value() {
93        let mut buf = Vec::new();
94        let mut w = Tlv8Writer::new(&mut buf);
95        w.push(0x01, &[0xAB, 0xCD]);
96        assert_eq!(buf, [0x01, 0x02, 0xAB, 0xCD]);
97    }
98
99    #[test]
100    fn push_empty_value_emits_zero_length_item() {
101        let mut buf = Vec::new();
102        let mut w = Tlv8Writer::new(&mut buf);
103        w.push(0x06, &[]);
104        assert_eq!(buf, [0x06, 0x00]);
105    }
106
107    #[test]
108    fn push_256_bytes_fragments_255_then_1() {
109        let mut buf = Vec::new();
110        let mut w = Tlv8Writer::new(&mut buf);
111        let value: Vec<u8> = (0..=u8::MAX).collect();
112        // value = 0x00,0x01,...,0xFF (256 bytes)
113        w.push(0x09, &value);
114        // header of first item
115        assert_eq!(&buf[0..2], &[0x09, 0xFF]);
116        // 255 value bytes 0x00..=0xFE
117        assert_eq!(&buf[2..257], &(0u8..=254).collect::<Vec<u8>>()[..]);
118        // header of second item: type 0x09, length 1
119        assert_eq!(&buf[257..259], &[0x09, 0x01]);
120        // last value byte 0xFF
121        assert_eq!(buf[259], 0xFF);
122        assert_eq!(buf.len(), 260);
123    }
124
125    #[test]
126    fn push_exactly_255_appends_terminating_zero_length_item() {
127        let mut buf = Vec::new();
128        let mut w = Tlv8Writer::new(&mut buf);
129        let value = vec![0x42_u8; 255];
130        w.push(0x09, &value);
131        assert_eq!(&buf[0..2], &[0x09, 0xFF]);
132        assert!(buf[2..257].iter().all(|&b| b == 0x42));
133        // terminating zero-length item
134        assert_eq!(&buf[257..259], &[0x09, 0x00]);
135        assert_eq!(buf.len(), 259);
136    }
137
138    #[test]
139    fn push_510_bytes_two_full_fragments_then_terminator() {
140        let mut buf = Vec::new();
141        let mut w = Tlv8Writer::new(&mut buf);
142        let value = vec![0xAB_u8; 510];
143        w.push(0x09, &value);
144        assert_eq!(&buf[0..2], &[0x09, 0xFF]);
145        assert_eq!(&buf[257..259], &[0x09, 0xFF]);
146        assert_eq!(&buf[514..516], &[0x09, 0x00]);
147        assert_eq!(buf.len(), 516);
148    }
149
150    #[test]
151    fn push_300_bytes_fragments_255_then_45() {
152        let mut buf = Vec::new();
153        let mut w = Tlv8Writer::new(&mut buf);
154        let value = vec![0x01_u8; 300];
155        w.push(0x09, &value);
156        assert_eq!(&buf[0..2], &[0x09, 0xFF]);
157        // second item length = 300 - 255 = 45 = 0x2D
158        assert_eq!(&buf[257..259], &[0x09, 0x2D]);
159        assert_eq!(buf.len(), 2 + 255 + 2 + 45);
160    }
161
162    #[test]
163    fn push_u8_emits_one_le_byte() {
164        let mut buf = Vec::new();
165        let mut w = Tlv8Writer::new(&mut buf);
166        w.push_u8(0x02, 0x2A);
167        assert_eq!(buf, [0x02, 0x01, 0x2A]);
168    }
169
170    #[test]
171    fn push_u16_emits_two_le_bytes() {
172        let mut buf = Vec::new();
173        let mut w = Tlv8Writer::new(&mut buf);
174        w.push_u16(0x03, 0x1234);
175        assert_eq!(buf, [0x03, 0x02, 0x34, 0x12]);
176    }
177
178    #[test]
179    fn push_u32_emits_four_le_bytes() {
180        let mut buf = Vec::new();
181        let mut w = Tlv8Writer::new(&mut buf);
182        w.push_u32(0x04, 0xCAFE_BABE);
183        assert_eq!(buf, [0x04, 0x04, 0xBE, 0xBA, 0xFE, 0xCA]);
184    }
185
186    #[test]
187    fn push_u64_emits_eight_le_bytes() {
188        let mut buf = Vec::new();
189        let mut w = Tlv8Writer::new(&mut buf);
190        w.push_u64(0x05, 0x0123_4567_89AB_CDEF);
191        assert_eq!(
192            buf,
193            [0x05, 0x08, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01]
194        );
195    }
196
197    #[test]
198    fn push_str_emits_utf8_bytes() {
199        let mut buf = Vec::new();
200        let mut w = Tlv8Writer::new(&mut buf);
201        w.push_str(0x07, "Pair");
202        // "Pair" = [0x50, 0x61, 0x69, 0x72], length 4.
203        assert_eq!(buf, [0x07, 0x04, 0x50, 0x61, 0x69, 0x72]);
204    }
205
206    #[test]
207    fn push_separator_emits_ff_zero() {
208        let mut buf = Vec::new();
209        let mut w = Tlv8Writer::new(&mut buf);
210        w.push_separator();
211        assert_eq!(buf, [0xFF, 0x00]);
212    }
213
214    #[test]
215    fn write_then_parse_round_trips_with_separator_and_fragment() {
216        use crate::Tlv8Reader;
217        let big = vec![0x07_u8; 300];
218        let mut buf = Vec::new();
219        {
220            let mut w = Tlv8Writer::new(&mut buf);
221            w.push(0x01, &[0xAA]);
222            w.push_separator();
223            w.push(0x01, &big);
224        }
225        let items = Tlv8Reader::parse(&buf).unwrap();
226        assert_eq!(items, vec![(0x01, vec![0xAA]), (0xFF, vec![]), (0x01, big)]);
227    }
228}