Skip to main content

rs_matter/im/
encoding.rs

1/*
2 *
3 *    Copyright (c) 2022-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
18//! The Interaction Model Encoding as defined by the Matter Core spec: the
19//! wire-level TLV-serde types and path primitives - request/response payloads
20//! (`ReadReq`, `ReportDataReq`, `WriteReq`, `InvReq`, …), paths (`AttrPath`,
21//! `CmdPath`, `EventPath`, `GenericPath`), status codes and opcodes.
22//!
23//! This is the lowest layer of the Interaction Model: it is what the data model
24//! ([`crate::dm`]) depends upon, and what the IM engine ([`crate::im`]) and
25//! client ([`crate::im::client`]) build their messages from.
26
27use num::FromPrimitive;
28use num_derive::FromPrimitive;
29
30use crate::error::{Error, ErrorCode};
31use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV, TLV};
32use crate::transport::exchange::MessageMeta;
33
34pub use attr::*;
35pub use event::*;
36pub use invoke::*;
37pub use invoke_builder::*;
38pub use status::*;
39pub use timed::*;
40pub use types::*;
41
42mod attr;
43mod event;
44mod invoke;
45mod invoke_builder;
46mod status;
47mod timed;
48pub(crate) mod types;
49
50/// The buffer type used by the Interaction Model for RX/TX payloads. Aliases the
51/// central [`Buffer`](crate::transport::exchange::Buffer).
52pub type IMBuffer = crate::transport::exchange::Buffer;
53
54/// Interaction Model ID as per the Matter Core spec
55pub const PROTO_ID_INTERACTION_MODEL: u16 = 0x01;
56
57/// `interactionModelRevision` value emitted on every outgoing IM message
58/// rs-matter sends — both responder-side (ReportData / WriteResponse /
59/// InvokeResponse / StatusResponse / SubscribeResponse, in [`crate::dm`]
60/// and [`status`] / [`attr::subscribe`]) and requestor-side (the client
61/// builders in [`crate::im::client`] and [`TimedReq`]).
62///
63/// The TLV context tag it is emitted under is [`IM_REVISION_TAG`] (= `0xFF`).
64///
65/// `13` has been the spec-mandated value since Matter 1.3; see the
66/// "Revision History" of the Matter Core Specification.
67pub const IM_REVISION: u8 = 13;
68
69/// TLV context tag for the trailing `interactionModelRevision` field present on
70/// every IM message (carries [`IM_REVISION`]). A Matter global element tag.
71pub const IM_REVISION_TAG: u8 = 0xFF;
72
73/// TLV context tag for the `fabricIndex` field of fabric-scoped structs.
74/// A Matter global element tag.
75pub const FABRIC_INDEX_TAG: u8 = 0xFE;
76
77/// An enumeration of all possible error codes that can be returned by the Interaction Model.
78#[derive(FromPrimitive, Debug, Clone, Copy, PartialEq, Eq, Hash)]
79#[cfg_attr(feature = "defmt", derive(defmt::Format))]
80pub enum IMStatusCode {
81    Success = 0,
82    Failure = 1,
83    InvalidSubscription = 0x7D,
84    UnsupportedAccess = 0x7E,
85    UnsupportedEndpoint = 0x7F,
86    InvalidAction = 0x80,
87    UnsupportedCommand = 0x81,
88    InvalidCommand = 0x85,
89    UnsupportedAttribute = 0x86,
90    ConstraintError = 0x87,
91    UnsupportedWrite = 0x88,
92    ResourceExhausted = 0x89,
93    NotFound = 0x8b,
94    UnreportableAttribute = 0x8c,
95    InvalidDataType = 0x8d,
96    UnsupportedRead = 0x8f,
97    DataVersionMismatch = 0x92,
98    Timeout = 0x94,
99    UnsupportedNode = 0x9b,
100    Busy = 0x9c,
101    UnsupportedCluster = 0xc3,
102    NoUpstreamSubscription = 0xc5,
103    NeedsTimedInteraction = 0xc6,
104    UnsupportedEvent = 0xc7,
105    PathsExhausted = 0xc8,
106    TimedRequestMisMatch = 0xc9,
107    FailSafeRequired = 0xca,
108    InvalidInState = 0xcb,
109    NoCommandResponse = 0xcc,
110    DynamicConstraintError = 0xcf,
111    AlreadyExists = 0xd0,
112}
113
114impl From<ErrorCode> for IMStatusCode {
115    fn from(e: ErrorCode) -> Self {
116        match e {
117            ErrorCode::NodeNotFound => IMStatusCode::UnsupportedNode,
118            ErrorCode::EndpointNotFound => IMStatusCode::UnsupportedEndpoint,
119            ErrorCode::ClusterNotFound => IMStatusCode::UnsupportedCluster,
120            ErrorCode::AttributeNotFound => IMStatusCode::UnsupportedAttribute,
121            ErrorCode::CommandNotFound => IMStatusCode::UnsupportedCommand,
122            ErrorCode::EventNotFound => IMStatusCode::UnsupportedEvent,
123            ErrorCode::InvalidAction => IMStatusCode::InvalidAction,
124            ErrorCode::InvalidCommand => IMStatusCode::InvalidCommand,
125            ErrorCode::InvalidDataType => IMStatusCode::InvalidDataType,
126            ErrorCode::UnsupportedAccess => IMStatusCode::UnsupportedAccess,
127            ErrorCode::Busy => IMStatusCode::Busy,
128            ErrorCode::DataVersionMismatch => IMStatusCode::DataVersionMismatch,
129            ErrorCode::ResourceExhausted => IMStatusCode::ResourceExhausted,
130            ErrorCode::FailSafeRequired => IMStatusCode::FailSafeRequired,
131            ErrorCode::NeedsTimedInteraction => IMStatusCode::NeedsTimedInteraction,
132            ErrorCode::ConstraintError => IMStatusCode::ConstraintError,
133            ErrorCode::DynamicConstraintError => IMStatusCode::DynamicConstraintError,
134            ErrorCode::NotFound => IMStatusCode::NotFound,
135            ErrorCode::AlreadyExists => IMStatusCode::AlreadyExists,
136            ErrorCode::Failure => IMStatusCode::Failure,
137            _ => IMStatusCode::Failure,
138        }
139    }
140}
141
142impl From<Error> for IMStatusCode {
143    fn from(value: Error) -> Self {
144        Self::from(value.code())
145    }
146}
147
148impl IMStatusCode {
149    /// Convert a non-success IM status code to an `ErrorCode`.
150    ///
151    /// Returns `None` for `Success`, since success is not an error.
152    pub fn to_error_code(self) -> Option<ErrorCode> {
153        match self {
154            Self::Success => None,
155            Self::UnsupportedAccess => Some(ErrorCode::UnsupportedAccess),
156            Self::InvalidAction => Some(ErrorCode::InvalidAction),
157            Self::UnsupportedCommand => Some(ErrorCode::CommandNotFound),
158            Self::InvalidCommand => Some(ErrorCode::InvalidCommand),
159            Self::UnsupportedAttribute => Some(ErrorCode::AttributeNotFound),
160            Self::ConstraintError => Some(ErrorCode::ConstraintError),
161            Self::DynamicConstraintError => Some(ErrorCode::DynamicConstraintError),
162            Self::ResourceExhausted => Some(ErrorCode::ResourceExhausted),
163            Self::NotFound => Some(ErrorCode::NotFound),
164            Self::InvalidDataType => Some(ErrorCode::InvalidDataType),
165            Self::DataVersionMismatch => Some(ErrorCode::DataVersionMismatch),
166            Self::Busy => Some(ErrorCode::Busy),
167            Self::UnsupportedNode => Some(ErrorCode::NodeNotFound),
168            Self::UnsupportedEndpoint => Some(ErrorCode::EndpointNotFound),
169            Self::UnsupportedCluster => Some(ErrorCode::ClusterNotFound),
170            Self::UnsupportedEvent => Some(ErrorCode::EventNotFound),
171            Self::NeedsTimedInteraction => Some(ErrorCode::NeedsTimedInteraction),
172            Self::FailSafeRequired => Some(ErrorCode::FailSafeRequired),
173            _ => Some(ErrorCode::Failure),
174        }
175    }
176}
177
178impl FromTLV<'_> for IMStatusCode {
179    fn from_tlv(t: &TLVElement) -> Result<Self, Error> {
180        FromPrimitive::from_u16(t.u16()?).ok_or_else(|| ErrorCode::Invalid.into())
181    }
182}
183
184impl ToTLV for IMStatusCode {
185    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
186        tw.u16(tag, *self as _)
187    }
188
189    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
190        TLV::u16(tag, *self as _).into_tlv_iter()
191    }
192}
193
194/// An enumeration of all possible opcodes used in the Interaction Model.
195#[derive(FromPrimitive, Debug, Copy, Clone, Eq, PartialEq)]
196#[cfg_attr(feature = "defmt", derive(defmt::Format))]
197pub enum OpCode {
198    Reserved = 0,
199    StatusResponse = 1,
200    ReadRequest = 2,
201    SubscribeRequest = 3,
202    SubscribeResponse = 4,
203    ReportData = 5,
204    WriteRequest = 6,
205    WriteResponse = 7,
206    InvokeRequest = 8,
207    InvokeResponse = 9,
208    TimedRequest = 10,
209}
210
211impl OpCode {
212    /// Return the opcode as a `MessageMeta` structure, which contains
213    /// the protocol ID, opcode, and reliability information.
214    ///
215    /// Reliability is set to `true` as all IM messages are reliable.
216    pub const fn meta(&self) -> MessageMeta {
217        MessageMeta {
218            proto_id: PROTO_ID_INTERACTION_MODEL,
219            proto_opcode: *self as u8,
220            reliable: true,
221        }
222    }
223
224    /// Return `true` if the opcode payload is in TLV format.
225    ///
226    /// Currently, the payload of all IM opcodes except `Reserved` is in TLV format.
227    pub const fn is_tlv(&self) -> bool {
228        !matches!(self, Self::Reserved)
229    }
230}
231
232impl From<OpCode> for MessageMeta {
233    fn from(opcode: OpCode) -> Self {
234        opcode.meta()
235    }
236}
237
238/// A generic (possibly a wildcard) path with endpoint, clusters, and a leaf
239///
240/// The leaf could be a command, an attribute, or an event
241///
242/// Note that this type does not implement `FromTLV` / `ToTLV` because it does not correspond
243/// to a specific TLV structure in the Interaction Model.
244///
245/// Note also that it only captures a _subset_ of the fields of `AttrPath`, and as such, it should be used with care!
246///
247/// Look at `AttrPath`, `CmdPath`, and `EventPath` for specific TLV structures, which
248/// can be turned into `GenericPath` using their `to_gp()` method.
249#[derive(Default, Clone, Debug, PartialEq)]
250#[cfg_attr(feature = "defmt", derive(defmt::Format))]
251pub struct GenericPath {
252    /// The endpoint ID, if specified, otherwise `None` for wildcard
253    pub endpoint: Option<EndptId>,
254    /// The cluster ID, if specified, otherwise `None` for wildcard
255    pub cluster: Option<ClusterId>,
256    /// The leaf ID, if specified, otherwise `None` for wildcard
257    pub leaf: Option<u32>,
258}
259
260impl GenericPath {
261    /// Create a new `GenericPath` with the given endpoint, cluster, and leaf.
262    pub const fn new(
263        endpoint: Option<EndptId>,
264        cluster: Option<ClusterId>,
265        leaf: Option<u32>,
266    ) -> Self {
267        Self {
268            endpoint,
269            cluster,
270            leaf,
271        }
272    }
273
274    /// Return Ok, if the path is non wildcard, otherwise returns an error
275    pub fn not_wildcard(&self) -> Result<(EndptId, ClusterId, u32), Error> {
276        match *self {
277            GenericPath {
278                endpoint: Some(e),
279                cluster: Some(c),
280                leaf: Some(l),
281            } => Ok((e, c, l)),
282            _ => Err(ErrorCode::Invalid.into()),
283        }
284    }
285
286    /// Return true, if the path is wildcard
287    pub const fn is_wildcard(&self) -> bool {
288        !matches!(
289            *self,
290            GenericPath {
291                endpoint: Some(_),
292                cluster: Some(_),
293                leaf: Some(_),
294            }
295        )
296    }
297}