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