1#![forbid(unsafe_code)]
4
5use crate::error::ImError;
6use crate::event::{EventFilter, EventPath};
7use crate::path::attribute_path_and_append_from_value;
8pub use crate::path::{AttributePath, ReadPath};
9use crate::{
10 expect_message_struct, read_container_members, read_container_value, skip_container,
11 IM_REVISION,
12};
13use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter, Value};
14
15#[must_use]
25#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_read_request_full(
27 attr_paths: &[ReadPath],
28 event_paths: &[EventPath],
29 event_filters: &[EventFilter],
30) -> Vec<u8> {
31 let mut buf = Vec::new();
32 let mut w = TlvWriter::new(&mut buf);
33 w.start_structure(Tag::Anonymous)
34 .expect("infallible: vec writer");
35 if !attr_paths.is_empty() {
36 w.start_array(Tag::Context(0))
37 .expect("infallible: vec writer"); for p in attr_paths {
39 w.start_list(Tag::Anonymous)
40 .expect("infallible: vec writer");
41 if let Some(ep) = p.endpoint {
42 w.put_uint(Tag::Context(2), u64::from(ep))
43 .expect("infallible: vec writer");
44 }
45 if let Some(cl) = p.cluster {
46 w.put_uint(Tag::Context(3), u64::from(cl))
47 .expect("infallible: vec writer");
48 }
49 if let Some(at) = p.attribute {
50 w.put_uint(Tag::Context(4), u64::from(at))
51 .expect("infallible: vec writer");
52 }
53 w.end_container().expect("infallible: vec writer");
54 }
55 w.end_container().expect("infallible: vec writer"); }
57 if !event_paths.is_empty() {
58 w.start_array(Tag::Context(1))
59 .expect("infallible: vec writer"); for p in event_paths {
61 p.write(&mut w).expect("infallible: vec writer");
62 }
63 w.end_container().expect("infallible: vec writer");
64 }
65 if !event_filters.is_empty() {
66 w.start_array(Tag::Context(2))
67 .expect("infallible: vec writer"); for f in event_filters {
69 f.write(&mut w).expect("infallible: vec writer");
70 }
71 w.end_container().expect("infallible: vec writer");
72 }
73 w.put_bool(Tag::Context(3), false)
74 .expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
76 .expect("infallible: vec writer");
77 w.end_container().expect("infallible: vec writer");
78 buf
79}
80
81#[must_use]
89pub fn build_read_request_paths(paths: &[ReadPath]) -> Vec<u8> {
90 build_read_request_full(paths, &[], &[])
91}
92
93#[must_use]
99pub fn build_read_request(paths: &[AttributePath]) -> Vec<u8> {
100 let read_paths: Vec<ReadPath> = paths.iter().map(|&p| ReadPath::from(p)).collect();
101 build_read_request_paths(&read_paths)
102}
103
104#[derive(Clone, Debug, PartialEq)]
106#[non_exhaustive]
107pub struct ReportData {
108 pub items: Vec<AttributeReportItem>,
115 pub subscription_id: Option<u32>,
119 pub more_chunked_messages: bool,
123 pub suppress_response: bool,
126 pub events: Vec<crate::event::EventReport>,
129}
130
131impl ReportData {
132 #[must_use]
144 pub fn new(
145 items: Vec<AttributeReportItem>,
146 subscription_id: Option<u32>,
147 more_chunked_messages: bool,
148 suppress_response: bool,
149 ) -> Self {
150 Self {
151 items,
152 subscription_id,
153 more_chunked_messages,
154 suppress_response,
155 events: Vec::new(),
156 }
157 }
158
159 #[must_use]
162 pub fn events(&self) -> &[crate::event::EventReport] {
163 &self.events
164 }
165
166 pub fn attributes(&self) -> impl Iterator<Item = (&AttributePath, &Value)> {
179 self.items
180 .iter()
181 .filter(|it| it.op == ReportOp::Replace)
182 .map(|it| (&it.path, &it.value))
183 }
184}
185
186#[derive(Clone, Debug, PartialEq)]
189#[non_exhaustive]
190pub struct AttributeReportItem {
191 pub path: AttributePath,
193 pub op: ReportOp,
195 pub value: Value,
197 pub data_version: Option<u32>,
199}
200
201impl AttributeReportItem {
202 #[must_use]
209 pub fn new(path: AttributePath, op: ReportOp, value: Value, data_version: Option<u32>) -> Self {
210 Self {
211 path,
212 op,
213 value,
214 data_version,
215 }
216 }
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
221#[non_exhaustive]
222pub enum ReportOp {
223 Replace,
225 Append,
227}
228
229pub fn parse_report_data(bytes: &[u8]) -> Result<ReportData, ImError> {
242 let mut r = TlvReader::new(bytes);
243 expect_message_struct(&mut r)?;
244
245 let mut items: Vec<AttributeReportItem> = Vec::new();
246 let mut events: Vec<crate::event::EventReport> = Vec::new();
247 let mut subscription_id: Option<u32> = None;
248 let mut more_chunked_messages = false;
249 let mut suppress_response = false;
250
251 loop {
255 match r.next()? {
256 None | Some(Element::ContainerEnd) => break,
257 Some(Element::Scalar {
259 tag: Tag::Context(0),
260 value: Value::Uint(n),
261 }) => {
262 subscription_id = Some(u32::try_from(n).map_err(|_| {
263 ImError::UnexpectedValue("ReportData.subscriptionId exceeds u32")
264 })?);
265 }
266 Some(Element::ContainerStart {
268 tag: Tag::Context(1),
269 kind: ContainerKind::Array,
270 }) => parse_attribute_reports(&mut r, &mut items)?,
271 Some(Element::Scalar {
273 tag: Tag::Context(3),
274 value: Value::Bool(b),
275 }) => more_chunked_messages = b,
276 Some(Element::Scalar {
278 tag: Tag::Context(4),
279 value: Value::Bool(b),
280 }) => suppress_response = b,
281 Some(Element::ContainerStart {
283 tag: Tag::Context(2),
284 kind: ContainerKind::Array,
285 }) => crate::event::parse_event_reports(&mut r, &mut events)?,
286 Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
288 Some(_) => {}
289 }
290 }
291
292 Ok(ReportData {
293 items,
294 subscription_id,
295 more_chunked_messages,
296 suppress_response,
297 events,
298 })
299}
300
301fn parse_attribute_reports(
305 r: &mut TlvReader<'_>,
306 items: &mut Vec<AttributeReportItem>,
307) -> Result<(), ImError> {
308 loop {
309 match r.next()? {
310 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
311 Some(Element::ContainerEnd) => return Ok(()), Some(Element::ContainerStart {
313 kind: ContainerKind::Structure,
314 ..
315 }) => {
316 if let Some(item) = parse_attribute_report_ib(r)? {
317 items.push(item);
318 }
319 }
320 Some(Element::ContainerStart { .. }) => skip_container(r)?,
321 Some(_) => {}
322 }
323 }
324}
325
326fn parse_attribute_report_ib(
329 r: &mut TlvReader<'_>,
330) -> Result<Option<AttributeReportItem>, ImError> {
331 let mut path = None;
332 let mut value = None;
333 let mut data_version = None;
334 let mut append = false;
335 loop {
336 match r.next()? {
337 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
338 Some(Element::ContainerEnd) => break,
339 Some(Element::ContainerStart {
340 tag: Tag::Context(1),
341 kind: ContainerKind::Structure,
342 }) => {
343 parse_attribute_data(r, &mut path, &mut value, &mut data_version, &mut append)?;
345 }
346 Some(Element::ContainerStart { .. }) => skip_container(r)?,
348 Some(_) => {}
349 }
350 }
351 match (path, value) {
352 (Some(p), Some(v)) => Ok(Some(AttributeReportItem {
353 path: p,
354 op: if append {
355 ReportOp::Append
356 } else {
357 ReportOp::Replace
358 },
359 value: v,
360 data_version,
361 })),
362 (None, None) => Ok(None), (Some(_), None) => Err(ImError::MissingField("AttributeData.Data")),
364 (None, Some(_)) => Err(ImError::MissingField("AttributeData.Path")),
365 }
366}
367
368fn parse_attribute_data(
377 r: &mut TlvReader<'_>,
378 path: &mut Option<AttributePath>,
379 value: &mut Option<Value>,
380 data_version: &mut Option<u32>,
381 append: &mut bool,
382) -> Result<(), ImError> {
383 loop {
384 match r.next()? {
385 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
386 Some(Element::ContainerEnd) => return Ok(()),
387 Some(Element::Scalar {
388 tag: Tag::Context(0),
389 value: Value::Uint(n),
390 }) => {
391 *data_version = Some(u32::try_from(n).map_err(|_| {
392 ImError::UnexpectedValue("AttributeData.DataVersion exceeds u32")
393 })?);
394 }
395 Some(Element::ContainerStart {
396 tag: Tag::Context(1),
397 kind: ContainerKind::List,
398 }) => {
399 let members = read_container_members(r)?;
400 let (p, is_append) = attribute_path_and_append_from_value(&members)?;
401 *path = Some(p);
402 *append = is_append;
403 }
404 Some(Element::Scalar {
405 tag: Tag::Context(2),
406 value: v,
407 }) => *value = Some(v),
408 Some(Element::ContainerStart {
409 tag: Tag::Context(2),
410 kind,
411 }) => *value = Some(read_container_value(r, kind)?),
412 Some(Element::ContainerStart { .. }) => skip_container(r)?,
413 Some(_) => {}
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 #![allow(clippy::unwrap_used, clippy::expect_used)]
421 use super::*;
422 use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
423
424 #[test]
425 fn read_request_has_attribute_requests_array() {
426 let bytes = build_read_request(&[AttributePath {
427 endpoint: 0,
428 cluster: 0x0031,
429 attribute: 0xFFFC, }]);
431 let mut r = TlvReader::new(&bytes);
432 assert!(matches!(
433 r.next().unwrap(),
434 Some(Element::ContainerStart {
435 tag: Tag::Anonymous,
436 kind: ContainerKind::Structure
437 })
438 ));
439 assert!(matches!(
440 r.next().unwrap(),
441 Some(Element::ContainerStart {
442 tag: Tag::Context(0),
443 kind: ContainerKind::Array
444 })
445 ));
446 assert!(matches!(
447 r.next().unwrap(),
448 Some(Element::ContainerStart {
449 tag: Tag::Anonymous,
450 kind: ContainerKind::List
451 })
452 ));
453 assert!(matches!(
454 r.next().unwrap(),
455 Some(Element::Scalar {
456 tag: Tag::Context(2),
457 value: Value::Uint(0)
458 })
459 ));
460 assert!(matches!(
461 r.next().unwrap(),
462 Some(Element::Scalar {
463 tag: Tag::Context(3),
464 value: Value::Uint(0x0031)
465 })
466 ));
467 assert!(matches!(
468 r.next().unwrap(),
469 Some(Element::Scalar {
470 tag: Tag::Context(4),
471 value: Value::Uint(0xFFFC)
472 })
473 ));
474 }
475
476 #[test]
477 fn parses_single_attribute_value() {
478 use matter_codec::{Tag, TlvWriter};
479 let mut buf = Vec::new();
480 let mut w = TlvWriter::new(&mut buf);
481 w.start_structure(Tag::Anonymous).unwrap();
482 w.start_array(Tag::Context(1)).unwrap(); {
484 w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 0).unwrap();
488 w.put_uint(Tag::Context(3), 0x0031).unwrap();
489 w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
490 w.end_container().unwrap();
491 w.put_uint(Tag::Context(2), 0x0001).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); }
495 w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
497 w.end_container().unwrap();
498
499 let report = parse_report_data(&buf).unwrap();
500 let attrs: Vec<_> = report.attributes().collect();
501 assert_eq!(attrs.len(), 1);
502 let (path, value) = attrs[0];
503 assert_eq!(path.endpoint, 0);
504 assert_eq!(path.cluster, 0x0031);
505 assert_eq!(path.attribute, 0xFFFC);
506 assert_eq!(*value, matter_codec::Value::Uint(0x0001));
507 }
508
509 #[test]
510 fn attribute_status_report_is_skipped() {
511 use matter_codec::{Tag, TlvWriter};
512 let mut buf = Vec::new();
513 let mut w = TlvWriter::new(&mut buf);
514 w.start_structure(Tag::Anonymous).unwrap();
515 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(0)).unwrap(); w.put_uint(Tag::Context(0), 0x01).unwrap(); w.end_container().unwrap();
520 w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
523 w.end_container().unwrap();
524
525 let report = parse_report_data(&buf).unwrap();
526 assert_eq!(report.attributes().count(), 0);
527 }
528
529 #[test]
530 fn multi_attribute_report_accumulates_all_entries() {
531 use matter_codec::{Tag, TlvWriter};
532 let mut buf = Vec::new();
533 let mut w = TlvWriter::new(&mut buf);
534 w.start_structure(Tag::Anonymous).unwrap();
535 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap();
539 w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 0).unwrap();
542 w.put_uint(Tag::Context(3), 0x0028).unwrap();
543 w.put_uint(Tag::Context(4), 0x0000).unwrap();
544 w.end_container().unwrap();
545 w.put_uint(Tag::Context(2), 42).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.start_structure(Tag::Anonymous).unwrap();
551 w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 1).unwrap();
554 w.put_uint(Tag::Context(3), 0x0006).unwrap();
555 w.put_uint(Tag::Context(4), 0x0000).unwrap();
556 w.end_container().unwrap();
557 w.put_uint(Tag::Context(2), 1).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
563 w.end_container().unwrap();
564
565 let report = parse_report_data(&buf).unwrap();
566 let attrs: Vec<_> = report.attributes().collect();
567 assert_eq!(attrs.len(), 2);
568
569 let (path0, val0) = attrs[0];
570 assert_eq!(path0.endpoint, 0);
571 assert_eq!(path0.cluster, 0x0028);
572 assert_eq!(path0.attribute, 0x0000);
573 assert_eq!(*val0, matter_codec::Value::Uint(42));
574
575 let (path1, val1) = attrs[1];
576 assert_eq!(path1.endpoint, 1);
577 assert_eq!(path1.cluster, 0x0006);
578 assert_eq!(path1.attribute, 0x0000);
579 assert_eq!(*val1, matter_codec::Value::Uint(1));
580 }
581
582 #[test]
583 fn out_of_range_endpoint_yields_unexpected_value() {
584 use crate::error::ImError;
585 use matter_codec::{Tag, TlvWriter};
586 let mut buf = Vec::new();
587 let mut w = TlvWriter::new(&mut buf);
588 w.start_structure(Tag::Anonymous).unwrap();
589 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 0x0001_0000).unwrap(); w.put_uint(Tag::Context(3), 0x0031).unwrap();
595 w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
596 w.end_container().unwrap();
597 w.put_uint(Tag::Context(2), 0x0001).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
602 w.end_container().unwrap();
603
604 let result = parse_report_data(&buf);
605 assert!(
606 matches!(result, Err(ImError::UnexpectedValue(_))),
607 "expected UnexpectedValue, got {result:?}"
608 );
609 }
610
611 #[test]
612 fn parses_more_chunked_and_suppress_response_flags() {
613 use matter_codec::{Tag, TlvWriter};
614 let mut buf = Vec::new();
616 let mut w = TlvWriter::new(&mut buf);
617 w.start_structure(Tag::Anonymous).unwrap();
618 w.start_array(Tag::Context(1)).unwrap(); w.end_container().unwrap();
620 w.put_bool(Tag::Context(3), true).unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
622 w.end_container().unwrap();
623
624 let report = parse_report_data(&buf).unwrap();
625 assert!(
626 report.more_chunked_messages,
627 "tag 3 must be read after the array"
628 );
629 assert!(!report.suppress_response);
630 }
631
632 #[test]
633 fn parses_suppress_response_after_array() {
634 use matter_codec::{Tag, TlvWriter};
635 let mut buf = Vec::new();
636 let mut w = TlvWriter::new(&mut buf);
637 w.start_structure(Tag::Anonymous).unwrap();
638 w.start_array(Tag::Context(1)).unwrap();
639 w.end_container().unwrap();
640 w.put_bool(Tag::Context(4), true).unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
642 w.end_container().unwrap();
643
644 let report = parse_report_data(&buf).unwrap();
645 assert!(report.suppress_response);
646 assert!(!report.more_chunked_messages);
647 }
648
649 #[test]
650 fn captures_data_version_and_append_op() {
651 use matter_codec::{Tag, TlvWriter};
652 let mut buf = Vec::new();
653 let mut w = TlvWriter::new(&mut buf);
654 w.start_structure(Tag::Anonymous).unwrap();
655 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(0), 7).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 0).unwrap();
661 w.put_uint(Tag::Context(3), 0x1d).unwrap();
662 w.put_uint(Tag::Context(4), 0x0003).unwrap();
663 w.put_null(Tag::Context(5)).unwrap(); w.end_container().unwrap();
665 w.put_uint(Tag::Context(2), 42).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
670 w.end_container().unwrap();
671
672 let report = parse_report_data(&buf).unwrap();
673 assert_eq!(report.items.len(), 1);
674 let it = &report.items[0];
675 assert_eq!(it.op, ReportOp::Append);
676 assert_eq!(it.data_version, Some(7));
677 assert_eq!(it.value, Value::Uint(42));
678 assert_eq!(report.attributes().count(), 0);
680 }
681
682 #[test]
686 fn attributes_view_matches_items_filtered_to_replace() {
687 use matter_codec::{Tag, TlvWriter};
688 let mut buf = Vec::new();
689 let mut w = TlvWriter::new(&mut buf);
690 w.start_structure(Tag::Anonymous).unwrap();
691 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap();
695 w.start_structure(Tag::Context(1)).unwrap();
696 w.start_list(Tag::Context(1)).unwrap();
697 w.put_uint(Tag::Context(2), 0).unwrap();
698 w.put_uint(Tag::Context(3), 0x0028).unwrap();
699 w.put_uint(Tag::Context(4), 0x0000).unwrap();
700 w.end_container().unwrap();
701 w.put_uint(Tag::Context(2), 42).unwrap();
702 w.end_container().unwrap();
703 w.end_container().unwrap();
704
705 w.start_structure(Tag::Anonymous).unwrap();
707 w.start_structure(Tag::Context(1)).unwrap();
708 w.start_list(Tag::Context(1)).unwrap();
709 w.put_uint(Tag::Context(2), 0).unwrap();
710 w.put_uint(Tag::Context(3), 0x001d).unwrap();
711 w.put_uint(Tag::Context(4), 0x0003).unwrap();
712 w.put_null(Tag::Context(5)).unwrap(); w.end_container().unwrap();
714 w.put_uint(Tag::Context(2), 7).unwrap();
715 w.end_container().unwrap();
716 w.end_container().unwrap();
717
718 w.start_structure(Tag::Anonymous).unwrap();
720 w.start_structure(Tag::Context(1)).unwrap();
721 w.start_list(Tag::Context(1)).unwrap();
722 w.put_uint(Tag::Context(2), 1).unwrap();
723 w.put_uint(Tag::Context(3), 0x0006).unwrap();
724 w.put_uint(Tag::Context(4), 0x0000).unwrap();
725 w.end_container().unwrap();
726 w.put_bool(Tag::Context(2), true).unwrap();
727 w.end_container().unwrap();
728 w.end_container().unwrap();
729
730 w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
732 w.end_container().unwrap();
733
734 let report = parse_report_data(&buf).unwrap();
735
736 let expected: Vec<(&AttributePath, &Value)> = report
738 .items
739 .iter()
740 .filter(|it| it.op == ReportOp::Replace)
741 .map(|it| (&it.path, &it.value))
742 .collect();
743 let got: Vec<(&AttributePath, &Value)> = report.attributes().collect();
744 assert_eq!(got, expected);
745
746 assert_eq!(got.len(), 2);
748 assert_eq!(got[0].1, &Value::Uint(42));
749 assert_eq!(got[1].1, &Value::Bool(true));
750 }
751}