matter_interaction/
subscription.rs1#![forbid(unsafe_code)]
8
9use crate::error::ImError;
10use crate::event::{EventFilter, EventPath};
11use crate::path::ReadPath;
12use crate::{expect_message_struct, skip_container, IM_REVISION};
13use matter_codec::{Element, Tag, TlvReader, TlvWriter};
14
15#[derive(Clone, Debug, PartialEq)]
22pub struct SubscribeRequest {
23 pub keep_subscriptions: bool,
26 pub min_interval_floor: u16,
28 pub max_interval_ceiling: u16,
30 pub paths: Vec<ReadPath>,
35 pub event_paths: Vec<EventPath>,
38 pub event_filters: Vec<EventFilter>,
41}
42
43#[derive(Clone, Debug, PartialEq)]
48#[non_exhaustive]
49pub struct SubscribeResponse {
50 pub subscription_id: u32,
52 pub max_interval: u16,
54}
55
56#[must_use]
67#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_subscribe_request(req: &SubscribeRequest) -> Vec<u8> {
69 let mut buf = Vec::with_capacity(48 + req.paths.len() * 24 + req.event_paths.len() * 24);
70 let mut w = TlvWriter::new(&mut buf);
71
72 w.start_structure(Tag::Anonymous)
73 .expect("infallible: vec writer");
74
75 w.put_bool(Tag::Context(0), req.keep_subscriptions)
77 .expect("infallible: vec writer");
78
79 w.put_uint(Tag::Context(1), u64::from(req.min_interval_floor))
81 .expect("infallible: vec writer");
82
83 w.put_uint(Tag::Context(2), u64::from(req.max_interval_ceiling))
85 .expect("infallible: vec writer");
86
87 w.start_array(Tag::Context(3))
89 .expect("infallible: vec writer");
90 for p in &req.paths {
91 w.start_list(Tag::Anonymous)
92 .expect("infallible: vec writer");
93 if let Some(ep) = p.endpoint {
94 w.put_uint(Tag::Context(2), u64::from(ep))
95 .expect("infallible: vec writer");
96 }
97 if let Some(cl) = p.cluster {
98 w.put_uint(Tag::Context(3), u64::from(cl))
99 .expect("infallible: vec writer");
100 }
101 if let Some(at) = p.attribute {
102 w.put_uint(Tag::Context(4), u64::from(at))
103 .expect("infallible: vec writer");
104 }
105 w.end_container().expect("infallible: vec writer");
106 }
107 w.end_container().expect("infallible: vec writer"); if !req.event_paths.is_empty() {
111 w.start_array(Tag::Context(4))
112 .expect("infallible: vec writer");
113 for p in &req.event_paths {
114 p.write(&mut w).expect("infallible: vec writer");
115 }
116 w.end_container().expect("infallible: vec writer");
117 }
118
119 if !req.event_filters.is_empty() {
121 w.start_array(Tag::Context(5))
122 .expect("infallible: vec writer");
123 for f in &req.event_filters {
124 f.write(&mut w).expect("infallible: vec writer");
125 }
126 w.end_container().expect("infallible: vec writer");
127 }
128
129 w.put_bool(Tag::Context(7), false)
133 .expect("infallible: vec writer");
134
135 w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
137 .expect("infallible: vec writer");
138
139 w.end_container().expect("infallible: vec writer");
140 buf
141}
142
143pub fn parse_subscribe_response(bytes: &[u8]) -> Result<SubscribeResponse, ImError> {
153 let mut r = TlvReader::new(bytes);
154 expect_message_struct(&mut r)?;
155
156 let mut subscription_id: Option<u32> = None;
157 let mut max_interval: Option<u16> = None;
158
159 loop {
160 match r.next()? {
161 None | Some(Element::ContainerEnd) => break,
162 Some(Element::Scalar {
163 tag: Tag::Context(0),
164 value: matter_codec::Value::Uint(n),
165 }) => {
166 subscription_id = Some(u32::try_from(n).map_err(|_| {
167 ImError::UnexpectedValue("SubscribeResponse.subscriptionId exceeds u32")
168 })?);
169 }
170 Some(Element::Scalar {
171 tag: Tag::Context(2),
172 value: matter_codec::Value::Uint(n),
173 }) => {
174 max_interval = Some(u16::try_from(n).map_err(|_| {
175 ImError::UnexpectedValue("SubscribeResponse.maxInterval exceeds u16")
176 })?);
177 }
178 Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
179 Some(_) => {}
180 }
181 }
182
183 Ok(SubscribeResponse {
184 subscription_id: subscription_id
185 .ok_or(ImError::MissingField("SubscribeResponse.subscriptionId"))?,
186 max_interval: max_interval.ok_or(ImError::MissingField("SubscribeResponse.maxInterval"))?,
187 })
188}
189
190#[must_use]
198#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_status_response(status: u8) -> Vec<u8> {
200 let mut buf = Vec::with_capacity(16);
201 let mut w = TlvWriter::new(&mut buf);
202
203 w.start_structure(Tag::Anonymous)
204 .expect("infallible: vec writer");
205 w.put_uint(Tag::Context(0), u64::from(status))
206 .expect("infallible: vec writer");
207 w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
208 .expect("infallible: vec writer");
209 w.end_container().expect("infallible: vec writer");
210 buf
211}
212
213#[cfg(test)]
214mod tests {
215 #![allow(clippy::unwrap_used)]
216 use super::*;
217 use matter_codec::ContainerKind;
218
219 #[test]
220 fn status_response_success_has_expected_structure() {
221 let bytes = build_status_response(0);
222 let mut r = TlvReader::new(&bytes);
223 assert!(matches!(
225 r.next().unwrap(),
226 Some(Element::ContainerStart {
227 tag: Tag::Anonymous,
228 kind: ContainerKind::Structure
229 })
230 ));
231 assert!(matches!(
233 r.next().unwrap(),
234 Some(Element::Scalar {
235 tag: Tag::Context(0),
236 value: matter_codec::Value::Uint(0)
237 })
238 ));
239 assert!(matches!(
241 r.next().unwrap(),
242 Some(Element::Scalar {
243 tag: Tag::Context(0xFF),
244 value: matter_codec::Value::Uint(11)
245 })
246 ));
247 }
248
249 #[test]
250 fn subscribe_request_has_expected_structure() {
251 let req = SubscribeRequest {
252 keep_subscriptions: false,
253 min_interval_floor: 1,
254 max_interval_ceiling: 30,
255 paths: vec![ReadPath::concrete(1, 0x06, 0x0000)],
256 event_paths: vec![],
257 event_filters: vec![],
258 };
259 let bytes = build_subscribe_request(&req);
260 let mut r = TlvReader::new(&bytes);
261 assert!(matches!(
263 r.next().unwrap(),
264 Some(Element::ContainerStart {
265 tag: Tag::Anonymous,
266 kind: ContainerKind::Structure
267 })
268 ));
269 assert!(matches!(
271 r.next().unwrap(),
272 Some(Element::Scalar {
273 tag: Tag::Context(0),
274 value: matter_codec::Value::Bool(false)
275 })
276 ));
277 assert!(matches!(
279 r.next().unwrap(),
280 Some(Element::Scalar {
281 tag: Tag::Context(1),
282 value: matter_codec::Value::Uint(1)
283 })
284 ));
285 assert!(matches!(
287 r.next().unwrap(),
288 Some(Element::Scalar {
289 tag: Tag::Context(2),
290 value: matter_codec::Value::Uint(30)
291 })
292 ));
293 assert!(matches!(
295 r.next().unwrap(),
296 Some(Element::ContainerStart {
297 tag: Tag::Context(3),
298 kind: ContainerKind::Array
299 })
300 ));
301 }
302
303 #[test]
304 fn parse_subscribe_response_roundtrip() {
305 let mut buf = Vec::new();
307 let mut w = TlvWriter::new(&mut buf);
308 w.start_structure(Tag::Anonymous).unwrap();
309 w.put_uint(Tag::Context(0), 0x1234_5678_u64).unwrap(); w.put_uint(Tag::Context(2), 30_u64).unwrap(); w.put_uint(Tag::Context(0xFF), 11_u64).unwrap(); w.end_container().unwrap();
313
314 let result = parse_subscribe_response(&buf).unwrap();
315 assert_eq!(result.subscription_id, 0x1234_5678);
316 assert_eq!(result.max_interval, 30);
317 }
318}