1#![forbid(unsafe_code)]
4
5use crate::error::ImError;
6use crate::event::{EventFilter, EventPath};
7pub use crate::path::{AttributePath, ReadPath};
8use crate::{expect_message_struct, read_container_value, skip_container, IM_REVISION};
9use matter_codec::{ContainerKind, Element, Tag, TlvReader, TlvWriter, Value};
10
11#[must_use]
21#[allow(clippy::expect_used, clippy::missing_panics_doc)] pub fn build_read_request_full(
23 attr_paths: &[ReadPath],
24 event_paths: &[EventPath],
25 event_filters: &[EventFilter],
26) -> Vec<u8> {
27 let mut buf = Vec::with_capacity(32 + attr_paths.len() * 24 + event_paths.len() * 24);
28 let mut w = TlvWriter::new(&mut buf);
29 w.start_structure(Tag::Anonymous)
30 .expect("infallible: vec writer");
31 if !attr_paths.is_empty() {
32 w.start_array(Tag::Context(0))
33 .expect("infallible: vec writer"); for p in attr_paths {
35 w.start_list(Tag::Anonymous)
36 .expect("infallible: vec writer");
37 if let Some(ep) = p.endpoint {
38 w.put_uint(Tag::Context(2), u64::from(ep))
39 .expect("infallible: vec writer");
40 }
41 if let Some(cl) = p.cluster {
42 w.put_uint(Tag::Context(3), u64::from(cl))
43 .expect("infallible: vec writer");
44 }
45 if let Some(at) = p.attribute {
46 w.put_uint(Tag::Context(4), u64::from(at))
47 .expect("infallible: vec writer");
48 }
49 w.end_container().expect("infallible: vec writer");
50 }
51 w.end_container().expect("infallible: vec writer"); }
53 if !event_paths.is_empty() {
54 w.start_array(Tag::Context(1))
55 .expect("infallible: vec writer"); for p in event_paths {
57 p.write(&mut w).expect("infallible: vec writer");
58 }
59 w.end_container().expect("infallible: vec writer");
60 }
61 if !event_filters.is_empty() {
62 w.start_array(Tag::Context(2))
63 .expect("infallible: vec writer"); for f in event_filters {
65 f.write(&mut w).expect("infallible: vec writer");
66 }
67 w.end_container().expect("infallible: vec writer");
68 }
69 w.put_bool(Tag::Context(3), false)
70 .expect("infallible: vec writer"); w.put_uint(Tag::Context(0xFF), u64::from(IM_REVISION))
72 .expect("infallible: vec writer");
73 w.end_container().expect("infallible: vec writer");
74 buf
75}
76
77#[must_use]
85pub fn build_read_request_paths(paths: &[ReadPath]) -> Vec<u8> {
86 build_read_request_full(paths, &[], &[])
87}
88
89#[must_use]
95pub fn build_read_request(paths: &[AttributePath]) -> Vec<u8> {
96 let read_paths: Vec<ReadPath> = paths.iter().map(|&p| ReadPath::from(p)).collect();
97 build_read_request_paths(&read_paths)
98}
99
100#[derive(Clone, Debug, PartialEq)]
102#[non_exhaustive]
103pub struct ReportData {
104 pub items: Vec<AttributeReportItem>,
111 pub subscription_id: Option<u32>,
115 pub more_chunked_messages: bool,
119 pub suppress_response: bool,
122 pub events: Vec<crate::event::EventReport>,
125 pub statuses: Vec<(AttributePath, crate::status::ImStatus)>,
133}
134
135impl ReportData {
136 #[must_use]
148 pub fn new(
149 items: Vec<AttributeReportItem>,
150 subscription_id: Option<u32>,
151 more_chunked_messages: bool,
152 suppress_response: bool,
153 ) -> Self {
154 Self {
155 items,
156 subscription_id,
157 more_chunked_messages,
158 suppress_response,
159 events: Vec::new(),
160 statuses: Vec::new(),
161 }
162 }
163
164 #[must_use]
167 pub fn events(&self) -> &[crate::event::EventReport] {
168 &self.events
169 }
170
171 pub fn attributes(&self) -> impl Iterator<Item = (&AttributePath, &Value)> {
184 self.items
185 .iter()
186 .filter(|it| it.op == ReportOp::Replace)
187 .map(|it| (&it.path, &it.value))
188 }
189}
190
191#[derive(Clone, Debug, PartialEq)]
194#[non_exhaustive]
195pub struct AttributeReportItem {
196 pub path: AttributePath,
198 pub op: ReportOp,
200 pub value: Value,
202 pub data_version: Option<u32>,
204}
205
206impl AttributeReportItem {
207 #[must_use]
214 pub fn new(path: AttributePath, op: ReportOp, value: Value, data_version: Option<u32>) -> Self {
215 Self {
216 path,
217 op,
218 value,
219 data_version,
220 }
221 }
222}
223
224#[derive(Clone, Copy, Debug, PartialEq, Eq)]
226#[non_exhaustive]
227pub enum ReportOp {
228 Replace,
230 Append,
232}
233
234pub fn parse_report_data(bytes: &[u8]) -> Result<ReportData, ImError> {
247 let mut r = TlvReader::new(bytes);
248 expect_message_struct(&mut r)?;
249
250 let mut items: Vec<AttributeReportItem> = Vec::new();
251 let mut statuses: Vec<(AttributePath, crate::status::ImStatus)> = Vec::new();
252 let mut events: Vec<crate::event::EventReport> = Vec::new();
253 let mut subscription_id: Option<u32> = None;
254 let mut more_chunked_messages = false;
255 let mut suppress_response = false;
256
257 loop {
261 match r.next()? {
262 None | Some(Element::ContainerEnd) => break,
263 Some(Element::Scalar {
265 tag: Tag::Context(0),
266 value: Value::Uint(n),
267 }) => {
268 subscription_id = Some(u32::try_from(n).map_err(|_| {
269 ImError::UnexpectedValue("ReportData.subscriptionId exceeds u32")
270 })?);
271 }
272 Some(Element::ContainerStart {
274 tag: Tag::Context(1),
275 kind: ContainerKind::Array,
276 }) => parse_attribute_reports(&mut r, &mut items, &mut statuses)?,
277 Some(Element::Scalar {
279 tag: Tag::Context(3),
280 value: Value::Bool(b),
281 }) => more_chunked_messages = b,
282 Some(Element::Scalar {
284 tag: Tag::Context(4),
285 value: Value::Bool(b),
286 }) => suppress_response = b,
287 Some(Element::ContainerStart {
289 tag: Tag::Context(2),
290 kind: ContainerKind::Array,
291 }) => crate::event::parse_event_reports(&mut r, &mut events)?,
292 Some(Element::ContainerStart { .. }) => skip_container(&mut r)?,
294 Some(_) => {}
295 }
296 }
297
298 Ok(ReportData {
299 items,
300 subscription_id,
301 more_chunked_messages,
302 suppress_response,
303 events,
304 statuses,
305 })
306}
307
308enum ReportIb {
311 Data(AttributeReportItem),
312 Status(AttributePath, crate::status::ImStatus),
313 Empty,
314}
315
316fn parse_attribute_reports(
321 r: &mut TlvReader<'_>,
322 items: &mut Vec<AttributeReportItem>,
323 statuses: &mut Vec<(AttributePath, crate::status::ImStatus)>,
324) -> Result<(), ImError> {
325 loop {
326 match r.next()? {
327 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
328 Some(Element::ContainerEnd) => return Ok(()), Some(Element::ContainerStart {
330 kind: ContainerKind::Structure,
331 ..
332 }) => match parse_attribute_report_ib(r)? {
333 ReportIb::Data(item) => items.push(item),
334 ReportIb::Status(path, status) => statuses.push((path, status)),
335 ReportIb::Empty => {}
336 },
337 Some(Element::ContainerStart { .. }) => skip_container(r)?,
338 Some(_) => {}
339 }
340 }
341}
342
343fn parse_attribute_report_ib(r: &mut TlvReader<'_>) -> Result<ReportIb, ImError> {
346 let mut path = None;
347 let mut value = None;
348 let mut data_version = None;
349 let mut append = false;
350 let mut status: Option<(AttributePath, crate::status::ImStatus)> = None;
351 loop {
352 match r.next()? {
353 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
354 Some(Element::ContainerEnd) => break,
355 Some(Element::ContainerStart {
356 tag: Tag::Context(1),
357 kind: ContainerKind::Structure,
358 }) => {
359 parse_attribute_data(r, &mut path, &mut value, &mut data_version, &mut append)?;
361 }
362 Some(Element::ContainerStart {
366 tag: Tag::Context(0),
367 kind: ContainerKind::Structure,
368 }) => {
369 status = Some(crate::write::parse_attribute_status_ib(r)?);
370 }
371 Some(Element::ContainerStart { .. }) => skip_container(r)?,
373 Some(_) => {}
374 }
375 }
376 if let Some((p, s)) = status {
377 return Ok(ReportIb::Status(p, s));
378 }
379 match (path, value) {
380 (Some(p), Some(v)) => Ok(ReportIb::Data(AttributeReportItem {
381 path: p,
382 op: if append {
383 ReportOp::Append
384 } else {
385 ReportOp::Replace
386 },
387 value: v,
388 data_version,
389 })),
390 (None, None) => Ok(ReportIb::Empty), (Some(_), None) => Err(ImError::MissingField("AttributeData.Data")),
392 (None, Some(_)) => Err(ImError::MissingField("AttributeData.Path")),
393 }
394}
395
396fn parse_attribute_data(
405 r: &mut TlvReader<'_>,
406 path: &mut Option<AttributePath>,
407 value: &mut Option<Value>,
408 data_version: &mut Option<u32>,
409 append: &mut bool,
410) -> Result<(), ImError> {
411 loop {
412 match r.next()? {
413 None => return Err(ImError::Codec(matter_codec::Error::UnclosedContainer)),
414 Some(Element::ContainerEnd) => return Ok(()),
415 Some(Element::Scalar {
416 tag: Tag::Context(0),
417 value: Value::Uint(n),
418 }) => {
419 *data_version = Some(u32::try_from(n).map_err(|_| {
420 ImError::UnexpectedValue("AttributeData.DataVersion exceeds u32")
421 })?);
422 }
423 Some(Element::ContainerStart {
424 tag: Tag::Context(1),
425 kind: ContainerKind::List,
426 }) => {
427 let (p, is_append) = crate::path::attribute_path_from_reader(r)?;
428 *path = Some(p);
429 *append = is_append;
430 }
431 Some(Element::Scalar {
432 tag: Tag::Context(2),
433 value: v,
434 }) => *value = Some(v),
435 Some(Element::ContainerStart {
436 tag: Tag::Context(2),
437 kind,
438 }) => *value = Some(read_container_value(r, kind)?),
439 Some(Element::ContainerStart { .. }) => skip_container(r)?,
440 Some(_) => {}
441 }
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 #![allow(clippy::unwrap_used, clippy::expect_used)]
448 use super::*;
449 use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
450
451 #[test]
452 fn read_request_has_attribute_requests_array() {
453 let bytes = build_read_request(&[AttributePath {
454 endpoint: 0,
455 cluster: 0x0031,
456 attribute: 0xFFFC, }]);
458 let mut r = TlvReader::new(&bytes);
459 assert!(matches!(
460 r.next().unwrap(),
461 Some(Element::ContainerStart {
462 tag: Tag::Anonymous,
463 kind: ContainerKind::Structure
464 })
465 ));
466 assert!(matches!(
467 r.next().unwrap(),
468 Some(Element::ContainerStart {
469 tag: Tag::Context(0),
470 kind: ContainerKind::Array
471 })
472 ));
473 assert!(matches!(
474 r.next().unwrap(),
475 Some(Element::ContainerStart {
476 tag: Tag::Anonymous,
477 kind: ContainerKind::List
478 })
479 ));
480 assert!(matches!(
481 r.next().unwrap(),
482 Some(Element::Scalar {
483 tag: Tag::Context(2),
484 value: Value::Uint(0)
485 })
486 ));
487 assert!(matches!(
488 r.next().unwrap(),
489 Some(Element::Scalar {
490 tag: Tag::Context(3),
491 value: Value::Uint(0x0031)
492 })
493 ));
494 assert!(matches!(
495 r.next().unwrap(),
496 Some(Element::Scalar {
497 tag: Tag::Context(4),
498 value: Value::Uint(0xFFFC)
499 })
500 ));
501 }
502
503 #[test]
504 fn parses_single_attribute_value() {
505 use matter_codec::{Tag, TlvWriter};
506 let mut buf = Vec::new();
507 let mut w = TlvWriter::new(&mut buf);
508 w.start_structure(Tag::Anonymous).unwrap();
509 w.start_array(Tag::Context(1)).unwrap(); {
511 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();
515 w.put_uint(Tag::Context(3), 0x0031).unwrap();
516 w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
517 w.end_container().unwrap();
518 w.put_uint(Tag::Context(2), 0x0001).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); }
522 w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
524 w.end_container().unwrap();
525
526 let report = parse_report_data(&buf).unwrap();
527 let attrs: Vec<_> = report.attributes().collect();
528 assert_eq!(attrs.len(), 1);
529 let (path, value) = attrs[0];
530 assert_eq!(path.endpoint, 0);
531 assert_eq!(path.cluster, 0x0031);
532 assert_eq!(path.attribute, 0xFFFC);
533 assert_eq!(*value, matter_codec::Value::Uint(0x0001));
534 }
535
536 #[test]
537 fn attribute_status_report_is_surfaced() {
538 use matter_codec::{Tag, TlvWriter};
542 let mut buf = Vec::new();
543 let mut w = TlvWriter::new(&mut buf);
544 w.start_structure(Tag::Anonymous).unwrap();
545 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap(); w.start_structure(Tag::Context(0)).unwrap(); w.start_list(Tag::Context(0)).unwrap(); w.put_uint(Tag::Context(2), 1).unwrap(); w.put_uint(Tag::Context(3), 0x0006).unwrap(); w.put_uint(Tag::Context(4), 0x4242).unwrap(); w.end_container().unwrap(); w.start_structure(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(0), 0x86).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
560 w.end_container().unwrap();
561
562 let report = parse_report_data(&buf).unwrap();
563 assert_eq!(report.attributes().count(), 0, "no data items");
564 assert_eq!(report.statuses.len(), 1, "the status IB must be surfaced");
565 let (path, status) = &report.statuses[0];
566 assert_eq!(path.endpoint, 1);
567 assert_eq!(path.cluster, 0x0006);
568 assert_eq!(path.attribute, 0x4242);
569 assert_eq!(*status, crate::status::ImStatus::Failure(0x86));
570 }
571
572 #[test]
573 fn multi_attribute_report_accumulates_all_entries() {
574 use matter_codec::{Tag, TlvWriter};
575 let mut buf = Vec::new();
576 let mut w = TlvWriter::new(&mut buf);
577 w.start_structure(Tag::Anonymous).unwrap();
578 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap();
582 w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 0).unwrap();
585 w.put_uint(Tag::Context(3), 0x0028).unwrap();
586 w.put_uint(Tag::Context(4), 0x0000).unwrap();
587 w.end_container().unwrap();
588 w.put_uint(Tag::Context(2), 42).unwrap(); w.end_container().unwrap(); w.end_container().unwrap(); w.start_structure(Tag::Anonymous).unwrap();
594 w.start_structure(Tag::Context(1)).unwrap(); w.start_list(Tag::Context(1)).unwrap(); w.put_uint(Tag::Context(2), 1).unwrap();
597 w.put_uint(Tag::Context(3), 0x0006).unwrap();
598 w.put_uint(Tag::Context(4), 0x0000).unwrap();
599 w.end_container().unwrap();
600 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();
606 w.end_container().unwrap();
607
608 let report = parse_report_data(&buf).unwrap();
609 let attrs: Vec<_> = report.attributes().collect();
610 assert_eq!(attrs.len(), 2);
611
612 let (path0, val0) = attrs[0];
613 assert_eq!(path0.endpoint, 0);
614 assert_eq!(path0.cluster, 0x0028);
615 assert_eq!(path0.attribute, 0x0000);
616 assert_eq!(*val0, matter_codec::Value::Uint(42));
617
618 let (path1, val1) = attrs[1];
619 assert_eq!(path1.endpoint, 1);
620 assert_eq!(path1.cluster, 0x0006);
621 assert_eq!(path1.attribute, 0x0000);
622 assert_eq!(*val1, matter_codec::Value::Uint(1));
623 }
624
625 #[test]
626 fn out_of_range_endpoint_yields_unexpected_value() {
627 use crate::error::ImError;
628 use matter_codec::{Tag, TlvWriter};
629 let mut buf = Vec::new();
630 let mut w = TlvWriter::new(&mut buf);
631 w.start_structure(Tag::Anonymous).unwrap();
632 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();
638 w.put_uint(Tag::Context(4), 0xFFFC).unwrap();
639 w.end_container().unwrap();
640 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();
645 w.end_container().unwrap();
646
647 let result = parse_report_data(&buf);
648 assert!(
649 matches!(result, Err(ImError::UnexpectedValue(_))),
650 "expected UnexpectedValue, got {result:?}"
651 );
652 }
653
654 #[test]
655 fn parses_more_chunked_and_suppress_response_flags() {
656 use matter_codec::{Tag, TlvWriter};
657 let mut buf = Vec::new();
659 let mut w = TlvWriter::new(&mut buf);
660 w.start_structure(Tag::Anonymous).unwrap();
661 w.start_array(Tag::Context(1)).unwrap(); w.end_container().unwrap();
663 w.put_bool(Tag::Context(3), true).unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
665 w.end_container().unwrap();
666
667 let report = parse_report_data(&buf).unwrap();
668 assert!(
669 report.more_chunked_messages,
670 "tag 3 must be read after the array"
671 );
672 assert!(!report.suppress_response);
673 }
674
675 #[test]
676 fn parses_suppress_response_after_array() {
677 use matter_codec::{Tag, TlvWriter};
678 let mut buf = Vec::new();
679 let mut w = TlvWriter::new(&mut buf);
680 w.start_structure(Tag::Anonymous).unwrap();
681 w.start_array(Tag::Context(1)).unwrap();
682 w.end_container().unwrap();
683 w.put_bool(Tag::Context(4), true).unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
685 w.end_container().unwrap();
686
687 let report = parse_report_data(&buf).unwrap();
688 assert!(report.suppress_response);
689 assert!(!report.more_chunked_messages);
690 }
691
692 #[test]
693 fn captures_data_version_and_append_op() {
694 use matter_codec::{Tag, TlvWriter};
695 let mut buf = Vec::new();
696 let mut w = TlvWriter::new(&mut buf);
697 w.start_structure(Tag::Anonymous).unwrap();
698 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();
704 w.put_uint(Tag::Context(3), 0x1d).unwrap();
705 w.put_uint(Tag::Context(4), 0x0003).unwrap();
706 w.put_null(Tag::Context(5)).unwrap(); w.end_container().unwrap();
708 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();
713 w.end_container().unwrap();
714
715 let report = parse_report_data(&buf).unwrap();
716 assert_eq!(report.items.len(), 1);
717 let it = &report.items[0];
718 assert_eq!(it.op, ReportOp::Append);
719 assert_eq!(it.data_version, Some(7));
720 assert_eq!(it.value, Value::Uint(42));
721 assert_eq!(report.attributes().count(), 0);
723 }
724
725 #[test]
729 fn attributes_view_matches_items_filtered_to_replace() {
730 use matter_codec::{Tag, TlvWriter};
731 let mut buf = Vec::new();
732 let mut w = TlvWriter::new(&mut buf);
733 w.start_structure(Tag::Anonymous).unwrap();
734 w.start_array(Tag::Context(1)).unwrap(); w.start_structure(Tag::Anonymous).unwrap();
738 w.start_structure(Tag::Context(1)).unwrap();
739 w.start_list(Tag::Context(1)).unwrap();
740 w.put_uint(Tag::Context(2), 0).unwrap();
741 w.put_uint(Tag::Context(3), 0x0028).unwrap();
742 w.put_uint(Tag::Context(4), 0x0000).unwrap();
743 w.end_container().unwrap();
744 w.put_uint(Tag::Context(2), 42).unwrap();
745 w.end_container().unwrap();
746 w.end_container().unwrap();
747
748 w.start_structure(Tag::Anonymous).unwrap();
750 w.start_structure(Tag::Context(1)).unwrap();
751 w.start_list(Tag::Context(1)).unwrap();
752 w.put_uint(Tag::Context(2), 0).unwrap();
753 w.put_uint(Tag::Context(3), 0x001d).unwrap();
754 w.put_uint(Tag::Context(4), 0x0003).unwrap();
755 w.put_null(Tag::Context(5)).unwrap(); w.end_container().unwrap();
757 w.put_uint(Tag::Context(2), 7).unwrap();
758 w.end_container().unwrap();
759 w.end_container().unwrap();
760
761 w.start_structure(Tag::Anonymous).unwrap();
763 w.start_structure(Tag::Context(1)).unwrap();
764 w.start_list(Tag::Context(1)).unwrap();
765 w.put_uint(Tag::Context(2), 1).unwrap();
766 w.put_uint(Tag::Context(3), 0x0006).unwrap();
767 w.put_uint(Tag::Context(4), 0x0000).unwrap();
768 w.end_container().unwrap();
769 w.put_bool(Tag::Context(2), true).unwrap();
770 w.end_container().unwrap();
771 w.end_container().unwrap();
772
773 w.end_container().unwrap(); w.put_uint(Tag::Context(0xFF), 11).unwrap();
775 w.end_container().unwrap();
776
777 let report = parse_report_data(&buf).unwrap();
778
779 let expected: Vec<(&AttributePath, &Value)> = report
781 .items
782 .iter()
783 .filter(|it| it.op == ReportOp::Replace)
784 .map(|it| (&it.path, &it.value))
785 .collect();
786 let got: Vec<(&AttributePath, &Value)> = report.attributes().collect();
787 assert_eq!(got, expected);
788
789 assert_eq!(got.len(), 2);
791 assert_eq!(got[0].1, &Value::Uint(42));
792 assert_eq!(got[1].1, &Value::Bool(true));
793 }
794}