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}