Skip to main content

matter_codec/
writer.rs

1//! Streaming TLV encoder. Appends to a caller-provided `Vec<u8>`.
2
3use crate::error::{Error, Result};
4use crate::reader::MAX_DEPTH;
5use crate::tag::Tag;
6use crate::value::Value;
7use crate::{element_type as et, tag_control as tc};
8
9/// A streaming TLV encoder that appends to a caller-provided `Vec<u8>`.
10pub struct TlvWriter<'a> {
11    out: &'a mut Vec<u8>,
12}
13
14impl<'a> TlvWriter<'a> {
15    /// Construct a writer that appends to `out`. The writer borrows `out`
16    /// mutably; release the borrow by dropping the writer.
17    pub fn new(out: &'a mut Vec<u8>) -> Self {
18        Self { out }
19    }
20
21    /// Write a control octet (tag form bits OR'd with element type bits)
22    /// followed by any tag bytes the tag form requires.
23    fn write_tag(&mut self, tag: Tag, element_type: u8) {
24        match tag {
25            Tag::Anonymous => {
26                self.out.push(tc::ANONYMOUS | element_type);
27            }
28            Tag::Context(n) => {
29                self.out.push(tc::CONTEXT | element_type);
30                self.out.push(n);
31            }
32            Tag::CommonProfile(n) => {
33                if let Ok(n16) = u16::try_from(n) {
34                    self.out.push(tc::COMMON_PROFILE_2 | element_type);
35                    self.out.extend_from_slice(&n16.to_le_bytes());
36                } else {
37                    self.out.push(tc::COMMON_PROFILE_4 | element_type);
38                    self.out.extend_from_slice(&n.to_le_bytes());
39                }
40            }
41            Tag::ImplicitProfile(n) => {
42                if let Ok(n16) = u16::try_from(n) {
43                    self.out.push(tc::IMPLICIT_PROFILE_2 | element_type);
44                    self.out.extend_from_slice(&n16.to_le_bytes());
45                } else {
46                    self.out.push(tc::IMPLICIT_PROFILE_4 | element_type);
47                    self.out.extend_from_slice(&n.to_le_bytes());
48                }
49            }
50            Tag::FullyQualified {
51                vendor,
52                profile,
53                tag,
54            } => {
55                if let Ok(tag16) = u16::try_from(tag) {
56                    self.out.push(tc::FULLY_QUALIFIED_6 | element_type);
57                    self.out.extend_from_slice(&vendor.to_le_bytes());
58                    self.out.extend_from_slice(&profile.to_le_bytes());
59                    self.out.extend_from_slice(&tag16.to_le_bytes());
60                } else {
61                    self.out.push(tc::FULLY_QUALIFIED_8 | element_type);
62                    self.out.extend_from_slice(&vendor.to_le_bytes());
63                    self.out.extend_from_slice(&profile.to_le_bytes());
64                    self.out.extend_from_slice(&tag.to_le_bytes());
65                }
66            }
67        }
68    }
69
70    /// Emit a boolean value with the given tag.
71    ///
72    /// # Errors
73    ///
74    /// Currently infallible; returns `Ok(())` always. The `Result` return
75    /// type is reserved for future I/O-backed writers.
76    pub fn put_bool(&mut self, tag: Tag, v: bool) -> Result<()> {
77        let et = if v { et::BOOL_TRUE } else { et::BOOL_FALSE };
78        self.write_tag(tag, et);
79        Ok(())
80    }
81
82    /// Emit a null value with the given tag.
83    ///
84    /// # Errors
85    ///
86    /// Currently infallible; returns `Ok(())` always. The `Result` return
87    /// type is reserved for future I/O-backed writers.
88    pub fn put_null(&mut self, tag: Tag) -> Result<()> {
89        self.write_tag(tag, et::NULL);
90        Ok(())
91    }
92
93    /// Emit an unsigned integer with the given tag. The minimum-width
94    /// encoding (1, 2, 4, or 8 bytes) is chosen automatically per
95    /// Matter Core Spec §A.2.
96    ///
97    /// # Errors
98    ///
99    /// Currently infallible; returns `Ok(())` always. The `Result` return
100    /// type is reserved for future I/O-backed writers.
101    pub fn put_uint(&mut self, tag: Tag, v: u64) -> Result<()> {
102        if let Ok(n) = u8::try_from(v) {
103            self.write_tag(tag, et::UINT8);
104            self.out.push(n);
105        } else if let Ok(n) = u16::try_from(v) {
106            self.write_tag(tag, et::UINT16);
107            self.out.extend_from_slice(&n.to_le_bytes());
108        } else if let Ok(n) = u32::try_from(v) {
109            self.write_tag(tag, et::UINT32);
110            self.out.extend_from_slice(&n.to_le_bytes());
111        } else {
112            self.write_tag(tag, et::UINT64);
113            self.out.extend_from_slice(&v.to_le_bytes());
114        }
115        Ok(())
116    }
117
118    /// Emit a signed integer with the given tag. The minimum-width
119    /// encoding (1, 2, 4, or 8 bytes) is chosen automatically per
120    /// Matter Core Spec §A.2.
121    ///
122    /// # Errors
123    ///
124    /// Currently infallible; returns `Ok(())` always. The `Result` return
125    /// type is reserved for future I/O-backed writers.
126    pub fn put_int(&mut self, tag: Tag, v: i64) -> Result<()> {
127        if let Ok(n) = i8::try_from(v) {
128            self.write_tag(tag, et::INT8);
129            self.out.push(n.to_le_bytes()[0]);
130        } else if let Ok(n) = i16::try_from(v) {
131            self.write_tag(tag, et::INT16);
132            self.out.extend_from_slice(&n.to_le_bytes());
133        } else if let Ok(n) = i32::try_from(v) {
134            self.write_tag(tag, et::INT32);
135            self.out.extend_from_slice(&n.to_le_bytes());
136        } else {
137            self.write_tag(tag, et::INT64);
138            self.out.extend_from_slice(&v.to_le_bytes());
139        }
140        Ok(())
141    }
142
143    /// Emit a single-precision IEEE 754 float with the given tag.
144    ///
145    /// # Errors
146    ///
147    /// Currently infallible; returns `Ok(())` always. The `Result` return
148    /// type is reserved for future I/O-backed writers.
149    pub fn put_float(&mut self, tag: Tag, v: f32) -> Result<()> {
150        self.write_tag(tag, et::FLOAT32);
151        self.out.extend_from_slice(&v.to_le_bytes());
152        Ok(())
153    }
154
155    /// Emit a double-precision IEEE 754 float with the given tag.
156    ///
157    /// # Errors
158    ///
159    /// Currently infallible; returns `Ok(())` always. The `Result` return
160    /// type is reserved for future I/O-backed writers.
161    pub fn put_double(&mut self, tag: Tag, v: f64) -> Result<()> {
162        self.write_tag(tag, et::FLOAT64);
163        self.out.extend_from_slice(&v.to_le_bytes());
164        Ok(())
165    }
166
167    /// Emit a UTF-8 string with the given tag. The minimum-width length
168    /// field (1, 2, 4, or 8 bytes) is chosen automatically.
169    ///
170    /// # Errors
171    ///
172    /// Returns [`Error::LengthOverflow`] if the string is longer than
173    /// `u64::MAX` bytes (impossible in practice on any supported platform,
174    /// but the return type is `Result` for portability).
175    pub fn put_utf8(&mut self, tag: Tag, v: &str) -> Result<()> {
176        self.put_string_payload(
177            tag,
178            v.as_bytes(),
179            et::UTF8_LEN8,
180            et::UTF8_LEN16,
181            et::UTF8_LEN32,
182            et::UTF8_LEN64,
183        )
184    }
185
186    /// Emit an octet string with the given tag. The minimum-width length
187    /// field (1, 2, 4, or 8 bytes) is chosen automatically.
188    ///
189    /// # Errors
190    ///
191    /// Returns [`Error::LengthOverflow`] if the slice is longer than
192    /// `u64::MAX` bytes (impossible in practice on any supported platform,
193    /// but the return type is `Result` for portability).
194    pub fn put_bytes(&mut self, tag: Tag, v: &[u8]) -> Result<()> {
195        self.put_string_payload(
196            tag,
197            v,
198            et::BYTES_LEN8,
199            et::BYTES_LEN16,
200            et::BYTES_LEN32,
201            et::BYTES_LEN64,
202        )
203    }
204
205    /// Splice an already-encoded TLV element into the stream under a new
206    /// `tag`, replacing the element's own tag control.
207    ///
208    /// `element` MUST be a single complete TLV element encoded with an
209    /// **anonymous** tag (one control octet, no tag bytes), e.g. the output
210    /// of another `TlvWriter` that began with `start_structure(Tag::Anonymous)`.
211    /// Used to embed pre-encoded command-fields / payloads under a context
212    /// tag without re-parsing them.
213    ///
214    /// # Errors
215    ///
216    /// Returns [`Error::UnexpectedEof`] if `element` is empty,
217    /// [`Error::InvalidTagControl`] if `element` does not begin with an
218    /// anonymous-tagged control octet, or [`Error::InvalidElementType`] if
219    /// the element is a bare end-of-container marker (`0x18`), which is a
220    /// delimiter, not a complete element.
221    pub fn put_preencoded(&mut self, tag: Tag, element: &[u8]) -> Result<()> {
222        let (&control, rest) = element.split_first().ok_or(Error::UnexpectedEof)?;
223        if control & tc::TAG_CONTROL_MASK != tc::ANONYMOUS {
224            return Err(Error::InvalidTagControl(control & tc::TAG_CONTROL_MASK));
225        }
226        let element_type = control & et::ELEMENT_TYPE_MASK;
227        if element_type == et::END_OF_CONTAINER {
228            return Err(Error::InvalidElementType(element_type));
229        }
230        self.write_tag(tag, element_type);
231        self.out.extend_from_slice(rest);
232        Ok(())
233    }
234
235    fn put_string_payload(
236        &mut self,
237        tag: Tag,
238        bytes: &[u8],
239        et_len8: u8,
240        et_len16: u8,
241        et_len32: u8,
242        et_len64: u8,
243    ) -> Result<()> {
244        let len = bytes.len();
245        if let Ok(len8) = u8::try_from(len) {
246            self.write_tag(tag, et_len8);
247            self.out.push(len8);
248        } else if let Ok(len16) = u16::try_from(len) {
249            self.write_tag(tag, et_len16);
250            self.out.extend_from_slice(&len16.to_le_bytes());
251        } else if let Ok(len32) = u32::try_from(len) {
252            self.write_tag(tag, et_len32);
253            self.out.extend_from_slice(&len32.to_le_bytes());
254        } else {
255            let len64 = u64::try_from(len).map_err(|_| Error::LengthOverflow)?;
256            self.write_tag(tag, et_len64);
257            self.out.extend_from_slice(&len64.to_le_bytes());
258        }
259        self.out.extend_from_slice(bytes);
260        Ok(())
261    }
262
263    /// Begin a structure with the given tag. Children must be emitted
264    /// with their own `put_*` / `write_value` calls; the structure is
265    /// closed with [`Self::end_container`].
266    ///
267    /// # Errors
268    ///
269    /// Currently infallible; returns `Ok(())` always. The `Result` return
270    /// type is reserved for future I/O-backed writers.
271    pub fn start_structure(&mut self, tag: Tag) -> Result<()> {
272        self.write_tag(tag, et::STRUCTURE);
273        Ok(())
274    }
275
276    /// Begin an array with the given tag. Children MUST be emitted with
277    /// `Tag::Anonymous` per the Matter spec.
278    ///
279    /// # Errors
280    ///
281    /// Currently infallible; returns `Ok(())` always. The `Result` return
282    /// type is reserved for future I/O-backed writers.
283    pub fn start_array(&mut self, tag: Tag) -> Result<()> {
284        self.write_tag(tag, et::ARRAY);
285        Ok(())
286    }
287
288    /// Begin a list with the given tag. List members may carry any tag
289    /// form (including anonymous).
290    ///
291    /// # Errors
292    ///
293    /// Currently infallible; returns `Ok(())` always. The `Result` return
294    /// type is reserved for future I/O-backed writers.
295    pub fn start_list(&mut self, tag: Tag) -> Result<()> {
296        self.write_tag(tag, et::LIST);
297        Ok(())
298    }
299
300    /// Emit the end-of-container marker (`0x18`) closing the most
301    /// recently-opened container. The marker has no tag.
302    ///
303    /// # Errors
304    ///
305    /// Currently infallible; returns `Ok(())` always. The `Result` return
306    /// type is reserved for future I/O-backed writers.
307    pub fn end_container(&mut self) -> Result<()> {
308        self.out.push(et::END_OF_CONTAINER);
309        Ok(())
310    }
311
312    /// Walk a [`Value`] tree and emit the appropriate sequence of TLV
313    /// elements. Scalar variants are dispatched to the corresponding
314    /// `put_*` method; container variants (`Structure`, `Array`, `List`)
315    /// recursively encode all members and close with [`Self::end_container`].
316    ///
317    /// Array elements are always written with [`Tag::Anonymous`] regardless of
318    /// what tag is stored in the `Value`, enforcing the Matter spec requirement
319    /// that array elements carry no tag.
320    ///
321    /// Container nesting is bounded by [`MAX_DEPTH`], mirroring the reader's
322    /// limit. A `Value` tree nested deeper than that is rejected with
323    /// [`Error::ContainerTooDeep`] rather than risking a stack overflow on a
324    /// hostile or buggy input tree.
325    ///
326    /// # Errors
327    ///
328    /// - [`Error::ContainerTooDeep`] — the `value` tree nests containers more
329    ///   than [`MAX_DEPTH`] levels deep.
330    /// - Any error returned by the underlying `put_*` or container method.
331    pub fn write_value(&mut self, tag: Tag, value: &Value) -> Result<()> {
332        self.write_value_at_depth(tag, value, 0)
333    }
334
335    /// Recursive worker for [`Self::write_value`] that carries the current
336    /// container nesting depth so it can fail closed before the native call
337    /// stack is at risk.
338    fn write_value_at_depth(&mut self, tag: Tag, value: &Value, depth: usize) -> Result<()> {
339        match value {
340            Value::Bool(v) => self.put_bool(tag, *v),
341            Value::Null => self.put_null(tag),
342            Value::Uint(v) => self.put_uint(tag, *v),
343            Value::Int(v) => self.put_int(tag, *v),
344            Value::Float(v) => self.put_float(tag, *v),
345            Value::Double(v) => self.put_double(tag, *v),
346            Value::Utf8(v) => self.put_utf8(tag, v),
347            Value::Bytes(v) => self.put_bytes(tag, v),
348            Value::Structure(members) => {
349                if depth >= MAX_DEPTH {
350                    return Err(Error::ContainerTooDeep);
351                }
352                self.start_structure(tag)?;
353                for (member_tag, member_value) in members {
354                    self.write_value_at_depth(*member_tag, member_value, depth + 1)?;
355                }
356                self.end_container()
357            }
358            Value::Array(elements) => {
359                if depth >= MAX_DEPTH {
360                    return Err(Error::ContainerTooDeep);
361                }
362                self.start_array(tag)?;
363                for element in elements {
364                    self.write_value_at_depth(Tag::Anonymous, element, depth + 1)?;
365                }
366                self.end_container()
367            }
368            Value::List(members) => {
369                if depth >= MAX_DEPTH {
370                    return Err(Error::ContainerTooDeep);
371                }
372                self.start_list(tag)?;
373                for (member_tag, member_value) in members {
374                    self.write_value_at_depth(*member_tag, member_value, depth + 1)?;
375                }
376                self.end_container()
377            }
378        }
379    }
380}
381
382#[cfg(test)]
383#[allow(clippy::unwrap_used)] // Test code: CLAUDE.md allows unwrap with
384                              // a documented justification.
385mod tests {
386    use super::*;
387
388    // --- Cycle 1: put_bool ---
389
390    #[test]
391    fn put_bool_true_anonymous_matches_vector_0001() {
392        let mut buf = Vec::new();
393        let mut w = TlvWriter::new(&mut buf);
394        w.put_bool(Tag::Anonymous, true).unwrap();
395        assert_eq!(buf, [0x09]);
396    }
397
398    #[test]
399    fn put_bool_false_anonymous_matches_vector_0002() {
400        let mut buf = Vec::new();
401        let mut w = TlvWriter::new(&mut buf);
402        w.put_bool(Tag::Anonymous, false).unwrap();
403        assert_eq!(buf, [0x08]);
404    }
405
406    // --- Cycle 2: put_null ---
407
408    #[test]
409    fn put_null_anonymous_emits_0x14() {
410        let mut buf = Vec::new();
411        let mut w = TlvWriter::new(&mut buf);
412        w.put_null(Tag::Anonymous).unwrap();
413        assert_eq!(buf, [0x14]);
414    }
415
416    // --- Cycle 3: put_uint ---
417
418    #[test]
419    fn put_uint_42_anonymous_picks_1_byte_width_matches_vector_0003() {
420        let mut buf = Vec::new();
421        let mut w = TlvWriter::new(&mut buf);
422        w.put_uint(Tag::Anonymous, 42).unwrap();
423        assert_eq!(buf, [0x04, 0x2A]);
424    }
425
426    #[test]
427    fn put_uint_max_u8_anonymous_still_1_byte() {
428        let mut buf = Vec::new();
429        let mut w = TlvWriter::new(&mut buf);
430        w.put_uint(Tag::Anonymous, 255).unwrap();
431        assert_eq!(buf, [0x04, 0xFF]);
432    }
433
434    #[test]
435    fn put_uint_0x1234_anonymous_2_byte_le() {
436        let mut buf = Vec::new();
437        let mut w = TlvWriter::new(&mut buf);
438        w.put_uint(Tag::Anonymous, 0x1234).unwrap();
439        assert_eq!(buf, [0x05, 0x34, 0x12]);
440    }
441
442    #[test]
443    fn put_uint_0xcafebabe_anonymous_4_byte_le() {
444        let mut buf = Vec::new();
445        let mut w = TlvWriter::new(&mut buf);
446        w.put_uint(Tag::Anonymous, 0xCAFE_BABE).unwrap();
447        assert_eq!(buf, [0x06, 0xBE, 0xBA, 0xFE, 0xCA]);
448    }
449
450    #[test]
451    fn put_uint_big_anonymous_8_byte_le() {
452        let mut buf = Vec::new();
453        let mut w = TlvWriter::new(&mut buf);
454        w.put_uint(Tag::Anonymous, 0x0123_4567_89AB_CDEF).unwrap();
455        assert_eq!(buf, [0x07, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01]);
456    }
457
458    // --- Cycle 4: put_int ---
459
460    #[test]
461    fn put_int_neg17_anonymous_matches_vector_0008() {
462        let mut buf = Vec::new();
463        let mut w = TlvWriter::new(&mut buf);
464        w.put_int(Tag::Anonymous, -17).unwrap();
465        assert_eq!(buf, [0x00, 0xEF]);
466    }
467
468    #[test]
469    fn put_int_neg128_anonymous_1_byte() {
470        let mut buf = Vec::new();
471        let mut w = TlvWriter::new(&mut buf);
472        w.put_int(Tag::Anonymous, -128).unwrap();
473        assert_eq!(buf, [0x00, 0x80]);
474    }
475
476    #[test]
477    fn put_int_neg129_anonymous_2_byte() {
478        let mut buf = Vec::new();
479        let mut w = TlvWriter::new(&mut buf);
480        w.put_int(Tag::Anonymous, -129).unwrap();
481        assert_eq!(buf, [0x01, 0x7F, 0xFF]);
482    }
483
484    #[test]
485    fn put_int_i32_min_anonymous_4_byte() {
486        let mut buf = Vec::new();
487        let mut w = TlvWriter::new(&mut buf);
488        w.put_int(Tag::Anonymous, i64::from(i32::MIN)).unwrap();
489        assert_eq!(buf, [0x02, 0x00, 0x00, 0x00, 0x80]);
490    }
491
492    #[test]
493    fn put_int_i64_min_anonymous_8_byte() {
494        let mut buf = Vec::new();
495        let mut w = TlvWriter::new(&mut buf);
496        w.put_int(Tag::Anonymous, i64::MIN).unwrap();
497        assert_eq!(buf, [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]);
498    }
499
500    // --- Cycle 5: put_float and put_double ---
501
502    #[test]
503    fn put_float_zero_anonymous_matches_vector_0013() {
504        let mut buf = Vec::new();
505        let mut w = TlvWriter::new(&mut buf);
506        w.put_float(Tag::Anonymous, 0.0).unwrap();
507        assert_eq!(buf, [0x0A, 0x00, 0x00, 0x00, 0x00]);
508    }
509
510    #[test]
511    fn put_double_zero_anonymous_matches_vector_0014() {
512        let mut buf = Vec::new();
513        let mut w = TlvWriter::new(&mut buf);
514        w.put_double(Tag::Anonymous, 0.0).unwrap();
515        assert_eq!(buf, [0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
516    }
517
518    // --- Cycle 6: context tag emission ---
519
520    #[test]
521    fn put_uint_with_context_tag_5_emits_tag_byte() {
522        let mut buf = Vec::new();
523        let mut w = TlvWriter::new(&mut buf);
524        w.put_uint(Tag::Context(5), 42).unwrap();
525        // 0b001_00100 = 0x24 (context-tag form | UINT8 element type),
526        // then tag number 0x05, then payload 0x2A.
527        assert_eq!(buf, [0x24, 0x05, 0x2A]);
528    }
529
530    // --- Cycle 8: CommonProfile tag emission ---
531
532    #[test]
533    fn put_uint_with_common_profile_2_byte_tag() {
534        let mut buf = Vec::new();
535        let mut w = TlvWriter::new(&mut buf);
536        w.put_uint(Tag::CommonProfile(7), 42).unwrap();
537        // control = 0b010_00100 = 0x44 (CommonProfile 2-byte | UINT8)
538        // tag bytes = 0x07 0x00 (LE u16), payload = 0x2A
539        assert_eq!(buf, [0x44, 0x07, 0x00, 0x2A]);
540    }
541
542    #[test]
543    fn put_uint_with_common_profile_4_byte_tag() {
544        let mut buf = Vec::new();
545        let mut w = TlvWriter::new(&mut buf);
546        w.put_uint(Tag::CommonProfile(0x0001_2345), 42).unwrap();
547        // control = 0b011_00100 = 0x64
548        // tag bytes = 0x45 0x23 0x01 0x00 (LE u32), payload = 0x2A
549        assert_eq!(buf, [0x64, 0x45, 0x23, 0x01, 0x00, 0x2A]);
550    }
551
552    #[test]
553    fn put_uint_with_common_profile_at_u16_boundary_picks_2_byte() {
554        let mut buf = Vec::new();
555        let mut w = TlvWriter::new(&mut buf);
556        w.put_uint(Tag::CommonProfile(0xFFFF), 0).unwrap();
557        assert_eq!(buf, [0x44, 0xFF, 0xFF, 0x00]);
558    }
559
560    #[test]
561    fn put_uint_with_common_profile_just_above_u16_picks_4_byte() {
562        let mut buf = Vec::new();
563        let mut w = TlvWriter::new(&mut buf);
564        w.put_uint(Tag::CommonProfile(0x0001_0000), 0).unwrap();
565        assert_eq!(buf, [0x64, 0x00, 0x00, 0x01, 0x00, 0x00]);
566    }
567
568    // --- Cycle 9: ImplicitProfile tag emission ---
569
570    #[test]
571    fn put_uint_with_implicit_profile_2_byte_tag() {
572        let mut buf = Vec::new();
573        let mut w = TlvWriter::new(&mut buf);
574        w.put_uint(Tag::ImplicitProfile(7), 42).unwrap();
575        // control = 0b100_00100 = 0x84
576        assert_eq!(buf, [0x84, 0x07, 0x00, 0x2A]);
577    }
578
579    #[test]
580    fn put_uint_with_implicit_profile_4_byte_tag() {
581        let mut buf = Vec::new();
582        let mut w = TlvWriter::new(&mut buf);
583        w.put_uint(Tag::ImplicitProfile(0x0001_2345), 42).unwrap();
584        // control = 0b101_00100 = 0xA4
585        assert_eq!(buf, [0xA4, 0x45, 0x23, 0x01, 0x00, 0x2A]);
586    }
587
588    // --- Cycle 10: FullyQualified tag emission ---
589
590    #[test]
591    fn put_uint_with_fully_qualified_6_byte() {
592        let mut buf = Vec::new();
593        let mut w = TlvWriter::new(&mut buf);
594        w.put_uint(
595            Tag::FullyQualified {
596                vendor: 0xFFF1,
597                profile: 0x0006,
598                tag: 5,
599            },
600            42,
601        )
602        .unwrap();
603        // control = 0b110_00100 = 0xC4 (FQ 6-byte | UINT8)
604        // vendor 0xF1 0xFF, profile 0x06 0x00, tag 0x05 0x00, payload 0x2A
605        assert_eq!(buf, [0xC4, 0xF1, 0xFF, 0x06, 0x00, 0x05, 0x00, 0x2A]);
606    }
607
608    #[test]
609    fn put_uint_with_fully_qualified_8_byte() {
610        let mut buf = Vec::new();
611        let mut w = TlvWriter::new(&mut buf);
612        w.put_uint(
613            Tag::FullyQualified {
614                vendor: 0xFFF1,
615                profile: 0x0006,
616                tag: 0x0001_2345,
617            },
618            42,
619        )
620        .unwrap();
621        // control = 0b111_00100 = 0xE4
622        assert_eq!(
623            buf,
624            [0xE4, 0xF1, 0xFF, 0x06, 0x00, 0x45, 0x23, 0x01, 0x00, 0x2A]
625        );
626    }
627
628    // --- Cycle 11: put_utf8 ---
629
630    #[test]
631    fn put_utf8_hello_anonymous_matches_vector_0015() {
632        let mut buf = Vec::new();
633        let mut w = TlvWriter::new(&mut buf);
634        w.put_utf8(Tag::Anonymous, "Hello!").unwrap();
635        assert_eq!(buf, [0x0C, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21]);
636    }
637
638    #[test]
639    fn put_utf8_empty_anonymous_matches_vector_0016() {
640        let mut buf = Vec::new();
641        let mut w = TlvWriter::new(&mut buf);
642        w.put_utf8(Tag::Anonymous, "").unwrap();
643        assert_eq!(buf, [0x0C, 0x00]);
644    }
645
646    #[test]
647    fn put_utf8_at_255_byte_boundary_uses_len8() {
648        let s: String = "a".repeat(255);
649        let mut buf = Vec::new();
650        let mut w = TlvWriter::new(&mut buf);
651        w.put_utf8(Tag::Anonymous, &s).unwrap();
652        assert_eq!(buf.len(), 1 + 1 + 255);
653        assert_eq!(buf[0], 0x0C);
654        assert_eq!(buf[1], 0xFF);
655        assert!(buf[2..].iter().all(|&b| b == b'a'));
656    }
657
658    #[test]
659    fn put_utf8_at_256_bytes_picks_len16() {
660        let s: String = "a".repeat(256);
661        let mut buf = Vec::new();
662        let mut w = TlvWriter::new(&mut buf);
663        w.put_utf8(Tag::Anonymous, &s).unwrap();
664        assert_eq!(buf.len(), 1 + 2 + 256);
665        assert_eq!(buf[0], 0x0D);
666        assert_eq!(&buf[1..3], &[0x00, 0x01]);
667        assert!(buf[3..].iter().all(|&b| b == b'a'));
668    }
669
670    #[test]
671    fn put_utf8_at_u16_max_uses_len16() {
672        let s: String = "a".repeat(usize::from(u16::MAX));
673        let mut buf = Vec::new();
674        let mut w = TlvWriter::new(&mut buf);
675        w.put_utf8(Tag::Anonymous, &s).unwrap();
676        assert_eq!(buf[0], 0x0D);
677        assert_eq!(&buf[1..3], &[0xFF, 0xFF]);
678        assert_eq!(buf.len(), 1 + 2 + usize::from(u16::MAX));
679    }
680
681    #[test]
682    fn put_utf8_above_u16_max_picks_len32() {
683        let len = usize::from(u16::MAX) + 1; // 65,536
684        let s: String = "a".repeat(len);
685        let mut buf = Vec::new();
686        let mut w = TlvWriter::new(&mut buf);
687        w.put_utf8(Tag::Anonymous, &s).unwrap();
688        assert_eq!(buf[0], 0x0E);
689        assert_eq!(&buf[1..5], &[0x00, 0x00, 0x01, 0x00]);
690        assert_eq!(buf.len(), 1 + 4 + len);
691    }
692
693    // --- Cycle 12: put_bytes ---
694
695    #[test]
696    fn put_bytes_five_bytes_anonymous_matches_vector_0017() {
697        let mut buf = Vec::new();
698        let mut w = TlvWriter::new(&mut buf);
699        w.put_bytes(Tag::Anonymous, &[0x00, 0x01, 0x02, 0x03, 0x04])
700            .unwrap();
701        assert_eq!(buf, [0x10, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04]);
702    }
703
704    #[test]
705    fn put_bytes_empty_anonymous_matches_vector_0018() {
706        let mut buf = Vec::new();
707        let mut w = TlvWriter::new(&mut buf);
708        w.put_bytes(Tag::Anonymous, &[]).unwrap();
709        assert_eq!(buf, [0x10, 0x00]);
710    }
711
712    #[test]
713    fn put_bytes_at_256_bytes_picks_len16() {
714        let data = vec![0xAB; 256];
715        let mut buf = Vec::new();
716        let mut w = TlvWriter::new(&mut buf);
717        w.put_bytes(Tag::Anonymous, &data).unwrap();
718        assert_eq!(buf[0], 0x11);
719        assert_eq!(&buf[1..3], &[0x00, 0x01]);
720        assert_eq!(buf.len(), 1 + 2 + 256);
721        assert!(buf[3..].iter().all(|&b| b == 0xAB));
722    }
723
724    // --- Cycle 7: write_value dispatch ---
725
726    #[test]
727    fn write_value_dispatches_on_utf8_and_bytes_variants() {
728        for (value, expected) in [
729            (
730                Value::Utf8(String::from("Hello!")),
731                vec![0x0C, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21],
732            ),
733            (
734                Value::Bytes(vec![0x00, 0x01, 0x02, 0x03, 0x04]),
735                vec![0x10, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04],
736            ),
737        ] {
738            let mut buf = Vec::new();
739            let mut w = TlvWriter::new(&mut buf);
740            w.write_value(Tag::Anonymous, &value).unwrap();
741            assert_eq!(buf, expected, "value={value:?}");
742        }
743    }
744
745    #[test]
746    fn write_value_dispatches_on_variant() {
747        // One sanity case per variant. Bytes are taken from earlier per-method tests.
748        for (value, expected) in [
749            (Value::Bool(true), vec![0x09]),
750            (Value::Null, vec![0x14]),
751            (Value::Uint(42), vec![0x04, 0x2A]),
752            (Value::Int(-17), vec![0x00, 0xEF]),
753            (Value::Float(0.0), vec![0x0A, 0x00, 0x00, 0x00, 0x00]),
754            (
755                Value::Double(0.0),
756                vec![0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
757            ),
758        ] {
759            let mut buf = Vec::new();
760            let mut w = TlvWriter::new(&mut buf);
761            w.write_value(Tag::Anonymous, &value).unwrap();
762            assert_eq!(buf, expected, "value={value:?}");
763        }
764    }
765
766    // --- Phase 3 Task 2: container primitives ---
767
768    #[test]
769    fn start_structure_anonymous_emits_0x15() {
770        let mut buf = Vec::new();
771        let mut w = TlvWriter::new(&mut buf);
772        w.start_structure(Tag::Anonymous).unwrap();
773        assert_eq!(buf, [0x15]);
774    }
775
776    #[test]
777    fn start_array_anonymous_emits_0x16() {
778        let mut buf = Vec::new();
779        let mut w = TlvWriter::new(&mut buf);
780        w.start_array(Tag::Anonymous).unwrap();
781        assert_eq!(buf, [0x16]);
782    }
783
784    #[test]
785    fn start_list_anonymous_emits_0x17() {
786        let mut buf = Vec::new();
787        let mut w = TlvWriter::new(&mut buf);
788        w.start_list(Tag::Anonymous).unwrap();
789        assert_eq!(buf, [0x17]);
790    }
791
792    #[test]
793    fn end_container_emits_0x18() {
794        let mut buf = Vec::new();
795        let mut w = TlvWriter::new(&mut buf);
796        w.end_container().unwrap();
797        assert_eq!(buf, [0x18]);
798    }
799
800    #[test]
801    fn start_structure_with_context_tag_emits_combined_byte() {
802        let mut buf = Vec::new();
803        let mut w = TlvWriter::new(&mut buf);
804        w.start_structure(Tag::Context(7)).unwrap();
805        // 0b001_10101 = 0x35 (context tag form | STRUCTURE element type)
806        assert_eq!(buf, [0x35, 0x07]);
807    }
808
809    #[test]
810    fn empty_structure_anonymous_matches_vector_0019() {
811        let mut buf = Vec::new();
812        let mut w = TlvWriter::new(&mut buf);
813        w.start_structure(Tag::Anonymous).unwrap();
814        w.end_container().unwrap();
815        assert_eq!(buf, [0x15, 0x18]);
816    }
817
818    #[test]
819    fn structure_with_one_member_matches_vector_0021() {
820        // [0x15, 0x24, 0x00, 0x2A, 0x18]
821        let mut buf = Vec::new();
822        let mut w = TlvWriter::new(&mut buf);
823        w.start_structure(Tag::Anonymous).unwrap();
824        w.put_uint(Tag::Context(0), 42).unwrap();
825        w.end_container().unwrap();
826        assert_eq!(buf, [0x15, 0x24, 0x00, 0x2A, 0x18]);
827    }
828
829    // --- Phase 3 Task 3: write_value recursive container dispatch ---
830
831    #[test]
832    fn write_value_empty_structure_matches_vector_0019() {
833        let mut buf = Vec::new();
834        let mut w = TlvWriter::new(&mut buf);
835        w.write_value(Tag::Anonymous, &Value::Structure(Vec::new()))
836            .unwrap();
837        assert_eq!(buf, [0x15, 0x18]);
838    }
839
840    #[test]
841    fn write_value_empty_array_matches_vector_0020() {
842        let mut buf = Vec::new();
843        let mut w = TlvWriter::new(&mut buf);
844        w.write_value(Tag::Anonymous, &Value::Array(Vec::new()))
845            .unwrap();
846        assert_eq!(buf, [0x16, 0x18]);
847    }
848
849    #[test]
850    fn write_value_structure_with_ctx_member_matches_vector_0021() {
851        let value = Value::Structure(vec![(Tag::Context(0), Value::Uint(42))]);
852        let mut buf = Vec::new();
853        let mut w = TlvWriter::new(&mut buf);
854        w.write_value(Tag::Anonymous, &value).unwrap();
855        assert_eq!(buf, [0x15, 0x24, 0x00, 0x2A, 0x18]);
856    }
857
858    #[test]
859    fn write_value_array_of_three_uint8_matches_vector_0022() {
860        let value = Value::Array(vec![Value::Uint(1), Value::Uint(2), Value::Uint(3)]);
861        let mut buf = Vec::new();
862        let mut w = TlvWriter::new(&mut buf);
863        w.write_value(Tag::Anonymous, &value).unwrap();
864        assert_eq!(buf, [0x16, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x18]);
865    }
866
867    #[test]
868    fn write_value_structure_with_bool_at_ctx7_matches_vector_0023() {
869        let value = Value::Structure(vec![(Tag::Context(7), Value::Bool(true))]);
870        let mut buf = Vec::new();
871        let mut w = TlvWriter::new(&mut buf);
872        w.write_value(Tag::Anonymous, &value).unwrap();
873        assert_eq!(buf, [0x15, 0x29, 0x07, 0x18]);
874    }
875
876    #[test]
877    fn write_value_empty_list_emits_0x17_0x18() {
878        let mut buf = Vec::new();
879        let mut w = TlvWriter::new(&mut buf);
880        w.write_value(Tag::Anonymous, &Value::List(Vec::new()))
881            .unwrap();
882        assert_eq!(buf, [0x17, 0x18]);
883    }
884
885    // --- put_preencoded ---
886
887    #[test]
888    fn put_preencoded_retags_anonymous_struct_to_context_1() {
889        // An empty anonymous struct: 0x15 (anon-struct-start) 0x18 (end-container).
890        let anonymous_struct = vec![0x15u8, 0x18];
891        let mut buf = Vec::new();
892        let mut w = TlvWriter::new(&mut buf);
893        w.put_preencoded(Tag::Context(1), &anonymous_struct)
894            .unwrap();
895        // Expected: context-tag struct at tag 1, then the body (0x18 end).
896        // Control octet: tc::CONTEXT | et::STRUCTURE = 0x20 | 0x15 = 0x35
897        // Tag byte: 0x01
898        // Body: 0x18
899        assert_eq!(buf, [0x35, 0x01, 0x18]);
900    }
901
902    #[test]
903    fn put_preencoded_rejects_empty_input() {
904        let mut buf = Vec::new();
905        let mut w = TlvWriter::new(&mut buf);
906        assert!(matches!(
907            w.put_preencoded(Tag::Context(0), &[]),
908            Err(Error::UnexpectedEof)
909        ));
910    }
911
912    #[test]
913    fn put_preencoded_rejects_non_anonymous_input() {
914        // A context-tagged bool (tc::CONTEXT | et::BOOL_FALSE = 0x20 | 0x08 = 0x28).
915        let non_anonymous = vec![0x28u8, 0x00];
916        let mut buf = Vec::new();
917        let mut w = TlvWriter::new(&mut buf);
918        assert!(matches!(
919            w.put_preencoded(Tag::Context(0), &non_anonymous),
920            Err(Error::InvalidTagControl(_))
921        ));
922    }
923
924    #[test]
925    fn put_preencoded_rejects_bare_end_of_container() {
926        // 0x18 is END_OF_CONTAINER — anonymous tag bits (0b000) are valid, but
927        // the element type is the delimiter, not a complete element.
928        let mut buf = Vec::new();
929        let mut w = TlvWriter::new(&mut buf);
930        assert!(matches!(
931            w.put_preencoded(Tag::Context(1), &[0x18]),
932            Err(Error::InvalidElementType(_))
933        ));
934    }
935
936    #[test]
937    fn write_value_nested_structure() {
938        // outer { ctx(0): inner { ctx(0): uint8=42 } }
939        let inner = Value::Structure(vec![(Tag::Context(0), Value::Uint(42))]);
940        let outer = Value::Structure(vec![(Tag::Context(0), inner)]);
941        let mut buf = Vec::new();
942        let mut w = TlvWriter::new(&mut buf);
943        w.write_value(Tag::Anonymous, &outer).unwrap();
944        // 0x15 = anon-struct-start
945        //   0x35 0x00 = ctx-tag-struct-start at tag 0 (0b001_10101)
946        //     0x24 0x00 0x2A = ctx-tag uint8=42 at tag 0
947        //   0x18 = inner end
948        // 0x18 = outer end
949        assert_eq!(buf, [0x15, 0x35, 0x00, 0x24, 0x00, 0x2A, 0x18, 0x18]);
950    }
951
952    // --- Task 19: write-side depth guard ---
953
954    /// Build a `Value` tree of `levels` nested structures, innermost holding a
955    /// single uint. `levels == 1` is one structure wrapping the scalar.
956    fn nested_structure(levels: usize) -> Value {
957        let mut v = Value::Uint(0);
958        for _ in 0..levels {
959            v = Value::Structure(vec![(Tag::Anonymous, v)]);
960        }
961        v
962    }
963
964    #[test]
965    fn write_value_accepts_tree_at_max_depth() {
966        // MAX_DEPTH nested containers is the deepest the reader accepts, so the
967        // writer must accept it too (symmetric limits).
968        let value = nested_structure(MAX_DEPTH);
969        let mut buf = Vec::new();
970        let mut w = TlvWriter::new(&mut buf);
971        assert!(w.write_value(Tag::Anonymous, &value).is_ok());
972    }
973
974    #[test]
975    fn write_value_rejects_over_deep_tree() {
976        // One container deeper than the reader's limit must error (rather than
977        // recurse far enough to risk a native stack overflow).
978        let value = nested_structure(MAX_DEPTH + 1);
979        let mut buf = Vec::new();
980        let mut w = TlvWriter::new(&mut buf);
981        assert!(matches!(
982            w.write_value(Tag::Anonymous, &value),
983            Err(Error::ContainerTooDeep)
984        ));
985    }
986
987    #[test]
988    fn write_value_over_deep_tree_roundtrips_with_reader_limit() {
989        // A tree the writer accepts (== MAX_DEPTH) must also decode back, and a
990        // tree one deeper that the writer rejects matches the reader's own cap.
991        let ok = nested_structure(MAX_DEPTH);
992        let mut buf = Vec::new();
993        TlvWriter::new(&mut buf)
994            .write_value(Tag::Anonymous, &ok)
995            .unwrap();
996        let (_, decoded) = crate::reader::TlvReader::new(&buf).read_value().unwrap();
997        assert_eq!(decoded, ok);
998    }
999}