Skip to main content

rs_matter/tlv/
write.rs

1/*
2 *
3 *    Copyright (c) 2024-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18use num_traits::ToBytes;
19
20use crate::error::{Error, ErrorCode};
21use crate::utils::storage::WriteBuf;
22
23use super::{TLVControl, TLVTag, TLVTagType, TLVValue, TLVValueType, ToTLV};
24
25/// A trait representing a storage where data can be serialized as a TLV stream.
26/// by synchronously emitting bytes to the storage.
27///
28/// The one method that needs to be implemented by user code is `write`.
29///
30/// The trait operates in an append-only manner without requiring access to the serialized
31/// TLV data, so it can be implemented with an in-memory storage, or a file storage, or anything
32/// that can output a byte to somewhere (like the `Write` Rust traits).
33///
34/// With that said, the trait has two additional methods that (optionally) allow for "rewinding"
35/// the storage. Implementing these is optional, and they currently exist only for backwards
36/// compatibility with code implemented prior to the introduction of this trait.
37///
38/// For iterator-style TLV serialization look at the `ToTLVIter` trait.
39pub trait TLVWrite {
40    type Position: PartialEq + Copy;
41
42    /// Write `value` under a context tag.
43    ///
44    /// Taking the tag as a runtime argument rather than baking it into the
45    /// caller is what keeps this cheap in code size: the generated per-field
46    /// builder setters are otherwise structurally identical yet monomorphise
47    /// separately - and, because the builders carry both the parent chain and
48    /// the field index in their type, once per (field, parent, index) rather
49    /// than once per type.
50    fn write_ctx<T: ToTLV>(&mut self, ctx: u8, value: &T) -> Result<(), Error>
51    where
52        Self: Sized,
53    {
54        value.to_tlv(&TLVTag::Context(ctx), self)
55    }
56
57    /// Write a TLV tag and value to the TLV stream.
58    fn tlv(&mut self, tag: &TLVTag, value: &TLVValue) -> Result<(), Error> {
59        self.raw_value(tag, value.value_type(), &[])?;
60
61        match value {
62            TLVValue::Str8l(a) => self.write_raw_data((a.len() as u8).to_le_bytes()),
63            TLVValue::Str16l(a) => self.write_raw_data((a.len() as u16).to_le_bytes()),
64            TLVValue::Str32l(a) => self.write_raw_data((a.len() as u32).to_le_bytes()),
65            TLVValue::Str64l(a) => self.write_raw_data((a.len() as u64).to_le_bytes()),
66            TLVValue::Utf8l(a) => self.write_raw_data((a.len() as u8).to_le_bytes()),
67            TLVValue::Utf16l(a) => self.write_raw_data((a.len() as u16).to_le_bytes()),
68            TLVValue::Utf32l(a) => self.write_raw_data((a.len() as u32).to_le_bytes()),
69            TLVValue::Utf64l(a) => self.write_raw_data((a.len() as u64).to_le_bytes()),
70            _ => Ok(()),
71        }?;
72
73        match value {
74            TLVValue::S8(a) => self.write_raw_data(a.to_le_bytes()),
75            TLVValue::S16(a) => self.write_raw_data(a.to_le_bytes()),
76            TLVValue::S32(a) => self.write_raw_data(a.to_le_bytes()),
77            TLVValue::S64(a) => self.write_raw_data(a.to_le_bytes()),
78            TLVValue::U8(a) => self.write_raw_data(a.to_le_bytes()),
79            TLVValue::U16(a) => self.write_raw_data(a.to_le_bytes()),
80            TLVValue::U32(a) => self.write_raw_data(a.to_le_bytes()),
81            TLVValue::U64(a) => self.write_raw_data(a.to_le_bytes()),
82            TLVValue::False => Ok(()),
83            TLVValue::True => Ok(()),
84            TLVValue::F32(a) => self.write_raw_data(a.to_le_bytes()),
85            TLVValue::F64(a) => self.write_raw_data(a.to_le_bytes()),
86            TLVValue::Utf8l(a)
87            | TLVValue::Utf16l(a)
88            | TLVValue::Utf32l(a)
89            | TLVValue::Utf64l(a) => self.write_raw_data(a.as_bytes().iter().copied()),
90            TLVValue::Str8l(a)
91            | TLVValue::Str16l(a)
92            | TLVValue::Str32l(a)
93            | TLVValue::Str64l(a) => self.write_raw_data(a.iter().copied()),
94            TLVValue::Null
95            | TLVValue::Struct
96            | TLVValue::Array
97            | TLVValue::List
98            | TLVValue::EndCnt => Ok(()),
99        }
100    }
101
102    /// Write a tag and a TLV S8 value to the TLV stream.
103    fn i8(&mut self, tag: &TLVTag, data: i8) -> Result<(), Error> {
104        self.raw_value(tag, TLVValueType::S8, &data.to_le_bytes())
105    }
106
107    /// Write a tag and a TLV U8 value to the TLV stream.
108    fn u8(&mut self, tag: &TLVTag, data: u8) -> Result<(), Error> {
109        self.raw_value(tag, TLVValueType::U8, &data.to_le_bytes())
110    }
111
112    /// Write a tag and a TLV S16 or (if the data is small enough) S8 value to the TLV stream.
113    fn i16(&mut self, tag: &TLVTag, data: i16) -> Result<(), Error> {
114        if data >= i8::MIN as i16 && data <= i8::MAX as i16 {
115            self.i8(tag, data as i8)
116        } else {
117            self.raw_value(tag, TLVValueType::S16, &data.to_le_bytes())
118        }
119    }
120
121    /// Write a tag and a TLV U16 or (if the data is small enough) U8 value to the TLV stream.
122    fn u16(&mut self, tag: &TLVTag, data: u16) -> Result<(), Error> {
123        if data <= u8::MAX as u16 {
124            self.u8(tag, data as u8)
125        } else {
126            self.raw_value(tag, TLVValueType::U16, &data.to_le_bytes())
127        }
128    }
129
130    /// Write a tag and a TLV S32 or (if the data is small enough) S16 or S8 value to the TLV stream.
131    fn i32(&mut self, tag: &TLVTag, data: i32) -> Result<(), Error> {
132        if data >= i16::MIN as i32 && data <= i16::MAX as i32 {
133            self.i16(tag, data as i16)
134        } else {
135            self.raw_value(tag, TLVValueType::S32, &data.to_le_bytes())
136        }
137    }
138
139    /// Write a tag and a TLV U32 or (if the data is small enough) U16 or U8 value to the TLV stream.
140    fn u32(&mut self, tag: &TLVTag, data: u32) -> Result<(), Error> {
141        if data <= u16::MAX as u32 {
142            self.u16(tag, data as u16)
143        } else {
144            self.raw_value(tag, TLVValueType::U32, &data.to_le_bytes())
145        }
146    }
147
148    /// Write a tag and a TLV S64 or (if the data is small enough) S32, S16, or S8 value to the TLV stream.
149    fn i64(&mut self, tag: &TLVTag, data: i64) -> Result<(), Error> {
150        if data >= i32::MIN as i64 && data <= i32::MAX as i64 {
151            self.i32(tag, data as i32)
152        } else {
153            self.raw_value(tag, TLVValueType::S64, &data.to_le_bytes())
154        }
155    }
156
157    /// Write a tag and a TLV U64 or (if the data is small enough) U32, U16, or U8 value to the TLV stream.
158    fn u64(&mut self, tag: &TLVTag, data: u64) -> Result<(), Error> {
159        if data <= u32::MAX as u64 {
160            self.u32(tag, data as u32)
161        } else {
162            self.raw_value(tag, TLVValueType::U64, &data.to_le_bytes())
163        }
164    }
165
166    /// Write a tag and a TLV F32 to the TLV stream.
167    fn f32(&mut self, tag: &TLVTag, data: f32) -> Result<(), Error> {
168        self.raw_value(tag, TLVValueType::F32, &data.to_le_bytes())
169    }
170
171    /// Write a tag and a TLV F64 to the TLV stream.
172    fn f64(&mut self, tag: &TLVTag, data: f64) -> Result<(), Error> {
173        self.raw_value(tag, TLVValueType::F64, &data.to_le_bytes())
174    }
175
176    /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is a slice of u8 bytes.
177    ///
178    /// The exact octet string type (Str8l, Str16l, Str32l, or Str64l) is chosen based on the length of the data,
179    /// whereas the smallest type filling the provided data length is chosen.
180    fn str(&mut self, tag: &TLVTag, data: &[u8]) -> Result<(), Error> {
181        self.stri(tag, data.len(), data.iter().copied())
182    }
183
184    /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is a slice of u8 bytes.
185    ///
186    /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data
187    /// and to return the length of the written data.
188    ///
189    /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where
190    /// to operate.
191    ///
192    /// Note that this method always uses a Str16l value type to write the data, which restricts the data length to no more than
193    /// 65535 bytes.
194    ///
195    /// Note also that this method might not be supported by all `TLVWrite` implementations.
196    fn str_cb(
197        &mut self,
198        _tag: &TLVTag,
199        _cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
200    ) -> Result<(), Error> {
201        unimplemented!("str_cb not implemented for this TLVWrite instance");
202    }
203
204    /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is
205    /// anything that can be turned into an iterator of u8 bytes.
206    ///
207    /// The exact octet string type (Str8l, Str16l, Str32l, or Str64l) is chosen based on the length of the data,
208    /// whereas the smallest type filling the provided data length is chosen.
209    ///
210    /// NOTE: The length of the Octet String must be provided by the user and it must match the
211    /// number of bytes returned by the provided iterator, or else the generated TLV stream will be invalid.
212    fn stri<I>(&mut self, tag: &TLVTag, len: usize, data: I) -> Result<(), Error>
213    where
214        I: IntoIterator<Item = u8>,
215    {
216        if len <= u8::MAX as usize {
217            self.raw_value(tag, TLVValueType::Str8l, &(len as u8).to_le_bytes())?;
218        } else if len <= u16::MAX as usize {
219            self.raw_value(tag, TLVValueType::Str16l, &(len as u16).to_le_bytes())?;
220        } else if len <= u32::MAX as usize {
221            self.raw_value(tag, TLVValueType::Str32l, &(len as u32).to_le_bytes())?;
222        } else {
223            self.raw_value(tag, TLVValueType::Str64l, &(len as u64).to_le_bytes())?;
224        }
225
226        self.write_raw_data(data)
227    }
228
229    /// Write a tag and a TLV UTF-8 String to the TLV stream, where the UTF-8 String is a str.
230    ///
231    /// The exact UTF-8 string type (Utf8l, Utf16l, Utf32l, or Utf64l) is chosen based on the length of the data,
232    /// whereas the smallest type filling the provided data length is chosen.
233    fn utf8(&mut self, tag: &TLVTag, data: &str) -> Result<(), Error> {
234        self.utf8i(tag, data.len(), data.as_bytes().iter().copied())
235    }
236
237    /// Write a tag and a TLV UTF-8 String to the TLV stream, where the UTF-8 String is
238    /// anything that can be turned into an iterator of u8 bytes.
239    ///
240    /// The exact UTF-8 string type (Utf8l, Utf16l, Utf32l, or Utf64l) is chosen based on the length of the data,
241    /// whereas the smallest type filling the provided data length is chosen.
242    ///
243    /// NOTE 1: The length of the UTF-8 String must be provided by the user and it must match the
244    /// number of bytes returned by the provided iterator, or else the generated TLV stream will be invalid.
245    ///
246    /// NOTE 2: The provided iterator must return valid UTF-8 bytes, or else the generated TLV stream will be invalid.
247    fn utf8i<I>(&mut self, tag: &TLVTag, len: usize, data: I) -> Result<(), Error>
248    where
249        I: IntoIterator<Item = u8>,
250    {
251        if len <= u8::MAX as usize {
252            self.raw_value(tag, TLVValueType::Utf8l, &(len as u8).to_le_bytes())?;
253        } else if len <= u16::MAX as usize {
254            self.raw_value(tag, TLVValueType::Utf16l, &(len as u16).to_le_bytes())?;
255        } else if len <= u32::MAX as usize {
256            self.raw_value(tag, TLVValueType::Utf32l, &(len as u32).to_le_bytes())?;
257        } else {
258            self.raw_value(tag, TLVValueType::Utf64l, &(len as u64).to_le_bytes())?;
259        }
260
261        self.write_raw_data(data)
262    }
263
264    /// Write a tag and a TLV UTF-8 String to the TLV stream, where the UTF-8 String is a str.
265    ///
266    /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data
267    /// and to return the length of the written data.
268    ///
269    /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where
270    /// to operate.
271    ///
272    /// Note that this method always uses a Utf16l value type to write the data, which restricts the data length to no more than
273    /// 65535 bytes.
274    ///
275    /// Note also that this method might not be supported by all `TLVWrite` implementations.
276    fn utf8_cb(
277        &mut self,
278        _tag: &TLVTag,
279        _cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
280    ) -> Result<(), Error> {
281        unimplemented!("utf8_cb not implemented for this TLVWrite instance");
282    }
283
284    /// Write a tag and a value indicating the start of a Struct TLV container.
285    ///
286    /// NOTE: The user must call `end_container` after writing all the Struct fields
287    /// to close the Struct container or else the generated TLV stream will be invalid.
288    fn start_struct(&mut self, tag: &TLVTag) -> Result<(), Error> {
289        self.raw_value(tag, TLVValueType::Struct, &[])
290    }
291
292    /// Write a tag and a value indicating the start of an Array TLV container.
293    ///
294    /// NOTE: The user must call `end_container` after writing all the Array elements
295    /// to close the Array container or else the generated TLV stream will be invalid.
296    fn start_array(&mut self, tag: &TLVTag) -> Result<(), Error> {
297        self.raw_value(tag, TLVValueType::Array, &[])
298    }
299
300    /// Write a tag and a value indicating the start of a List TLV container.
301    ///
302    /// NOTE: The user must call `end_container` after writing all the List elements
303    /// to close the List container or else the generated TLV stream will be invalid.
304    fn start_list(&mut self, tag: &TLVTag) -> Result<(), Error> {
305        self.raw_value(tag, TLVValueType::List, &[])
306    }
307
308    /// Write a tag and a value indicating the start of a Struct TLV container.
309    ///
310    /// NOTE: The user must call `end_container` after writing all the Struct fields
311    /// to close the Struct container or else the generated TLV stream will be invalid.
312    fn start_container(&mut self, tag: &TLVTag, container_type: TLVValueType) -> Result<(), Error> {
313        if !container_type.is_container() {
314            Err(ErrorCode::TLVTypeMismatch)?;
315        }
316
317        self.raw_value(tag, container_type, &[])
318    }
319
320    /// Write a value indicating the end of a Struct, Array, or List TLV container.
321    ///
322    /// NOTE: This method must be called only when the corresponding container has been opened
323    /// using `start_struct`, `start_array`, or `start_list`, or else the generated TLV stream will be invalid.
324    fn end_container(&mut self) -> Result<(), Error> {
325        self.write(TLVControl::new(TLVTagType::Anonymous, TLVValueType::EndCnt).as_raw())
326    }
327
328    /// Write a tag and a TLV Null value to the TLV stream.
329    fn null(&mut self, tag: &TLVTag) -> Result<(), Error> {
330        self.raw_value(tag, TLVValueType::Null, &[])
331    }
332
333    /// Write a tag and a TLV True or False value to the TLV stream.
334    fn bool(&mut self, tag: &TLVTag, val: bool) -> Result<(), Error> {
335        self.raw_value(
336            tag,
337            if val {
338                TLVValueType::True
339            } else {
340                TLVValueType::False
341            },
342            &[],
343        )
344    }
345
346    /// Write a tag and a raw, already-encoded TLV value represented as a byte slice.
347    fn raw_value(
348        &mut self,
349        tag: &TLVTag,
350        value_type: TLVValueType,
351        value_payload: &[u8],
352    ) -> Result<(), Error> {
353        self.write(TLVControl::new(tag.tag_type(), value_type).as_raw())?;
354
355        match tag {
356            TLVTag::Anonymous => Ok(()),
357            TLVTag::Context(v) => self.write_raw_data(v.to_le_bytes()),
358            TLVTag::CommonPrf16(v) | TLVTag::ImplPrf16(v) => self.write_raw_data(v.to_le_bytes()),
359            TLVTag::CommonPrf32(v) | TLVTag::ImplPrf32(v) => self.write_raw_data(v.to_le_bytes()),
360            TLVTag::FullQual48 {
361                vendor_id,
362                profile,
363                tag,
364            } => {
365                self.write_raw_data(vendor_id.to_le_bytes())?;
366                self.write_raw_data(profile.to_le_bytes())?;
367                self.write_raw_data(tag.to_le_bytes())
368            }
369            TLVTag::FullQual64 {
370                vendor_id,
371                profile,
372                tag,
373            } => {
374                self.write_raw_data(vendor_id.to_le_bytes())?;
375                self.write_raw_data(profile.to_le_bytes())?;
376                self.write_raw_data(tag.to_le_bytes())
377            }
378        }?;
379
380        self.write_raw_data(value_payload.iter().copied())
381    }
382
383    /// Append multiple raw bytes to the TLV stream.
384    fn write_raw_data<I>(&mut self, bytes: I) -> Result<(), Error>
385    where
386        I: IntoIterator<Item = u8>,
387    {
388        for byte in bytes {
389            self.write(byte)?;
390        }
391
392        Ok(())
393    }
394
395    /// Append a single byte to the TLV stream.
396    fn write(&mut self, byte: u8) -> Result<(), Error>;
397
398    /// Get the current position in the TLV stream.
399    ///
400    /// NOTE: This method might not be supported by all implementations and therefore it might panic.
401    fn get_tail(&self) -> Self::Position {
402        unimplemented!("get_tail not implemented for this TLVWrite instance");
403    }
404
405    /// Rewind the TLV stream to a previous position.
406    ///
407    /// NOTE: This method might not be supported by all implementations and therefore it might panic.
408    fn rewind_to(&mut self, _pos: Self::Position) {
409        unimplemented!("rewind_to not implemented for this TLVWrite instance");
410    }
411
412    /// Get a mutable slice of the available space in the TLV stream.
413    ///
414    /// NOTE: This method assumes that the TLV write implementation is writing to a buffer,
415    /// which might not be the case for all implementations. Therefore, this method might panic
416    /// if the implementation does not support it.
417    fn available_space(&mut self) -> &mut [u8] {
418        unimplemented!("available_space not implemented for this TLVWrite instance");
419    }
420}
421
422impl<T> TLVWrite for &mut T
423where
424    T: TLVWrite,
425{
426    type Position = T::Position;
427
428    fn write(&mut self, byte: u8) -> Result<(), Error> {
429        (**self).write(byte)
430    }
431
432    fn get_tail(&self) -> Self::Position {
433        (**self).get_tail()
434    }
435
436    fn rewind_to(&mut self, pos: Self::Position) {
437        (**self).rewind_to(pos)
438    }
439
440    fn available_space(&mut self) -> &mut [u8] {
441        (**self).available_space()
442    }
443
444    fn str_cb(
445        &mut self,
446        tag: &TLVTag,
447        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
448    ) -> Result<(), Error> {
449        (**self).str_cb(tag, cb)
450    }
451
452    fn utf8_cb(
453        &mut self,
454        tag: &TLVTag,
455        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
456    ) -> Result<(), Error> {
457        (**self).utf8_cb(tag, cb)
458    }
459}
460
461impl TLVWrite for WriteBuf<'_> {
462    type Position = usize;
463
464    fn write(&mut self, byte: u8) -> Result<(), Error> {
465        WriteBuf::append(self, &[byte])
466    }
467
468    fn get_tail(&self) -> Self::Position {
469        WriteBuf::get_tail(self)
470    }
471
472    fn rewind_to(&mut self, pos: Self::Position) {
473        WriteBuf::rewind_tail_to(self, pos)
474    }
475
476    fn available_space(&mut self) -> &mut [u8] {
477        WriteBuf::empty_as_mut_slice(self)
478    }
479
480    fn str_cb(
481        &mut self,
482        tag: &TLVTag,
483        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
484    ) -> Result<(), Error> {
485        WriteBuf::str_cb(self, tag, cb)
486    }
487
488    fn utf8_cb(
489        &mut self,
490        tag: &TLVTag,
491        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
492    ) -> Result<(), Error> {
493        WriteBuf::utf8_cb(self, tag, cb)
494    }
495}
496
497impl WriteBuf<'_> {
498    /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is a slice of u8 bytes.
499    ///
500    /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data
501    /// and to return the length of the written data.
502    ///
503    /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where
504    /// to operate.
505    ///
506    /// Note that this method reserves a Str16l header before invoking the callback, restricting
507    /// the data length to no more than 65535 bytes. If the actual length fits in a single byte,
508    /// the header is downgraded in-place to a canonical Str8l encoding (shortest-form length),
509    /// which some strict Matter controllers (e.g. SmartThings) require.
510    pub fn str_cb(
511        &mut self,
512        tag: &TLVTag,
513        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
514    ) -> Result<(), Error> {
515        let control_offset = self.get_tail();
516        self.raw_value(tag, TLVValueType::Str16l, &0_u16.to_le_bytes())?;
517
518        let value_offset = self.get_tail();
519
520        let len = self.append_with_buf(cb)?;
521
522        Self::finalize_len_header(
523            self,
524            control_offset,
525            value_offset,
526            len,
527            tag.tag_type(),
528            TLVValueType::Str8l,
529        );
530
531        Ok(())
532    }
533
534    /// Write a tag and a TLV UTF-8 String to the TLV stream, where the UTF-8 String is a str.
535    ///
536    /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data
537    /// and to return the length of the written data.
538    ///
539    /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where
540    /// to operate.
541    ///
542    /// Note that this method reserves a Utf16l header before invoking the callback, restricting
543    /// the data length to no more than 65535 bytes. If the actual length fits in a single byte,
544    /// the header is downgraded in-place to a canonical Utf8l encoding (shortest-form length).
545    pub fn utf8_cb(
546        &mut self,
547        tag: &TLVTag,
548        cb: impl FnOnce(&mut [u8]) -> Result<usize, Error>,
549    ) -> Result<(), Error> {
550        let control_offset = self.get_tail();
551        self.raw_value(tag, TLVValueType::Utf16l, &0_u16.to_le_bytes())?;
552
553        let value_offset = self.get_tail();
554
555        let len = self.append_with_buf(cb)?;
556
557        Self::finalize_len_header(
558            self,
559            control_offset,
560            value_offset,
561            len,
562            tag.tag_type(),
563            TLVValueType::Utf8l,
564        );
565
566        Ok(())
567    }
568
569    /// Rewrite the length header reserved by `str_cb` / `utf8_cb` so that the shortest
570    /// possible length encoding is used (canonical Matter TLV).
571    ///
572    /// Before the call, the buffer layout is:
573    ///   [... prefix ...][control=<16l>][tag bytes][0x00][0x00][data ...]
574    ///                   ^control_offset           ^value_offset-2      ^value_offset+len
575    ///
576    /// If `len <= u8::MAX`, the header is rewritten to:
577    ///   [... prefix ...][control=<8l>][tag bytes][len][data ...]
578    /// and `end` is decremented by 1.
579    ///
580    /// Otherwise, the 16-bit length is simply patched in place.
581    fn finalize_len_header(
582        this: &mut WriteBuf<'_>,
583        control_offset: usize,
584        value_offset: usize,
585        len: usize,
586        tag_type: TLVTagType,
587        short_value_type: TLVValueType,
588    ) {
589        if len <= u8::MAX as usize {
590            // Canonical: use 1-byte length encoding.
591            this.buf[control_offset] = TLVControl::new(tag_type, short_value_type).as_raw();
592            this.buf[value_offset - 2] = len as u8;
593            this.buf
594                .copy_within(value_offset..value_offset + len, value_offset - 1);
595            this.rewind_tail_to(this.get_tail() - 1);
596        } else if len <= u16::MAX as usize {
597            // Keep 2-byte length encoding and patch the placeholder.
598            this.buf[value_offset - 2..value_offset].copy_from_slice(&(len as u16).to_le_bytes());
599        } else {
600            // This should not happen, as the callback is expected to respect the reserved header size.
601            panic!("Callback wrote more data than the reserved header can encode");
602        }
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use core::f32;
609
610    use super::{TLVTag, TLVWrite};
611    use crate::{tlv::TLVValue, utils::storage::WriteBuf};
612
613    #[test]
614    fn test_write_success() {
615        let mut buf = [0; 20];
616        let mut tw = WriteBuf::new(&mut buf);
617
618        tw.start_struct(&TLVTag::Anonymous).unwrap();
619        tw.u8(&TLVTag::Anonymous, 12).unwrap();
620        tw.u8(&TLVTag::Context(1), 13).unwrap();
621        tw.u16(&TLVTag::Anonymous, 0x1212).unwrap();
622        tw.u16(&TLVTag::Context(2), 0x1313).unwrap();
623        tw.start_array(&TLVTag::Context(3)).unwrap();
624        tw.bool(&TLVTag::Anonymous, true).unwrap();
625        tw.end_container().unwrap();
626        tw.end_container().unwrap();
627        assert_eq!(
628            buf,
629            [21, 4, 12, 36, 1, 13, 5, 0x12, 0x012, 37, 2, 0x13, 0x13, 54, 3, 9, 24, 24, 0, 0]
630        );
631    }
632
633    #[test]
634    fn test_write_overflow() {
635        let mut buf = [0; 6];
636        let mut tw = WriteBuf::new(&mut buf);
637
638        tw.u8(&TLVTag::Anonymous, 12).unwrap();
639        tw.u8(&TLVTag::Context(1), 13).unwrap();
640        if tw.u16(&TLVTag::Anonymous, 12).is_ok() {
641            panic!("This should have returned error")
642        }
643        if tw.u16(&TLVTag::Context(2), 13).is_ok() {
644            panic!("This should have returned error")
645        }
646        assert_eq!(buf, [4, 12, 36, 1, 13, 4]);
647    }
648
649    #[test]
650    fn test_put_str8() {
651        let mut buf = [0; 20];
652        let mut tw = WriteBuf::new(&mut buf);
653
654        tw.u8(&TLVTag::Context(1), 13).unwrap();
655        tw.str(&TLVTag::Anonymous, &[10, 11, 12, 13, 14]).unwrap();
656        tw.u16(&TLVTag::Context(2), 0x1313).unwrap();
657        tw.str(&TLVTag::Context(3), &[20, 21, 22]).unwrap();
658        assert_eq!(
659            buf,
660            [36, 1, 13, 16, 5, 10, 11, 12, 13, 14, 37, 2, 0x13, 0x13, 48, 3, 3, 20, 21, 22]
661        );
662    }
663
664    #[test]
665    fn test_matter_spec_examples() {
666        let mut buf = [0; 200];
667        let mut tw = WriteBuf::new(&mut buf);
668
669        // Boolean false
670
671        tw.bool(&TLVTag::Anonymous, false).unwrap();
672        assert_eq!(&[0x08], tw.as_slice());
673
674        // Boolean true
675
676        tw.reset();
677        tw.bool(&TLVTag::Anonymous, true).unwrap();
678        assert_eq!(&[0x09], tw.as_slice());
679
680        // Signed Integer, 1-octet, value 42
681
682        tw.reset();
683        tw.i8(&TLVTag::Anonymous, 42).unwrap();
684        assert_eq!(&[0x00, 0x2a], tw.as_slice());
685
686        // Signed Integer, 1-octet, value -17
687
688        tw.reset();
689        tw.i32(&TLVTag::Anonymous, -17).unwrap();
690        assert_eq!(&[0x00, 0xef], tw.as_slice());
691
692        // Unsigned Integer, 1-octet, value 42U
693
694        tw.reset();
695        tw.u8(&TLVTag::Anonymous, 42).unwrap();
696        assert_eq!(&[0x04, 0x2a], tw.as_slice());
697
698        // Signed Integer, 2-octet, value 422
699
700        tw.reset();
701        tw.i16(&TLVTag::Anonymous, 422).unwrap();
702        assert_eq!(&[0x01, 0xa6, 0x01], tw.as_slice());
703
704        // Signed Integer, 4-octet, value -170000
705
706        tw.reset();
707        tw.i32(&TLVTag::Anonymous, -170000).unwrap();
708        assert_eq!(&[0x02, 0xf0, 0x67, 0xfd, 0xff], tw.as_slice());
709
710        // Signed Integer, 8-octet, value 40000000000
711
712        tw.reset();
713        tw.i64(&TLVTag::Anonymous, 40000000000).unwrap();
714        assert_eq!(
715            &[0x03, 0x00, 0x90, 0x2f, 0x50, 0x09, 0x00, 0x00, 0x00],
716            tw.as_slice()
717        );
718
719        // UTF-8 String, 1-octet length, "Hello!"
720
721        tw.reset();
722        tw.utf8(&TLVTag::Anonymous, "Hello!").unwrap();
723        assert_eq!(
724            &[0x0c, 0x06, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21],
725            tw.as_slice()
726        );
727
728        // UTF-8 String, 1-octet length, "Tschüs"
729
730        tw.reset();
731        tw.utf8i(
732            &TLVTag::Anonymous,
733            "Tschüs".len(),
734            "Tschüs".as_bytes().iter().copied(),
735        )
736        .unwrap();
737        assert_eq!(
738            &[0x0c, 0x07, 0x54, 0x73, 0x63, 0x68, 0xc3, 0xbc, 0x73],
739            tw.as_slice()
740        );
741
742        // Octet String, 1-octet length, octets 00 01 02 03 04
743
744        tw.reset();
745        tw.str(&TLVTag::Anonymous, &[0x00, 0x01, 0x02, 0x03, 0x04])
746            .unwrap();
747        assert_eq!(&[0x10, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04], tw.as_slice());
748
749        // Null
750
751        tw.reset();
752        tw.tlv(&TLVTag::Anonymous, &TLVValue::Null).unwrap();
753        assert_eq!(&[0x14], tw.as_slice());
754
755        // Single precision floating point 0.0
756
757        tw.reset();
758        tw.tlv(&TLVTag::Anonymous, &TLVValue::F32(0.0)).unwrap();
759        assert_eq!(&[0x0a, 0x00, 0x00, 0x00, 0x00], tw.as_slice());
760
761        // Single precision floating point (1.0 / 3.0)
762
763        tw.reset();
764        tw.f32(&TLVTag::Anonymous, 1.0 / 3.0).unwrap();
765        assert_eq!(&[0x0a, 0xab, 0xaa, 0xaa, 0x3e], tw.as_slice());
766
767        // Single precision floating point 17.9
768
769        tw.reset();
770        tw.f32(&TLVTag::Anonymous, 17.9).unwrap();
771        assert_eq!(&[0x0a, 0x33, 0x33, 0x8f, 0x41], tw.as_slice());
772
773        // Single precision floating point infinity
774
775        tw.reset();
776        tw.f32(&TLVTag::Anonymous, f32::INFINITY).unwrap();
777        assert_eq!(&[0x0a, 0x00, 0x00, 0x80, 0x7f], tw.as_slice());
778
779        // Single precision floating point negative infinity
780
781        tw.reset();
782        tw.f32(&TLVTag::Anonymous, f32::NEG_INFINITY).unwrap();
783        assert_eq!(&[0x0a, 0x00, 0x00, 0x80, 0xff], tw.as_slice());
784
785        // Double precision floating point 0.0
786
787        tw.reset();
788        tw.f64(&TLVTag::Anonymous, 0.0).unwrap();
789        assert_eq!(
790            &[0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
791            tw.as_slice()
792        );
793
794        // Double precision floating point (1.0 / 3.0)
795
796        tw.reset();
797        tw.f64(&TLVTag::Anonymous, 1.0 / 3.0).unwrap();
798        assert_eq!(
799            &[0x0b, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f],
800            tw.as_slice()
801        );
802
803        // Double precision floating point 17.9
804
805        tw.reset();
806        tw.f64(&TLVTag::Anonymous, 17.9).unwrap();
807        assert_eq!(
808            &[0x0b, 0x66, 0x66, 0x66, 0x66, 0x66, 0xe6, 0x31, 0x40],
809            tw.as_slice()
810        );
811
812        // Double precision floating point infinity (∞)
813
814        tw.reset();
815        tw.f64(&TLVTag::Anonymous, f64::INFINITY).unwrap();
816        assert_eq!(
817            &[0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f],
818            tw.as_slice()
819        );
820
821        // Double precision floating point negative infinity
822
823        tw.reset();
824        tw.f64(&TLVTag::Anonymous, f64::NEG_INFINITY).unwrap();
825        assert_eq!(
826            &[0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff],
827            tw.as_slice()
828        );
829
830        // Empty Structure, {}
831
832        tw.reset();
833        tw.start_struct(&TLVTag::Anonymous).unwrap();
834        tw.end_container().unwrap();
835        assert_eq!(&[0x15, 0x18], tw.as_slice());
836
837        // Empty Array, []
838
839        tw.reset();
840        tw.start_array(&TLVTag::Anonymous).unwrap();
841        tw.end_container().unwrap();
842        assert_eq!(&[0x16, 0x18], tw.as_slice());
843
844        // Empty List, []
845
846        tw.reset();
847        tw.start_list(&TLVTag::Anonymous).unwrap();
848        tw.end_container().unwrap();
849        assert_eq!(&[0x17, 0x18], tw.as_slice());
850
851        // Structure, two context specific tags, Signed Integer, 1 octet values, {0 = 42, 1 = -17}
852
853        tw.reset();
854        tw.start_struct(&TLVTag::Anonymous).unwrap();
855        tw.i8(&TLVTag::Context(0), 42).unwrap();
856        tw.i32(&TLVTag::Context(1), -17).unwrap();
857        tw.end_container().unwrap();
858        assert_eq!(
859            &[0x15, 0x20, 0x00, 0x2a, 0x20, 0x01, 0xef, 0x18],
860            tw.as_slice()
861        );
862
863        // Array, Signed Integer, 1-octet values, [0, 1, 2, 3, 4]
864
865        tw.reset();
866        tw.start_array(&TLVTag::Anonymous).unwrap();
867        for i in 0..5 {
868            tw.i8(&TLVTag::Anonymous, i as i8).unwrap();
869        }
870        tw.end_container().unwrap();
871        assert_eq!(
872            &[0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x18],
873            tw.as_slice()
874        );
875
876        // List, mix of anonymous and context tags, Signed Integer, 1 octet values, [[1, 0 = 42, 2, 3, 0 = -17]]
877
878        tw.reset();
879        tw.start_list(&TLVTag::Anonymous).unwrap();
880        tw.i64(&TLVTag::Anonymous, 1).unwrap();
881        tw.i16(&TLVTag::Context(0), 42).unwrap();
882        tw.i8(&TLVTag::Anonymous, 2).unwrap();
883        tw.i8(&TLVTag::Anonymous, 3).unwrap();
884        tw.i32(&TLVTag::Context(0), -17).unwrap();
885        tw.end_container().unwrap();
886        assert_eq!(
887            &[0x17, 0x00, 0x01, 0x20, 0x00, 0x2a, 0x00, 0x02, 0x00, 0x03, 0x20, 0x00, 0xef, 0x18],
888            tw.as_slice()
889        );
890
891        // Array, mix of element types, [42, -170000, {}, 17.9, "Hello!"]
892
893        tw.reset();
894        tw.start_array(&TLVTag::Anonymous).unwrap();
895        tw.i64(&TLVTag::Anonymous, 42).unwrap();
896        tw.i64(&TLVTag::Anonymous, -170000).unwrap();
897        tw.start_struct(&TLVTag::Anonymous).unwrap();
898        tw.end_container().unwrap();
899        tw.f32(&TLVTag::Anonymous, 17.9).unwrap();
900        tw.utf8(&TLVTag::Anonymous, "Hello!").unwrap();
901        tw.end_container().unwrap();
902        assert_eq!(
903            &[
904                0x16, 0x00, 0x2a, 0x02, 0xf0, 0x67, 0xfd, 0xff, 0x15, 0x18, 0x0a, 0x33, 0x33, 0x8f,
905                0x41, 0x0c, 0x06, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x18,
906            ],
907            tw.as_slice()
908        );
909
910        // Anonymous tag, Unsigned Integer, 1-octet value, 42U
911
912        tw.reset();
913        tw.u64(&TLVTag::Anonymous, 42).unwrap();
914        assert_eq!(&[0x04, 0x2a], tw.as_slice());
915
916        // Context tag 1, Unsigned Integer, 1-octet value, 1 = 42U
917
918        tw.reset();
919        tw.u64(&TLVTag::Context(1), 42).unwrap();
920        assert_eq!(&[0x24, 0x01, 0x2a], tw.as_slice());
921
922        // Common profile tag 1, Unsigned Integer, 1-octet value, Matter::1 = 42U
923
924        tw.reset();
925        tw.u64(&TLVTag::CommonPrf16(1), 42).unwrap();
926        assert_eq!(&[0x44, 0x01, 0x00, 0x2a], tw.as_slice());
927
928        // Common profile tag 100000, Unsigned Integer, 1-octet value, Matter::100000 = 42U
929
930        tw.reset();
931        tw.u64(&TLVTag::CommonPrf32(100000), 42).unwrap();
932        assert_eq!(&[0x64, 0xa0, 0x86, 0x01, 0x00, 0x2a], tw.as_slice());
933
934        // Fully qualified tag, Vendor ID 0xFFF1/65521, pro­file number 0xDEED/57069,
935        // 2-octet tag 1, Unsigned Integer, 1-octet value 42, 65521::57069:1 = 42U
936
937        tw.reset();
938        tw.u64(
939            &TLVTag::FullQual48 {
940                vendor_id: 65521,
941                profile: 57069,
942                tag: 1,
943            },
944            42,
945        )
946        .unwrap();
947        assert_eq!(
948            &[0xc4, 0xf1, 0xff, 0xed, 0xde, 0x01, 0x00, 0x2a],
949            tw.as_slice()
950        );
951
952        // Fully qualified tag, Vendor ID 0xFFF1/65521, pro­file number 0xDEED/57069,
953        // 4-octet tag 0xAA55FEED/2857762541, Unsigned Integer, 1-octet value 42, 65521::57069:2857762541 = 42U
954
955        tw.reset();
956        tw.u64(
957            &TLVTag::FullQual64 {
958                vendor_id: 65521,
959                profile: 57069,
960                tag: 2857762541,
961            },
962            42,
963        )
964        .unwrap();
965        assert_eq!(
966            &[0xe4, 0xf1, 0xff, 0xed, 0xde, 0xed, 0xfe, 0x55, 0xaa, 0x2a],
967            tw.as_slice()
968        );
969
970        // Structure with the fully qualified tag, Vendor ID 0xFFF1/65521, profile number 0xDEED/57069,
971        // 2-octet tag 1. The structure contains a single ele­ment labeled using a fully qualified tag under
972        // the same profile, with 2-octet tag 0xAA55/43605. 65521::57069:1 = {65521::57069:43605 = 42U}
973
974        tw.reset();
975        tw.start_struct(&TLVTag::FullQual48 {
976            vendor_id: 65521,
977            profile: 57069,
978            tag: 1,
979        })
980        .unwrap();
981        tw.u64(
982            &TLVTag::FullQual48 {
983                vendor_id: 65521,
984                profile: 57069,
985                tag: 43605,
986            },
987            42,
988        )
989        .unwrap();
990        tw.end_container().unwrap();
991        assert_eq!(
992            &[
993                0xd5, 0xf1, 0xff, 0xed, 0xde, 0x01, 0x00, 0xc4, 0xf1, 0xff, 0xed, 0xde, 0x55, 0xaa,
994                0x2a, 0x18,
995            ],
996            tw.as_slice()
997        );
998    }
999}