Skip to main content

rs_matter/im/encoding/attr/
subscribe_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 builder for `SubscribeRequestMessage`.
19//!
20//! Same typestate-machine + implicit-skip convention as
21//! [`crate::im::encoding::attr::read_builder::ReadReqBuilder`]: mandatory
22//! fields must be written in order (`keep_subs`, `min_int_floor`,
23//! `max_int_ceil`, `fabric_filtered`); optional intermediate fields
24//! (`AttributeRequests`, `EventRequests`, `EventFilters`,
25//! `DataVersionFilters`) are skipped simply by not calling their
26//! setter — later-field setters are available on earlier states so
27//! the user can write a minimal request in one straight chain. The
28//! `AttrPath` array sub-builder is re-used from
29//! [`crate::im::encoding::attr::read_builder`] since the wire shape is
30//! identical to the read variant.
31//!
32//! # Layout
33//!
34//! Per Matter Core spec `SubscribeRequestMessage` is an
35//! anonymous-tagged struct with seven fields plus a reserved gap at
36//! tag 6:
37//!
38//! | Tag | Field             | Type                   | Required |
39//! |-----|-------------------|------------------------|----------|
40//! | 0   | KeepSubs          | bool                   | **yes**  |
41//! | 1   | MinIntFloor       | u16                    | **yes**  |
42//! | 2   | MaxIntCeil        | u16                    | **yes**  |
43//! | 3   | AttributeRequests | array[AttrPath]?       | no       |
44//! | 4   | EventRequests     | array[EventPath]?      | no       |
45//! | 5   | EventFilters      | array[EventFilter]?    | no       |
46//! | 6   | *(reserved)*      | —                      | —        |
47//! | 7   | FabricFiltered    | bool                   | **yes**  |
48//! | 8   | DataVersionFilters| array[DataVersionFilter]? | no    |
49//!
50//! Event-side fields and dataver filters can be passed as pre-built
51//! slices via the `*_from(...)` helpers, matching what the
52//! `ReadReqBuilder` exposes.
53//!
54//! # Usage
55//!
56//! ```ignore
57//! exchange.send_with(|_, wb| {
58//!     let parent = TLVWriteParent::new("SubscribeRequest", wb);
59//!     SubscribeReqBuilder::new(parent)?
60//!         .keep_subs(true)?
61//!         .min_int_floor(0)?
62//!         .max_int_ceil(60)?
63//!         .attr_requests()?
64//!             .push()?.endpoint(1).cluster(0x0006).attr(0x0000).end()?
65//!         .end()?
66//!         .fabric_filtered(true)?
67//!         .end()?;
68//!     Ok(Some(OpCode::SubscribeRequest.into()))
69//! }).await
70//! ```
71
72use crate::error::Error;
73use crate::im::{
74    AttrPath, AttrPathArrayBuilder, DataVersionFilter, EventFilter, EventPath, SubscribeReqTag,
75    IM_REVISION,
76};
77use crate::tlv::{TLVBuilder, TLVBuilderParent, TLVTag, TLVWrite, ToTLV};
78
79/// Streaming builder for a `SubscribeRequestMessage`. Type-state-tagged
80/// so the compiler enforces in-order field writes; optional fields
81/// are implicitly skipped by not calling their setter.
82///
83/// Field-state values (state *after* each named field has been
84/// written or implicitly skipped):
85/// - `0`: nothing written yet
86/// - `1`: past `KeepSubs`
87/// - `2`: past `MinIntFloor`
88/// - `3`: past `MaxIntCeil`
89/// - `4`: past `AttributeRequests`
90/// - `5`: past `EventRequests`
91/// - `6`: past `EventFilters`
92/// - `7`: past `FabricFiltered` (mandatory; no implicit-skip from
93///   states 3-6 to `end()` — caller must invoke `fabric_filtered`)
94/// - `8`: past `DataVersionFilters`
95/// - `9`: past `InteractionModelRevision` (auto-injected at default
96///   value [`IM_REVISION`] by `end()` if the optional setter wasn't
97///   called)
98pub struct SubscribeReqBuilder<P, const F: usize = 0> {
99    p: P,
100}
101
102impl<P> SubscribeReqBuilder<P, 0>
103where
104    P: TLVBuilderParent,
105{
106    /// Begin a new `SubscribeRequestMessage` — opens a struct at the
107    /// given tag on the parent's writer. For top-level use (the usual
108    /// case) pass `&TLVTag::Anonymous`.
109    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
110        p.writer().start_struct(tag)?;
111        Ok(Self { p })
112    }
113}
114
115impl<P> TLVBuilder<P> for SubscribeReqBuilder<P, 0>
116where
117    P: TLVBuilderParent,
118{
119    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
120        Self::new(parent, tag)
121    }
122
123    fn unchecked_into_parent(self) -> P {
124        self.p
125    }
126}
127
128// ---------------------------------------------------------------------
129// `keep_subs` — mandatory; settable from state 0 only.
130// ---------------------------------------------------------------------
131impl<P> SubscribeReqBuilder<P, 0>
132where
133    P: TLVBuilderParent,
134{
135    /// Write the mandatory `KeepSubs` field. `true` (the typical
136    /// value) instructs the peer to keep any existing subscriptions
137    /// for this fabric+peer pair alongside the new one; `false`
138    /// terminates them before establishing this subscription.
139    pub fn keep_subs(mut self, value: bool) -> Result<SubscribeReqBuilder<P, 1>, Error> {
140        self.p
141            .writer()
142            .bool(&TLVTag::Context(SubscribeReqTag::KeepSubs as u8), value)?;
143        Ok(SubscribeReqBuilder { p: self.p })
144    }
145}
146
147// ---------------------------------------------------------------------
148// `min_int_floor` — mandatory; settable from state 1 only.
149// ---------------------------------------------------------------------
150impl<P> SubscribeReqBuilder<P, 1>
151where
152    P: TLVBuilderParent,
153{
154    /// Write the mandatory `MinIntervalFloor` field (seconds). The
155    /// minimum reporting interval the peer is allowed to use; the
156    /// server may pick any interval at or above this floor.
157    pub fn min_int_floor(mut self, value: u16) -> Result<SubscribeReqBuilder<P, 2>, Error> {
158        self.p
159            .writer()
160            .u16(&TLVTag::Context(SubscribeReqTag::MinIntFloor as u8), value)?;
161        Ok(SubscribeReqBuilder { p: self.p })
162    }
163}
164
165// ---------------------------------------------------------------------
166// `max_int_ceil` — mandatory; settable from state 2 only.
167// ---------------------------------------------------------------------
168impl<P> SubscribeReqBuilder<P, 2>
169where
170    P: TLVBuilderParent,
171{
172    /// Write the mandatory `MaxIntervalCeiling` field (seconds). The
173    /// peer MUST report no less frequently than this even if nothing
174    /// has changed (heartbeat). The server may pick any interval at
175    /// or below this ceiling — see Matter Core spec.
176    pub fn max_int_ceil(mut self, value: u16) -> Result<SubscribeReqBuilder<P, 3>, Error> {
177        self.p
178            .writer()
179            .u16(&TLVTag::Context(SubscribeReqTag::MaxIntCeil as u8), value)?;
180        Ok(SubscribeReqBuilder { p: self.p })
181    }
182}
183
184// ---------------------------------------------------------------------
185// `attr_requests` — openable from state 3; advances to state 4.
186// ---------------------------------------------------------------------
187impl<P> SubscribeReqBuilder<P, 3>
188where
189    P: TLVBuilderParent,
190{
191    /// Open the optional `AttributeRequests` array. Each `.push()`
192    /// yields an [`AttrPathBuilder`]; close with `.end()` to advance
193    /// to the next message field.
194    pub fn attr_requests(self) -> Result<AttrPathArrayBuilder<SubscribeReqBuilder<P, 4>>, Error> {
195        AttrPathArrayBuilder::new(
196            SubscribeReqBuilder { p: self.p },
197            &TLVTag::Context(SubscribeReqTag::AttrRequests as u8),
198        )
199    }
200
201    /// Write `AttributeRequests` from a pre-built slice. Convenience
202    /// for callers that already have an `&[AttrPath]` on hand.
203    pub fn attr_requests_from(
204        mut self,
205        paths: &[AttrPath],
206    ) -> Result<SubscribeReqBuilder<P, 4>, Error> {
207        let w = self.p.writer();
208        w.start_array(&TLVTag::Context(SubscribeReqTag::AttrRequests as u8))?;
209        for p in paths {
210            p.to_tlv(&TLVTag::Anonymous, &mut *w)?;
211        }
212        w.end_container()?;
213        Ok(SubscribeReqBuilder { p: self.p })
214    }
215}
216
217// ---------------------------------------------------------------------
218// `event_requests` — openable from state 3 or 4; advances to state 5.
219// ---------------------------------------------------------------------
220impl<P> SubscribeReqBuilder<P, 3>
221where
222    P: TLVBuilderParent,
223{
224    /// Write `EventRequests` from a pre-built slice, implicitly
225    /// skipping `AttributeRequests`.
226    pub fn event_requests_from(
227        self,
228        paths: &[EventPath],
229    ) -> Result<SubscribeReqBuilder<P, 5>, Error> {
230        SubscribeReqBuilder::<P, 4> { p: self.p }.event_requests_from(paths)
231    }
232}
233
234impl<P> SubscribeReqBuilder<P, 4>
235where
236    P: TLVBuilderParent,
237{
238    /// Write `EventRequests` from a pre-built slice.
239    pub fn event_requests_from(
240        mut self,
241        paths: &[EventPath],
242    ) -> Result<SubscribeReqBuilder<P, 5>, Error> {
243        let w = self.p.writer();
244        w.start_array(&TLVTag::Context(SubscribeReqTag::EventRequests as u8))?;
245        for p in paths {
246            p.to_tlv(&TLVTag::Anonymous, &mut *w)?;
247        }
248        w.end_container()?;
249        Ok(SubscribeReqBuilder { p: self.p })
250    }
251}
252
253// ---------------------------------------------------------------------
254// `event_filters` — settable from state 3, 4, or 5; advances to 6.
255// ---------------------------------------------------------------------
256impl<P> SubscribeReqBuilder<P, 3>
257where
258    P: TLVBuilderParent,
259{
260    pub fn event_filters_from(
261        self,
262        filters: &[EventFilter],
263    ) -> Result<SubscribeReqBuilder<P, 6>, Error> {
264        SubscribeReqBuilder::<P, 5> { p: self.p }.event_filters_from(filters)
265    }
266}
267
268impl<P> SubscribeReqBuilder<P, 4>
269where
270    P: TLVBuilderParent,
271{
272    pub fn event_filters_from(
273        self,
274        filters: &[EventFilter],
275    ) -> Result<SubscribeReqBuilder<P, 6>, Error> {
276        SubscribeReqBuilder::<P, 5> { p: self.p }.event_filters_from(filters)
277    }
278}
279
280impl<P> SubscribeReqBuilder<P, 5>
281where
282    P: TLVBuilderParent,
283{
284    /// Write `EventFilters` from a pre-built slice.
285    pub fn event_filters_from(
286        mut self,
287        filters: &[EventFilter],
288    ) -> Result<SubscribeReqBuilder<P, 6>, Error> {
289        let w = self.p.writer();
290        w.start_array(&TLVTag::Context(SubscribeReqTag::EventFilters as u8))?;
291        for ef in filters {
292            ef.to_tlv(&TLVTag::Anonymous, &mut *w)?;
293        }
294        w.end_container()?;
295        Ok(SubscribeReqBuilder { p: self.p })
296    }
297}
298
299// ---------------------------------------------------------------------
300// `fabric_filtered` — *mandatory*; settable from state 3, 4, 5, or 6.
301// ---------------------------------------------------------------------
302impl<P> SubscribeReqBuilder<P, 3>
303where
304    P: TLVBuilderParent,
305{
306    /// Write the mandatory `FabricFiltered` field, implicitly
307    /// skipping `AttributeRequests`, `EventRequests`, and
308    /// `EventFilters`.
309    pub fn fabric_filtered(self, value: bool) -> Result<SubscribeReqBuilder<P, 7>, Error> {
310        SubscribeReqBuilder::<P, 6> { p: self.p }.fabric_filtered(value)
311    }
312}
313
314impl<P> SubscribeReqBuilder<P, 4>
315where
316    P: TLVBuilderParent,
317{
318    pub fn fabric_filtered(self, value: bool) -> Result<SubscribeReqBuilder<P, 7>, Error> {
319        SubscribeReqBuilder::<P, 6> { p: self.p }.fabric_filtered(value)
320    }
321}
322
323impl<P> SubscribeReqBuilder<P, 5>
324where
325    P: TLVBuilderParent,
326{
327    pub fn fabric_filtered(self, value: bool) -> Result<SubscribeReqBuilder<P, 7>, Error> {
328        SubscribeReqBuilder::<P, 6> { p: self.p }.fabric_filtered(value)
329    }
330}
331
332impl<P> SubscribeReqBuilder<P, 6>
333where
334    P: TLVBuilderParent,
335{
336    /// Write the mandatory `FabricFiltered` field. `true` (the
337    /// typical value) constrains the subscription to attribute /
338    /// event reports for the accessing fabric only.
339    pub fn fabric_filtered(mut self, value: bool) -> Result<SubscribeReqBuilder<P, 7>, Error> {
340        self.p.writer().bool(
341            &TLVTag::Context(SubscribeReqTag::FabricFiltered as u8),
342            value,
343        )?;
344        Ok(SubscribeReqBuilder { p: self.p })
345    }
346}
347
348// ---------------------------------------------------------------------
349// `dataver_filters` — settable from state 7; advances to state 8.
350// ---------------------------------------------------------------------
351impl<P> SubscribeReqBuilder<P, 7>
352where
353    P: TLVBuilderParent,
354{
355    /// Write `DataVersionFilters` from a pre-built slice. Used by
356    /// caching clients to skip attributes whose data version hasn't
357    /// advanced since the last cached read.
358    pub fn dataver_filters_from(
359        mut self,
360        filters: &[DataVersionFilter],
361    ) -> Result<SubscribeReqBuilder<P, 8>, Error> {
362        let w = self.p.writer();
363        w.start_array(&TLVTag::Context(SubscribeReqTag::DataVersionFilters as u8))?;
364        for f in filters {
365            f.to_tlv(&TLVTag::Anonymous, &mut *w)?;
366        }
367        w.end_container()?;
368        Ok(SubscribeReqBuilder { p: self.p })
369    }
370}
371
372// ---------------------------------------------------------------------
373// `end` — closable from state 7 or 8.
374// ---------------------------------------------------------------------
375impl<P> SubscribeReqBuilder<P, 7>
376where
377    P: TLVBuilderParent,
378{
379    /// Close the message struct, implicitly skipping
380    /// `DataVersionFilters`. Returns the parent.
381    pub fn end(self) -> Result<P, Error> {
382        SubscribeReqBuilder::<P, 8> { p: self.p }.end()
383    }
384}
385
386impl<P> SubscribeReqBuilder<P, 7>
387where
388    P: TLVBuilderParent,
389{
390    /// Write `InteractionModelRevision`, implicitly skipping
391    /// `DataVersionFilters`. This is a typestate skip-shim mirroring
392    /// the pattern PR #447 established for `SuppressResponse` /
393    /// `TimedRequest` on `InvReqBuilder`: callers who don't populate
394    /// the optional preceding field can advance straight to setting
395    /// (or auto-injecting) `InteractionModelRevision` without an
396    /// explicit no-op transition.
397    pub fn interaction_model_revision(self, value: u8) -> Result<SubscribeReqBuilder<P, 9>, Error> {
398        SubscribeReqBuilder::<P, 8> { p: self.p }.interaction_model_revision(value)
399    }
400}
401
402impl<P> SubscribeReqBuilder<P, 8>
403where
404    P: TLVBuilderParent,
405{
406    /// Write the mandatory-on-the-wire `InteractionModelRevision`
407    /// field (Matter Core: value is `13` since Matter
408    /// 1.3, unchanged in 1.4 and 1.5). Optional at the API level —
409    /// omit and `end()` injects [`IM_REVISION`] automatically.
410    pub fn interaction_model_revision(
411        mut self,
412        value: u8,
413    ) -> Result<SubscribeReqBuilder<P, 9>, Error> {
414        self.p.writer().u8(
415            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
416            value,
417        )?;
418        Ok(SubscribeReqBuilder { p: self.p })
419    }
420
421    /// Close the message struct, auto-injecting
422    /// `InteractionModelRevision` at its default value
423    /// [`IM_REVISION`]. Returns the parent.
424    pub fn end(self) -> Result<P, Error> {
425        self.interaction_model_revision(IM_REVISION)?.end()
426    }
427}
428
429impl<P> SubscribeReqBuilder<P, 9>
430where
431    P: TLVBuilderParent,
432{
433    /// Close the message struct and return the parent.
434    pub fn end(mut self) -> Result<P, Error> {
435        self.p.writer().end_container()?;
436        Ok(self.p)
437    }
438}
439
440impl<P, const F: usize> TLVBuilderParent for SubscribeReqBuilder<P, F>
441where
442    P: TLVBuilderParent,
443{
444    type Write = P::Write;
445
446    fn writer(&mut self) -> &mut Self::Write {
447        self.p.writer()
448    }
449}
450
451impl<P, const F: usize> core::fmt::Debug for SubscribeReqBuilder<P, F>
452where
453    P: core::fmt::Debug,
454{
455    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
456        write!(f, "{:?}::SubscribeRequestMessage<{}>", self.p, F)
457    }
458}
459
460#[cfg(feature = "defmt")]
461impl<P, const F: usize> defmt::Format for SubscribeReqBuilder<P, F>
462where
463    P: defmt::Format,
464{
465    fn format(&self, fmt: defmt::Formatter<'_>) {
466        defmt::write!(fmt, "{:?}::SubscribeRequestMessage<{}>", self.p, F);
467    }
468}