Skip to main content

matter_interaction/
lib.rs

1//! Matter Interaction Model (IM) message framing — Matter Core Spec §10.
2//!
3//! Builders for the IM message envelopes the wire carries
4//! (`InvokeRequestMessage`, `ReadRequestMessage`, `WriteRequestMessage`,
5//! `SubscribeRequestMessage`, `StatusResponseMessage`)
6//! and parsers for the responses (`InvokeResponseMessage`,
7//! `ReportDataMessage`, `WriteResponseMessage`, `SubscribeResponseMessage`).
8//! Callers supply already-encoded cluster TLV payloads (e.g. from
9//! `matter-clusters` codecs) and compose them with the concrete paths in
10//! [`path`].
11//!
12//! Scope: single- and multi-command invoke (the latter via
13//! `build_invoke_request_batch` / `parse_invoke_response_batch` with `CommandRef`
14//! — the controller-side verb + `MaxPathsPerInvoke` gating are deferred until a
15//! batch-capable device exists), concrete and wildcard read paths, **events**
16//! (event paths/filters in `ReadRequest` and `SubscribeRequest`, `EventReportIB`
17//! parsing), **timed write/invoke** (the `TimedRequest` message + the
18//! `TimedRequest` flag), no chunked writes (deferred to the ACL/groups work).
19//!
20//! Lifted from `matter-commissioning` in M7.1 (the M6.6 design kept this
21//! module free of state-machine dependencies for exactly this move).
22//! Byte-parity with matter.js is enforced by `tests/im_byte_parity.rs`
23//! against fixtures captured via `cargo xtask capture-im`.
24
25#![forbid(unsafe_code)]
26
27mod accumulator;
28pub mod error;
29pub mod event;
30pub mod invoke;
31pub mod invoke_server;
32pub mod path;
33pub mod read;
34pub mod status;
35pub mod subscription;
36pub mod timed;
37pub mod write;
38
39pub use accumulator::{ReportAccumulator, DEFAULT_MAX_BYTES, DEFAULT_MAX_ELEMENTS};
40pub use error::ImError;
41pub use event::{
42    EventFilter, EventPath, EventPriority, EventReport, EventReportItem, EventTimestamp,
43};
44pub use invoke::{
45    build_invoke_request, build_invoke_request_batch, build_invoke_request_group,
46    build_invoke_request_timed, parse_invoke_response, parse_invoke_response_batch, InvokeResponse,
47    InvokeResponseEntry,
48};
49pub use invoke_server::{
50    build_invoke_response_command, build_invoke_response_status, parse_invoke_request,
51    InvokedCommand, ParsedInvokeRequest,
52};
53pub use path::{AttributePath, CommandPath, ReadPath};
54pub use read::{
55    build_read_request, build_read_request_full, build_read_request_paths, parse_report_data,
56    AttributeReportItem, ReportData, ReportOp,
57};
58pub use status::{parse_status_response, ImStatus};
59pub use subscription::{
60    build_status_response, build_subscribe_request, parse_subscribe_response, SubscribeRequest,
61    SubscribeResponse,
62};
63pub use timed::build_timed_request;
64pub use write::{
65    build_list_write_chunks, build_write_request, build_write_request_timed, parse_write_response,
66    AttributeWriteRequest,
67};
68
69/// Interaction Model protocol revision emitted at context tag `0xFF` in
70/// every top-level IM message. Confirmed against the matter.js byte-parity
71/// fixture (see `tests/im_byte_parity.rs`); bump only when a captured
72/// fixture proves matter.js changed it.
73pub const IM_REVISION: u8 = 11;
74
75use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
76
77/// Assert the reader's first element is an anonymous message struct and
78/// consume its start.
79///
80/// # Errors
81///
82/// Returns [`error::ImError::NotAStruct`] if the first element is not an
83/// anonymous structure start, or propagates any [`error::ImError::Codec`]
84/// error from the reader.
85pub fn expect_message_struct(r: &mut TlvReader<'_>) -> Result<(), error::ImError> {
86    match r.next()? {
87        Some(Element::ContainerStart {
88            tag: Tag::Anonymous,
89            kind: ContainerKind::Structure,
90        }) => Ok(()),
91        Some(_) | None => Err(error::ImError::NotAStruct),
92    }
93}
94
95/// Reader positioned just after a container start: consume to its matching
96/// end, returning the members as `(tag, value)` pairs (for List/Structure).
97/// Calling this with the reader in any other position yields an
98/// [`error::ImError`] or misattributed members — never a panic or UB.
99///
100/// # Errors
101///
102/// Returns [`error::ImError::Codec`] wrapping
103/// [`matter_codec::Error::UnclosedContainer`] if the input ends before a
104/// matching end-of-container, or propagates any other codec error.
105pub fn read_container_members(r: &mut TlvReader<'_>) -> Result<Vec<(Tag, Value)>, error::ImError> {
106    let mut out = Vec::new();
107    loop {
108        match r.next()? {
109            None => {
110                return Err(error::ImError::Codec(
111                    matter_codec::Error::UnclosedContainer,
112                ))
113            }
114            Some(Element::ContainerEnd) => return Ok(out),
115            Some(Element::Scalar { tag, value }) => out.push((tag, value)),
116            Some(Element::ContainerStart { tag, kind }) => {
117                let v = read_container_value(r, kind)?;
118                out.push((tag, v));
119            }
120            Some(_) => {}
121        }
122    }
123}
124
125/// Reader positioned just after a container start (of `kind`): read the
126/// whole sub-tree into a [`Value`]. Calling this with the reader in any other
127/// position yields an [`error::ImError`] or misattributed members — never a
128/// panic or UB.
129///
130/// # Errors
131///
132/// Propagates any error from [`read_container_members`].
133pub fn read_container_value(
134    r: &mut TlvReader<'_>,
135    kind: ContainerKind,
136) -> Result<Value, error::ImError> {
137    let members = read_container_members(r)?;
138    Ok(match kind {
139        ContainerKind::Structure => Value::Structure(members),
140        ContainerKind::Array => Value::Array(members.into_iter().map(|(_, v)| v).collect()),
141        // ContainerKind::List and any future non-exhaustive variants: preserve as List.
142        _ => Value::List(members),
143    })
144}
145
146/// Reader positioned just after a container start: discard the whole
147/// sub-tree (used to skip fields we do not consume). Calling this with the
148/// reader in any other position yields an [`error::ImError`] or misattributed
149/// members — never a panic or UB.
150///
151/// # Errors
152///
153/// Propagates any error from [`read_container_members`].
154pub fn skip_container(r: &mut TlvReader<'_>) -> Result<(), error::ImError> {
155    let _ = read_container_members(r)?;
156    Ok(())
157}