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}
112
113impl From<ErrorCode> for IMStatusCode {
114    fn from(e: ErrorCode) -> Self {
115        match e {
116            ErrorCode::NodeNotFound => IMStatusCode::UnsupportedNode,
117            ErrorCode::EndpointNotFound => IMStatusCode::UnsupportedEndpoint,
118            ErrorCode::ClusterNotFound => IMStatusCode::UnsupportedCluster,
119            ErrorCode::AttributeNotFound => IMStatusCode::UnsupportedAttribute,
120            ErrorCode::CommandNotFound => IMStatusCode::UnsupportedCommand,
121            ErrorCode::EventNotFound => IMStatusCode::UnsupportedEvent,
122            ErrorCode::InvalidAction => IMStatusCode::InvalidAction,
123            ErrorCode::InvalidCommand => IMStatusCode::InvalidCommand,
124            ErrorCode::InvalidDataType => IMStatusCode::InvalidDataType,
125            ErrorCode::UnsupportedAccess => IMStatusCode::UnsupportedAccess,
126            ErrorCode::Busy => IMStatusCode::Busy,
127            ErrorCode::DataVersionMismatch => IMStatusCode::DataVersionMismatch,
128            ErrorCode::ResourceExhausted => IMStatusCode::ResourceExhausted,
129            ErrorCode::FailSafeRequired => IMStatusCode::FailSafeRequired,
130            ErrorCode::NeedsTimedInteraction => IMStatusCode::NeedsTimedInteraction,
131            ErrorCode::ConstraintError => IMStatusCode::ConstraintError,
132            ErrorCode::DynamicConstraintError => IMStatusCode::DynamicConstraintError,
133            ErrorCode::NotFound => IMStatusCode::NotFound,
134            ErrorCode::Failure => IMStatusCode::Failure,
135            _ => IMStatusCode::Failure,
136        }
137    }
138}
139
140impl From<Error> for IMStatusCode {
141    fn from(value: Error) -> Self {
142        Self::from(value.code())
143    }
144}
145
146impl IMStatusCode {
147    /// Convert a non-success IM status code to an `ErrorCode`.
148    ///
149    /// Returns `None` for `Success`, since success is not an error.
150    pub fn to_error_code(self) -> Option<ErrorCode> {
151        match self {
152            Self::Success => None,
153            Self::UnsupportedAccess => Some(ErrorCode::UnsupportedAccess),
154            Self::InvalidAction => Some(ErrorCode::InvalidAction),
155            Self::UnsupportedCommand => Some(ErrorCode::CommandNotFound),
156            Self::InvalidCommand => Some(ErrorCode::InvalidCommand),
157            Self::UnsupportedAttribute => Some(ErrorCode::AttributeNotFound),
158            Self::ConstraintError => Some(ErrorCode::ConstraintError),
159            Self::DynamicConstraintError => Some(ErrorCode::DynamicConstraintError),
160            Self::ResourceExhausted => Some(ErrorCode::ResourceExhausted),
161            Self::NotFound => Some(ErrorCode::NotFound),
162            Self::InvalidDataType => Some(ErrorCode::InvalidDataType),
163            Self::DataVersionMismatch => Some(ErrorCode::DataVersionMismatch),
164            Self::Busy => Some(ErrorCode::Busy),
165            Self::UnsupportedNode => Some(ErrorCode::NodeNotFound),
166            Self::UnsupportedEndpoint => Some(ErrorCode::EndpointNotFound),
167            Self::UnsupportedCluster => Some(ErrorCode::ClusterNotFound),
168            Self::UnsupportedEvent => Some(ErrorCode::EventNotFound),
169            Self::NeedsTimedInteraction => Some(ErrorCode::NeedsTimedInteraction),
170            Self::FailSafeRequired => Some(ErrorCode::FailSafeRequired),
171            _ => Some(ErrorCode::Failure),
172        }
173    }
174}
175
176impl FromTLV<'_> for IMStatusCode {
177    fn from_tlv(t: &TLVElement) -> Result<Self, Error> {
178        FromPrimitive::from_u16(t.u16()?).ok_or_else(|| ErrorCode::Invalid.into())
179    }
180}
181
182impl ToTLV for IMStatusCode {
183    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
184        tw.u16(tag, *self as _)
185    }
186
187    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
188        TLV::u16(tag, *self as _).into_tlv_iter()
189    }
190}
191
192/// An enumeration of all possible opcodes used in the Interaction Model.
193#[derive(FromPrimitive, Debug, Copy, Clone, Eq, PartialEq)]
194#[cfg_attr(feature = "defmt", derive(defmt::Format))]
195pub enum OpCode {
196    Reserved = 0,
197    StatusResponse = 1,
198    ReadRequest = 2,
199    SubscribeRequest = 3,
200    SubscribeResponse = 4,
201    ReportData = 5,
202    WriteRequest = 6,
203    WriteResponse = 7,
204    InvokeRequest = 8,
205    InvokeResponse = 9,
206    TimedRequest = 10,
207}
208
209impl OpCode {
210    /// Return the opcode as a `MessageMeta` structure, which contains
211    /// the protocol ID, opcode, and reliability information.
212    ///
213    /// Reliability is set to `true` as all IM messages are reliable.
214    pub const fn meta(&self) -> MessageMeta {
215        MessageMeta {
216            proto_id: PROTO_ID_INTERACTION_MODEL,
217            proto_opcode: *self as u8,
218            reliable: true,
219        }
220    }
221
222    /// Return `true` if the opcode payload is in TLV format.
223    ///
224    /// Currently, the payload of all IM opcodes except `Reserved` is in TLV format.
225    pub const fn is_tlv(&self) -> bool {
226        !matches!(self, Self::Reserved)
227    }
228}
229
230impl From<OpCode> for MessageMeta {
231    fn from(opcode: OpCode) -> Self {
232        opcode.meta()
233    }
234}
235
236/// A generic (possibly a wildcard) path with endpoint, clusters, and a leaf
237///
238/// The leaf could be a command, an attribute, or an event
239///
240/// Note that this type does not implement `FromTLV` / `ToTLV` because it does not correspond
241/// to a specific TLV structure in the Interaction Model.
242///
243/// Note also that it only captures a _subset_ of the fields of `AttrPath`, and as such, it should be used with care!
244///
245/// Look at `AttrPath`, `CmdPath`, and `EventPath` for specific TLV structures, which
246/// can be turned into `GenericPath` using their `to_gp()` method.
247#[derive(Default, Clone, Debug, PartialEq)]
248#[cfg_attr(feature = "defmt", derive(defmt::Format))]
249pub struct GenericPath {
250    /// The endpoint ID, if specified, otherwise `None` for wildcard
251    pub endpoint: Option<EndptId>,
252    /// The cluster ID, if specified, otherwise `None` for wildcard
253    pub cluster: Option<ClusterId>,
254    /// The leaf ID, if specified, otherwise `None` for wildcard
255    pub leaf: Option<u32>,
256}
257
258impl GenericPath {
259    /// Create a new `GenericPath` with the given endpoint, cluster, and leaf.
260    pub const fn new(
261        endpoint: Option<EndptId>,
262        cluster: Option<ClusterId>,
263        leaf: Option<u32>,
264    ) -> Self {
265        Self {
266            endpoint,
267            cluster,
268            leaf,
269        }
270    }
271
272    /// Return Ok, if the path is non wildcard, otherwise returns an error
273    pub fn not_wildcard(&self) -> Result<(EndptId, ClusterId, u32), Error> {
274        match *self {
275            GenericPath {
276                endpoint: Some(e),
277                cluster: Some(c),
278                leaf: Some(l),
279            } => Ok((e, c, l)),
280            _ => Err(ErrorCode::Invalid.into()),
281        }
282    }
283
284    /// Return true, if the path is wildcard
285    pub const fn is_wildcard(&self) -> bool {
286        !matches!(
287            *self,
288            GenericPath {
289                endpoint: Some(_),
290                cluster: Some(_),
291                leaf: Some(_),
292            }
293        )
294    }
295}