Skip to main content

rs_matter/im/encoding/attr/
subscribe.rs

1/*
2 *
3 *    Copyright (c) 2025-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 core::fmt;
19
20use crate::error::Error;
21use crate::im::{AttrPath, DataVersionFilter, EventFilter, EventPath, IM_REVISION};
22use crate::tlv::{FromTLV, TLVArray, TLVElement, TagType, ToTLV};
23use crate::utils::storage::WriteBuf;
24
25/// A request to subscribe to attributes and events from a Matter device.
26///
27/// Corresponds to the `SubscribeRequestMessage` TLV structure in the Interaction Model.
28#[derive(Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
29#[tlvargs(lifetime = "'a")]
30pub struct SubscribeReq<'a>(TLVElement<'a>);
31
32impl<'a> SubscribeReq<'a> {
33    /// Create a new `SubscribeReq` from a `TLVElement`.
34    pub const fn new(element: TLVElement<'a>) -> Self {
35        Self(element)
36    }
37
38    /// Return `Ok(true)` if this subscription request should keep existing subscriptions.
39    pub fn keep_subs(&self) -> Result<bool, Error> {
40        self.0.r#struct()?.find_ctx(0)?.bool()
41    }
42
43    /// Return the minimum interval floor for this subscription request.
44    pub fn min_int_floor(&self) -> Result<u16, Error> {
45        self.0.r#struct()?.find_ctx(1)?.u16()
46    }
47
48    /// Return the maximum interval ceiling for this subscription request.
49    pub fn max_int_ceil(&self) -> Result<u16, Error> {
50        self.0.r#struct()?.find_ctx(2)?.u16()
51    }
52
53    /// Return the attribute requests in this subscription request, if any.
54    pub fn attr_requests(&self) -> Result<Option<TLVArray<'a, AttrPath>>, Error> {
55        Option::from_tlv(&self.0.r#struct()?.find_ctx(3)?)
56    }
57
58    /// Return the event requests in this subscription request, if any.
59    pub fn event_requests(&self) -> Result<Option<TLVArray<'a, EventPath>>, Error> {
60        Option::from_tlv(&self.0.r#struct()?.find_ctx(4)?)
61    }
62
63    /// Return the event filters in this subscription request, if any.
64    pub fn event_filters(&self) -> Result<Option<TLVArray<'a, EventFilter>>, Error> {
65        Option::from_tlv(&self.0.r#struct()?.find_ctx(5)?)
66    }
67
68    /// Return `Ok(true)` if this subscription request is fabric-filtered.
69    pub fn fabric_filtered(&self) -> Result<bool, Error> {
70        self.0.r#struct()?.find_ctx(7)?.bool()
71    }
72
73    /// Return the data version filters in this subscription request, if any.
74    pub fn dataver_filters(&self) -> Result<Option<TLVArray<'a, DataVersionFilter>>, Error> {
75        Option::from_tlv(&self.0.r#struct()?.find_ctx(8)?)
76    }
77}
78
79impl fmt::Debug for SubscribeReq<'_> {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("SubscribeReqRef")
82            .field("keep_subs", &self.keep_subs())
83            .field("min_int_floor", &self.min_int_floor())
84            .field("max_int_ceil", &self.max_int_ceil())
85            .field("attr_requests", &self.attr_requests())
86            .field("event_requests", &self.event_requests())
87            .field("event_filters", &self.event_filters())
88            .field("fabric_filtered", &self.fabric_filtered())
89            .field("dataver_filters", &self.dataver_filters())
90            .finish()
91    }
92}
93
94#[cfg(feature = "defmt")]
95impl defmt::Format for SubscribeReq<'_> {
96    fn format(&self, f: defmt::Formatter<'_>) {
97        defmt::write!(f,
98            "SubscribeReqRef {{\n  keep_subs: {:?},\n  min_int_floor: {:?},\n  max_int_ceil: {:?},\n  attr_requests: {:?},\n  event_requests: {:?},\n  event_filters: {:?},\n  fabric_filtered: {:?},\n  dataver_filters: {:?},\n}}",
99            self.keep_subs(),
100            self.min_int_floor(),
101            self.max_int_ceil(),
102            self.attr_requests(),
103            self.event_requests(),
104            self.event_filters(),
105            self.fabric_filtered(),
106            self.dataver_filters(),
107        )
108    }
109}
110
111/// Tags corresponding to the fields in the `SubscribeRequestMessage`
112/// TLV structure (Matter Core spec). Used by the streaming
113/// subscribe-request builder and any low-level TLV serde callers.
114///
115/// Note the gap at tag 6 — the spec leaves it reserved and the
116/// `FabricFiltered` field jumps from 5 to 7.
117#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
118#[cfg_attr(feature = "defmt", derive(defmt::Format))]
119#[repr(u8)]
120pub enum SubscribeReqTag {
121    KeepSubs = 0,
122    MinIntFloor = 1,
123    MaxIntCeil = 2,
124    AttrRequests = 3,
125    EventRequests = 4,
126    EventFilters = 5,
127    FabricFiltered = 7,
128    DataVersionFilters = 8,
129}
130
131/// A response to a subscription request.
132///
133/// Corresponds to the `SubscribeResponseMessage` TLV structure in the Interaction Model.
134#[derive(Debug, Clone, FromTLV, ToTLV)]
135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
136pub struct SubscribeResp {
137    pub subs_id: u32,
138    // The Context Tags are discontiguous for some reason
139    pub _dummy: Option<u32>,
140    pub max_int: u16,
141    /// `interactionModelRevision` — mandatory in every IM message we send;
142    /// modelled as `Option<u8>` so we tolerate peers that omit it (the C++
143    /// SDK is tolerant in practice).
144    #[tagval(crate::im::encoding::IM_REVISION_TAG)]
145    pub interaction_model_revision: Option<u8>,
146}
147
148impl SubscribeResp {
149    /// Create a new `SubscribeResp` with the given subscription ID and maximum interval.
150    pub fn new(subs_id: u32, max_int: u16) -> Self {
151        Self {
152            subs_id,
153            _dummy: None,
154            max_int,
155            interaction_model_revision: Some(IM_REVISION),
156        }
157    }
158
159    /// Write a `SubscribeResp` message to the provided `WriteBuf`.
160    ///
161    /// Returns a slice of the buffer containing the serialized response.
162    ///
163    /// Arguments:
164    /// - `wb`: A mutable reference to a `WriteBuf` where the response will be written.
165    /// - `subscription_id`: The subscription ID to include in the response.
166    /// - `max_int`: The maximum interval for the subscription to include in the response.
167    pub fn write<'a>(
168        wb: &'a mut WriteBuf,
169        subscription_id: u32,
170        max_int: u16,
171    ) -> Result<&'a [u8], Error> {
172        Self::new(subscription_id, max_int).to_tlv(&TagType::Anonymous, &mut *wb)?;
173        Ok(wb.as_slice())
174    }
175}