Skip to main content

rs_matter/im/encoding/attr/
write_builder.rs

1/*
2 *
3 *    Copyright (c) 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
18//! Streaming TLV builders for `WriteRequestMessage` and its sub-structures.
19//!
20//! Contrast with the snapshot-style `WriteRequestBuilder` in
21//! `crate::im::client`: that one is a plain data struct that holds a
22//! pre-built `&[AttrData]` and serialises it via `ToTLV` after the
23//! caller has assembled all paths and payloads in some other buffer.
24//!
25//! The builders in *this* module write **directly** into the outbound
26//! `TLVWrite` (typically the exchange's TX `WriteBuf`), field by field,
27//! in the same typestate style the codegen emits for cluster structs.
28//! No intermediate `Vec` of `AttrData`, no separate buffer for the
29//! attribute payload — every byte ends up in the TX buffer exactly
30//! once.
31//!
32//! # Layout
33//!
34//! Per Matter Core spec `WriteRequestMessage` is an
35//! anonymous-tagged struct with four fields:
36//!
37//! | Tag | Field             | Type                |
38//! |-----|-------------------|---------------------|
39//! | 0   | SuppressResponse  | bool? (omit = false)|
40//! | 1   | TimedRequest      | bool? (omit = false)|
41//! | 2   | WriteRequests     | array[AttrData]     |
42//! | 3   | MoreChunkedMessages | bool?             |
43//!
44//! `AttrData` (`AttributeDataIB`) is itself a struct with:
45//!
46//! | Tag | Field      | Type        |
47//! |-----|------------|-------------|
48//! | 0   | DataVersion | u32?       |
49//! | 1   | Path        | AttrPath   |
50//! | 2   | Data        | any TLV    |
51//!
52//! `AttrPath` (`AttributePathIB`) is a *list* (not a struct, per IM
53//! spec) with optional fields at tags 0..5 (tag_compression, node,
54//! endpoint, cluster, attr, list_index). For attribute writes the
55//! common shape is concrete `(endpoint, cluster, attr)`; the builder
56//! below exposes those three as required, leaving the wildcard /
57//! list-index variants out of the first-cut surface — they can be
58//! added via additional setters when a real use case appears.
59//!
60//! # Usage
61//!
62//! Optional fields are **implicitly skipped** — just don't call their
63//! setter. Later-field methods are available on earlier states, so a
64//! minimal call writes only the mandatory `WriteRequests` array:
65//!
66//! ```ignore
67//! exchange.send_with(|_, wb| {
68//!     let parent = TLVWriteParent::new("WriteRequest", wb);
69//!     WriteReqBuilder::new(parent)?
70//!         // SuppressResponse + TimedRequest implicitly skipped:
71//!         .write_requests()?
72//!             .push()?
73//!                 // DataVersion implicitly skipped:
74//!                 .path(1, 0x0006 /* OnOff */, 0x4001 /* OnTime */)?
75//!                 .data(|w| 60u16.to_tlv(&TLVTag::Context(2), w))?
76//!             .end()?
77//!         .end()?
78//!         // MoreChunkedMessages implicitly skipped:
79//!         .end()?;
80//!     Ok(Some(OpCode::WriteRequest.into()))
81//! }).await
82//! ```
83//!
84//! To include an optional field, just call its setter — that locks
85//! out earlier-state alternatives via the typestate, so call order
86//! still matches the spec field order:
87//!
88//! ```ignore
89//! WriteReqBuilder::new(parent)?
90//!     .timed_request(true)?            // SuppressResponse skipped
91//!     .write_requests()?
92//!         .push()?
93//!             .data_version(42)?       // optimistic-concurrency write
94//!             .path(1, 0x001F /* ACL */, 0x0000 /* ACL */)?
95//!             .data(|w| acl_value.to_tlv(&TLVTag::Context(2), w))?
96//!         .end()?
97//!     .end()?
98//!     .more_chunks(true)?              // explicit chunked write
99//!     .end()?
100//! ```
101
102use core::marker::PhantomData;
103
104use crate::error::Error;
105use crate::im::encoding::{AttrId, ClusterId, EndptId};
106use crate::im::{AttrDataTag, AttrPathTag, WriteReqTag, IM_REVISION};
107use crate::tlv::{TLVBuilder, TLVBuilderParent, TLVTag, TLVWrite};
108
109/// Streaming builder for a `WriteRequestMessage`. Type-state-tagged
110/// so the compiler enforces in-order field writes. Optional fields
111/// are **implicitly skipped** by simply not calling their setter —
112/// later-field setters are available on all earlier states.
113///
114/// Field-state values (the state *after* each named field has been
115/// written or implicitly skipped):
116/// - `0`: nothing written yet
117/// - `1`: past `SuppressResponse` (written or skipped)
118/// - `2`: past `TimedRequest`
119/// - `3`: past `WriteRequests` array (closed)
120/// - `4`: past `MoreChunkedMessages`
121/// - `5`: past `InteractionModelRevision` (auto-injected at default
122///   value [`IM_REVISION`] by `end()` if the optional setter wasn't
123///   called)
124///
125/// In practice almost every call is `WriteReqBuilder::new(p)?
126/// .write_requests()? … .end()?` — `SuppressResponse` and
127/// `MoreChunkedMessages` default to absent (= false on the wire),
128/// `TimedRequest` is only set when issuing a timed write.
129pub struct WriteReqBuilder<P, const F: usize = 0> {
130    p: P,
131}
132
133impl<P> WriteReqBuilder<P, 0>
134where
135    P: TLVBuilderParent,
136{
137    /// Begin a new `WriteRequestMessage` — opens a struct at the
138    /// given tag on the parent's writer. For top-level use (the
139    /// usual case) pass `&TLVTag::Anonymous`.
140    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
141        p.writer().start_struct(tag)?;
142        Ok(Self { p })
143    }
144}
145
146impl<P> TLVBuilder<P> for WriteReqBuilder<P, 0>
147where
148    P: TLVBuilderParent,
149{
150    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
151        Self::new(parent, tag)
152    }
153
154    fn unchecked_into_parent(self) -> P {
155        self.p
156    }
157}
158
159// ---------------------------------------------------------------------
160// `suppress_response` — settable from state 0; advances to state 1.
161// ---------------------------------------------------------------------
162impl<P> WriteReqBuilder<P, 0>
163where
164    P: TLVBuilderParent,
165{
166    /// Write the optional `SuppressResponse` field. Omit (don't call)
167    /// to leave the field absent on the wire.
168    pub fn suppress_response(mut self, value: bool) -> Result<WriteReqBuilder<P, 1>, Error> {
169        self.p
170            .writer()
171            .bool(&TLVTag::Context(WriteReqTag::SuppressResponse as u8), value)?;
172        Ok(WriteReqBuilder { p: self.p })
173    }
174}
175
176// ---------------------------------------------------------------------
177// `timed_request` — settable from state 0 or 1; advances to state 2.
178// ---------------------------------------------------------------------
179impl<P> WriteReqBuilder<P, 0>
180where
181    P: TLVBuilderParent,
182{
183    /// Write the optional `TimedRequest` field. Calling this from
184    /// state 0 implicitly skips `SuppressResponse`.
185    pub fn timed_request(self, value: bool) -> Result<WriteReqBuilder<P, 2>, Error> {
186        WriteReqBuilder::<P, 1> { p: self.p }.timed_request(value)
187    }
188}
189
190impl<P> WriteReqBuilder<P, 1>
191where
192    P: TLVBuilderParent,
193{
194    /// Write the optional `TimedRequest` field.
195    pub fn timed_request(mut self, value: bool) -> Result<WriteReqBuilder<P, 2>, Error> {
196        self.p
197            .writer()
198            .bool(&TLVTag::Context(WriteReqTag::TimedRequest as u8), value)?;
199        Ok(WriteReqBuilder { p: self.p })
200    }
201}
202
203// ---------------------------------------------------------------------
204// `write_requests` — required; openable from state 0, 1, or 2.
205// ---------------------------------------------------------------------
206impl<P> WriteReqBuilder<P, 0>
207where
208    P: TLVBuilderParent,
209{
210    /// Open the `WriteRequests` array. Calling from state 0
211    /// implicitly skips both `SuppressResponse` and `TimedRequest`.
212    pub fn write_requests(self) -> Result<AttrDataArrayBuilder<WriteReqBuilder<P, 3>>, Error> {
213        WriteReqBuilder::<P, 2> { p: self.p }.write_requests()
214    }
215}
216
217impl<P> WriteReqBuilder<P, 1>
218where
219    P: TLVBuilderParent,
220{
221    /// Open the `WriteRequests` array, implicitly skipping `TimedRequest`.
222    pub fn write_requests(self) -> Result<AttrDataArrayBuilder<WriteReqBuilder<P, 3>>, Error> {
223        WriteReqBuilder::<P, 2> { p: self.p }.write_requests()
224    }
225}
226
227impl<P> WriteReqBuilder<P, 2>
228where
229    P: TLVBuilderParent,
230{
231    /// Open the `WriteRequests` array. Each `.push()` on the returned
232    /// builder starts one `AttrData` entry; close with `.end()` to
233    /// return to the message builder.
234    pub fn write_requests(self) -> Result<AttrDataArrayBuilder<WriteReqBuilder<P, 3>>, Error> {
235        AttrDataArrayBuilder::new(
236            WriteReqBuilder { p: self.p },
237            &TLVTag::Context(WriteReqTag::WriteRequests as u8),
238        )
239    }
240}
241
242// ---------------------------------------------------------------------
243// `more_chunks` — settable from state 3; advances to state 4.
244// ---------------------------------------------------------------------
245impl<P> WriteReqBuilder<P, 3>
246where
247    P: TLVBuilderParent,
248{
249    /// Write the optional `MoreChunkedMessages` field. Omit (don't
250    /// call — go straight to `.end()`) for single-chunk writes.
251    pub fn more_chunks(mut self, value: bool) -> Result<WriteReqBuilder<P, 4>, Error> {
252        self.p
253            .writer()
254            .bool(&TLVTag::Context(WriteReqTag::MoreChunked as u8), value)?;
255        Ok(WriteReqBuilder { p: self.p })
256    }
257}
258
259// ---------------------------------------------------------------------
260// `end` — closable from state 3 or 4.
261// ---------------------------------------------------------------------
262impl<P> WriteReqBuilder<P, 3>
263where
264    P: TLVBuilderParent,
265{
266    /// Close the message struct, implicitly skipping
267    /// `MoreChunkedMessages`. Returns the parent.
268    pub fn end(self) -> Result<P, Error> {
269        WriteReqBuilder::<P, 4> { p: self.p }.end()
270    }
271}
272
273impl<P> WriteReqBuilder<P, 3>
274where
275    P: TLVBuilderParent,
276{
277    /// Write `InteractionModelRevision`, implicitly skipping
278    /// `MoreChunkedMessages`. This is a typestate skip-shim mirroring
279    /// the pattern PR #447 established for `SuppressResponse` /
280    /// `TimedRequest` on `InvReqBuilder`: callers who don't populate
281    /// the optional preceding field can advance straight to setting
282    /// (or auto-injecting) `InteractionModelRevision` without an
283    /// explicit no-op transition.
284    pub fn interaction_model_revision(self, value: u8) -> Result<WriteReqBuilder<P, 5>, Error> {
285        WriteReqBuilder::<P, 4> { p: self.p }.interaction_model_revision(value)
286    }
287}
288
289impl<P> WriteReqBuilder<P, 4>
290where
291    P: TLVBuilderParent,
292{
293    /// Write the mandatory-on-the-wire `InteractionModelRevision`
294    /// field (Matter Core: value is `13` since Matter
295    /// 1.3, unchanged in 1.4 and 1.5). Optional at the API level —
296    /// omit and `end()` injects [`IM_REVISION`] automatically.
297    pub fn interaction_model_revision(mut self, value: u8) -> Result<WriteReqBuilder<P, 5>, Error> {
298        self.p.writer().u8(
299            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
300            value,
301        )?;
302        Ok(WriteReqBuilder { p: self.p })
303    }
304
305    /// Close the message struct, auto-injecting
306    /// `InteractionModelRevision` at its default value
307    /// [`IM_REVISION`]. Returns the parent.
308    pub fn end(self) -> Result<P, Error> {
309        self.interaction_model_revision(IM_REVISION)?.end()
310    }
311}
312
313impl<P> WriteReqBuilder<P, 5>
314where
315    P: TLVBuilderParent,
316{
317    /// Close the message struct and return the parent.
318    pub fn end(mut self) -> Result<P, Error> {
319        self.p.writer().end_container()?;
320        Ok(self.p)
321    }
322}
323
324// Bridge: the `WriteReqBuilder<P, F>` is itself a parent
325// for sub-builders (the array of AttrData). Forward `writer()` to the
326// inner parent.
327impl<P, const F: usize> TLVBuilderParent for WriteReqBuilder<P, F>
328where
329    P: TLVBuilderParent,
330{
331    type Write = P::Write;
332
333    fn writer(&mut self) -> &mut Self::Write {
334        self.p.writer()
335    }
336}
337
338impl<P, const F: usize> core::fmt::Debug for WriteReqBuilder<P, F>
339where
340    P: core::fmt::Debug,
341{
342    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
343        write!(f, "{:?}::WriteRequestMessage<{}>", self.p, F)
344    }
345}
346
347#[cfg(feature = "defmt")]
348impl<P, const F: usize> defmt::Format for WriteReqBuilder<P, F>
349where
350    P: defmt::Format,
351{
352    fn format(&self, fmt: defmt::Formatter<'_>) {
353        defmt::write!(fmt, "{:?}::WriteRequestMessage<{}>", self.p, F);
354    }
355}
356
357/// Array builder for the `WriteRequests` field. The array is opened
358/// in `write_requests()`; this type provides `.push()` (start one
359/// entry) and `.end()` (close the array, return to the message
360/// builder).
361pub struct AttrDataArrayBuilder<P> {
362    p: P,
363}
364
365impl<P> AttrDataArrayBuilder<P>
366where
367    P: TLVBuilderParent,
368{
369    /// Begin a new `AttrData` array — opens an array at the given
370    /// tag on the parent's writer. Use the [`TLVBuilder`] trait
371    /// constructor for the standard call.
372    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
373        p.writer().start_array(tag)?;
374        Ok(Self { p })
375    }
376
377    /// Start a new `AttrData` entry. The returned [`AttrDataBuilder`]
378    /// terminates with `.end()` which returns this array builder.
379    pub fn push(self) -> Result<AttrDataBuilder<Self, 0>, Error> {
380        // Each AttrData is an anonymous-tagged struct (we're inside an array).
381        AttrDataBuilder::new(self, &TLVTag::Anonymous)
382    }
383
384    /// Close the array and return the message builder.
385    pub fn end(mut self) -> Result<P, Error> {
386        self.p.writer().end_container()?;
387        Ok(self.p)
388    }
389}
390
391impl<P> TLVBuilder<P> for AttrDataArrayBuilder<P>
392where
393    P: TLVBuilderParent,
394{
395    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
396        Self::new(parent, tag)
397    }
398
399    fn unchecked_into_parent(self) -> P {
400        self.p
401    }
402}
403
404impl<P> TLVBuilderParent for AttrDataArrayBuilder<P>
405where
406    P: TLVBuilderParent,
407{
408    type Write = P::Write;
409
410    fn writer(&mut self) -> &mut Self::Write {
411        self.p.writer()
412    }
413}
414
415impl<P> core::fmt::Debug for AttrDataArrayBuilder<P>
416where
417    P: core::fmt::Debug,
418{
419    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
420        write!(f, "{:?}[]", self.p)
421    }
422}
423
424#[cfg(feature = "defmt")]
425impl<P> defmt::Format for AttrDataArrayBuilder<P>
426where
427    P: defmt::Format,
428{
429    fn format(&self, fmt: defmt::Formatter<'_>) {
430        defmt::write!(fmt, "{:?}[]", self.p);
431    }
432}
433
434/// Streaming builder for a single `AttrData` entry inside the
435/// `WriteRequests` array.
436///
437/// Field-state values:
438/// - `0`: nothing written yet
439/// - `1`: `DataVersion` decided
440/// - `2`: `Path` written
441/// - `3`: `Data` written (struct can be closed)
442pub struct AttrDataBuilder<P, const F: usize = 0> {
443    p: P,
444    _f: PhantomData<[(); F]>,
445}
446
447impl<P> AttrDataBuilder<P, 0>
448where
449    P: TLVBuilderParent,
450{
451    /// Begin a new `AttrData` entry — opens a struct at the given
452    /// tag. Use `&TLVTag::Anonymous` when pushed into the
453    /// `WriteRequests` array (the typical case).
454    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
455        p.writer().start_struct(tag)?;
456        Ok(Self { p, _f: PhantomData })
457    }
458}
459
460impl<P> TLVBuilder<P> for AttrDataBuilder<P, 0>
461where
462    P: TLVBuilderParent,
463{
464    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
465        Self::new(parent, tag)
466    }
467
468    fn unchecked_into_parent(self) -> P {
469        self.p
470    }
471}
472
473// ---------------------------------------------------------------------
474// `data_version` — settable from state 0; advances to state 1.
475// ---------------------------------------------------------------------
476impl<P> AttrDataBuilder<P, 0>
477where
478    P: TLVBuilderParent,
479{
480    /// Write the optional `DataVersion` field — used for
481    /// optimistic-concurrency writes that should fail on stale data.
482    /// Omit (don't call) for unconditional writes; subsequent `path*`
483    /// methods are also available on state 0 and implicitly skip
484    /// this field.
485    pub fn data_version(mut self, value: u32) -> Result<AttrDataBuilder<P, 1>, Error> {
486        self.p
487            .writer()
488            .u32(&TLVTag::Context(AttrDataTag::DataVer as u8), value)?;
489        Ok(AttrDataBuilder {
490            p: self.p,
491            _f: PhantomData,
492        })
493    }
494}
495
496// ---------------------------------------------------------------------
497// `path` / `path_from` — required; available from state 0 or 1.
498// ---------------------------------------------------------------------
499impl<P> AttrDataBuilder<P, 0>
500where
501    P: TLVBuilderParent,
502{
503    /// Write the concrete `(endpoint, cluster, attribute)` path,
504    /// implicitly skipping `DataVersion`.
505    pub fn path(
506        self,
507        endpoint: EndptId,
508        cluster: ClusterId,
509        attr: AttrId,
510    ) -> Result<AttrDataBuilder<P, 2>, Error> {
511        AttrDataBuilder::<P, 1> {
512            p: self.p,
513            _f: PhantomData,
514        }
515        .path(endpoint, cluster, attr)
516    }
517
518    /// Write the path from an existing [`crate::im::AttrPath`],
519    /// implicitly skipping `DataVersion`. Used by the
520    /// snapshot→streaming bridge in `ImClient::write`.
521    pub fn path_from(self, path: &crate::im::AttrPath) -> Result<AttrDataBuilder<P, 2>, Error> {
522        AttrDataBuilder::<P, 1> {
523            p: self.p,
524            _f: PhantomData,
525        }
526        .path_from(path)
527    }
528}
529
530impl<P> AttrDataBuilder<P, 1>
531where
532    P: TLVBuilderParent,
533{
534    /// Write the concrete `(endpoint, cluster, attribute)` path. This
535    /// is the typical shape for attribute writes; wildcards aren't
536    /// generally meaningful for writes and aren't exposed here. The
537    /// path is encoded as a *list* (per spec `AttributePathIB`).
538    pub fn path(
539        mut self,
540        endpoint: EndptId,
541        cluster: ClusterId,
542        attr: AttrId,
543    ) -> Result<AttrDataBuilder<P, 2>, Error> {
544        let w = self.p.writer();
545        w.start_list(&TLVTag::Context(AttrDataTag::Path as u8))?;
546        w.u16(&TLVTag::Context(AttrPathTag::Endpoint as u8), endpoint)?;
547        w.u32(&TLVTag::Context(AttrPathTag::Cluster as u8), cluster)?;
548        w.u32(&TLVTag::Context(AttrPathTag::Attribute as u8), attr)?;
549        w.end_container()?;
550        Ok(AttrDataBuilder {
551            p: self.p,
552            _f: PhantomData,
553        })
554    }
555
556    /// Write the path from an existing [`crate::im::AttrPath`]. Used
557    /// by the snapshot→streaming bridge in `ImClient::write` so the
558    /// pre-built `AttrPath` (which may carry wildcards or
559    /// `list_index`) is re-emitted faithfully. New call sites should
560    /// prefer [`Self::path`].
561    pub fn path_from(mut self, path: &crate::im::AttrPath) -> Result<AttrDataBuilder<P, 2>, Error> {
562        use crate::tlv::ToTLV;
563        path.to_tlv(&TLVTag::Context(AttrDataTag::Path as u8), self.p.writer())?;
564        Ok(AttrDataBuilder {
565            p: self.p,
566            _f: PhantomData,
567        })
568    }
569}
570
571impl<P> AttrDataBuilder<P, 2>
572where
573    P: TLVBuilderParent,
574{
575    /// Write the attribute value into the `Data` slot.
576    ///
577    /// Per Matter Core spec `AttributeDataIB.Data` is "any
578    /// TLV value." The closure receives the parent's `TLVWrite` and
579    /// **must** emit exactly one TLV element tagged
580    /// `TLVTag::Context(2)` (i.e. `AttrDataTag::Data`). The IM-level
581    /// builder can't enforce the inner type because the schema lives
582    /// in the attribute the path named, not the IM message; the
583    /// closure body is where the caller asserts the schema match
584    /// (typically by calling a codegen-emitted typed writer).
585    ///
586    /// Idiomatic call patterns:
587    ///
588    /// ```ignore
589    /// // Any `T: ToTLV`:
590    /// .data(|w| 60u16.to_tlv(&TLVTag::Context(AttrDataTag::Data as u8), w))?
591    ///
592    /// // A bool, raw:
593    /// .data(|w| w.bool(&TLVTag::Context(AttrDataTag::Data as u8), true))?
594    ///
595    /// // A codegen-emitted typed helper that already knows the tag:
596    /// .data(|w| on_off::write_on_time_value(60u16, w))?
597    /// ```
598    pub fn data<F>(mut self, f: F) -> Result<AttrDataBuilder<P, 3>, Error>
599    where
600        F: FnOnce(&mut P::Write) -> Result<(), Error>,
601    {
602        f(self.p.writer())?;
603        Ok(AttrDataBuilder {
604            p: self.p,
605            _f: PhantomData,
606        })
607    }
608
609    /// Open the `Data` slot as a typed sub-builder.
610    ///
611    /// Closure-free counterpart to [`data`](Self::data) — hand back
612    /// the codegen-emitted typed value builder for the attribute,
613    /// already opened at `AttrDataTag::Data`. The caller fills the
614    /// value, then calls `.end()` on the sub-builder; that close
615    /// writes `Data`'s closing tag (for struct/array-valued attrs)
616    /// and yields an [`AttrDataBuilder<P, 3>`]. The caller then
617    /// `.end()`s once more to close the `AttrData` entry struct
618    /// (the "double-end" pattern of the IM-client glue).
619    ///
620    /// Useful for struct- or array-valued attributes (e.g. ACL
621    /// entries). For scalars prefer the closure-based [`data`].
622    ///
623    /// Soundness of the phantom typestate advance: see
624    /// [`CmdDataBuilder::data_builder`].
625    pub fn data_builder<B>(self) -> Result<B, Error>
626    where
627        B: TLVBuilder<AttrDataBuilder<P, 3>>,
628    {
629        let advanced = AttrDataBuilder {
630            p: self.p,
631            _f: PhantomData,
632        };
633        B::new(advanced, &TLVTag::Context(AttrDataTag::Data as u8))
634    }
635}
636
637impl<P> AttrDataBuilder<P, 3>
638where
639    P: TLVBuilderParent,
640{
641    /// Close the `AttrData` struct and return the array builder so
642    /// the caller can `.push()` another entry or `.end()` the array.
643    pub fn end(mut self) -> Result<P, Error> {
644        self.p.writer().end_container()?;
645        Ok(self.p)
646    }
647}
648
649impl<P, const F: usize> TLVBuilderParent for AttrDataBuilder<P, F>
650where
651    P: TLVBuilderParent,
652{
653    type Write = P::Write;
654
655    fn writer(&mut self) -> &mut Self::Write {
656        self.p.writer()
657    }
658}
659
660impl<P, const F: usize> core::fmt::Debug for AttrDataBuilder<P, F>
661where
662    P: core::fmt::Debug,
663{
664    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
665        write!(f, "{:?}::AttrData<{}>", self.p, F)
666    }
667}
668
669#[cfg(feature = "defmt")]
670impl<P, const F: usize> defmt::Format for AttrDataBuilder<P, F>
671where
672    P: defmt::Format,
673{
674    fn format(&self, fmt: defmt::Formatter<'_>) {
675        defmt::write!(fmt, "{:?}::AttrData<{}>", self.p, F);
676    }
677}