Skip to main content

rs_matter/
error.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
18use core::{array::TryFromSliceError, fmt, str::Utf8Error};
19
20#[cfg(all(feature = "alloc", feature = "backtrace"))]
21use alloc::{boxed::Box, string::ToString};
22
23// TODO: The error code enum is in a need of an overhaul
24//
25// We need separate error enums per chunks of functionality
26// and a way to map them to concrete IM and SC status codes
27//
28// This is a non-trivial effort though as we need to also generify
29// the returned error type of all APIs that take callbacks that return errors
30// (i.e., `Exchange::with_*`, `WriteBuf::append_with_buf` etc.)
31#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub enum ErrorCode {
34    AttributeNotFound,
35    AttributeIsCustom,
36    BufferTooSmall,
37    ClusterNotFound,
38    CommandNotFound,
39    Duplicate,
40    NodeNotFound,
41    EndpointNotFound,
42    EventNotFound,
43    InvalidAction,
44    InvalidCommand,
45    FailSafeRequired,
46    NeedsTimedInteraction,
47    ConstraintError,
48    DynamicConstraintError,
49    InvalidDataType,
50    UnsupportedAccess,
51    ResourceExhausted,
52    Busy,
53    DataVersionMismatch,
54    BtpError,
55    MdnsError,
56    NoCommand,
57    NoEndpoint,
58    NoExchange,
59    NoFabricId,
60    NoHandler,
61    NoNetworkInterface,
62    DBusError,
63    NoNodeId,
64    NoMemory,
65    NoSession,
66    // TODO: Rename to `TLVNoWriteSpace` or similar, so that it is clear
67    // that this error code should _only_ be used when writing a TLV using
68    // a `TLVWrite` instance which happens to run out of space
69    //
70    // All other cases of running out of space should use the generic:
71    // - `ResourceExhausted` (when number of fabrics, ACLs, sessions or exchanges becomes too big)
72    // - `BufferTooSmall` or `ConstraintError` when other internal buffers don't fit the data
73    // - ... or use-case-specific error codes like `NoSpaceExchanges` and `NoSpaceSessions`.
74    NoSpace,
75    NoSpaceExchanges,
76    NoSpaceSessions,
77    TxTimeout,
78    RxTimeout,
79    NoTagFound,
80    NotFound,
81    PacketPoolExhaust,
82    StdIoError,
83    SysTimeFail,
84    Invalid,
85    InvalidAAD,
86    InvalidData,
87    InvalidKeyLength,
88    InvalidOpcode,
89    InvalidProto,
90    InvalidPeerAddr,
91    // Invalid Auth Key in the Matter Certificate
92    InvalidAuthKey,
93    InvalidSignature,
94    InvalidState,
95    InvalidTime,
96    InvalidArgument,
97    RwLock,
98    TLVNotFound,
99    TLVTypeMismatch,
100    TruncatedPacket,
101    Utf8Fail,
102    GennCommInvalidAuthentication,
103    NocInvalidNoc,
104    NocInvalidPublicKey,
105    NocMissingCsr,
106    NocFabricTableFull,
107    NocFabricConflict,
108    NocLabelConflict,
109    NocInvalidFabricIndex,
110    NocInvalidAdminSubject,
111    Failure,
112    // Certification Declaration errors
113    CdInvalidFormat,
114    CdInvalidSignature,
115    CdSigningKeyNotFound,
116    CdInvalidVendorId,
117    CdInvalidProductId,
118    CdInvalidPaa,
119}
120
121impl From<ErrorCode> for Error {
122    fn from(code: ErrorCode) -> Self {
123        Self::new(code)
124    }
125}
126
127pub struct Error {
128    code: ErrorCode,
129    #[cfg(all(feature = "std", feature = "backtrace"))]
130    backtrace: std::backtrace::Backtrace,
131    #[cfg(all(feature = "alloc", feature = "backtrace"))]
132    inner: Option<Box<dyn core::error::Error + Send + Sync>>,
133}
134
135impl Error {
136    pub fn new(code: ErrorCode) -> Self {
137        Self {
138            code,
139            #[cfg(all(feature = "std", feature = "backtrace"))]
140            backtrace: std::backtrace::Backtrace::capture(),
141            #[cfg(all(feature = "alloc", feature = "backtrace"))]
142            inner: None,
143        }
144    }
145
146    #[cfg(all(feature = "alloc", feature = "backtrace"))]
147    pub fn new_with_details(
148        code: ErrorCode,
149        detailed_err: Box<dyn core::error::Error + Send + Sync>,
150    ) -> Self {
151        Self {
152            code,
153            #[cfg(feature = "std")]
154            backtrace: std::backtrace::Backtrace::capture(),
155            inner: Some(detailed_err),
156        }
157    }
158
159    pub const fn code(&self) -> ErrorCode {
160        self.code
161    }
162
163    #[cfg(all(feature = "std", feature = "backtrace"))]
164    pub const fn backtrace(&self) -> &std::backtrace::Backtrace {
165        &self.backtrace
166    }
167
168    #[cfg(all(feature = "alloc", feature = "backtrace"))]
169    pub fn details(&self) -> Option<&(dyn core::error::Error + Send + Sync)> {
170        self.inner.as_ref().map(|err| err.as_ref())
171    }
172}
173
174#[cfg(all(feature = "std", feature = "backtrace"))]
175impl From<std::io::Error> for Error {
176    fn from(e: std::io::Error) -> Self {
177        Self::new_with_details(ErrorCode::StdIoError, Box::new(e))
178    }
179}
180
181#[cfg(all(feature = "std", not(feature = "backtrace")))]
182impl From<std::io::Error> for Error {
183    fn from(_e: std::io::Error) -> Self {
184        Self::new(ErrorCode::StdIoError)
185    }
186}
187
188#[cfg(feature = "std")]
189impl<T> From<std::sync::PoisonError<T>> for Error {
190    fn from(_e: std::sync::PoisonError<T>) -> Self {
191        Self::new(ErrorCode::RwLock)
192    }
193}
194
195#[cfg(all(
196    feature = "os",
197    target_os = "linux",
198    feature = "bluer",
199    not(feature = "backtrace")
200))]
201impl From<bluer::Error> for Error {
202    fn from(e: bluer::Error) -> Self {
203        // Log the error given that we lose all context from the
204        // original error here
205        error!("Error in BTP: {}", display2format!(e));
206        Self::new(ErrorCode::BtpError)
207    }
208}
209
210#[cfg(all(
211    feature = "os",
212    target_os = "linux",
213    feature = "bluer",
214    feature = "backtrace"
215))]
216impl From<bluer::Error> for Error {
217    fn from(e: bluer::Error) -> Self {
218        Self::new_with_details(ErrorCode::BtpError, Box::new(e))
219    }
220}
221
222#[cfg(feature = "std")]
223impl From<std::time::SystemTimeError> for Error {
224    fn from(_e: std::time::SystemTimeError) -> Self {
225        Error::new(ErrorCode::SysTimeFail)
226    }
227}
228
229impl From<TryFromSliceError> for Error {
230    fn from(_e: TryFromSliceError) -> Self {
231        Self::new(ErrorCode::Invalid)
232    }
233}
234
235impl From<Utf8Error> for Error {
236    fn from(_e: Utf8Error) -> Self {
237        Self::new(ErrorCode::Utf8Fail)
238    }
239}
240
241impl<T: num_enum::TryFromPrimitive> From<num_enum::TryFromPrimitiveError<T>> for Error {
242    fn from(_e: num_enum::TryFromPrimitiveError<T>) -> Self {
243        Self::new(ErrorCode::Invalid)
244    }
245}
246
247impl fmt::Debug for Error {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        #[cfg(not(all(feature = "std", feature = "backtrace")))]
250        {
251            write!(f, "Error::{}", self)?;
252        }
253
254        #[cfg(all(feature = "std", feature = "backtrace"))]
255        {
256            writeln!(f, "Error::{} {{", self)?;
257            write!(f, "{}", self.backtrace())?;
258            writeln!(f, "}}")?;
259        }
260
261        Ok(())
262    }
263}
264
265impl fmt::Display for Error {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        #[cfg(all(feature = "alloc", feature = "backtrace"))]
268        {
269            let err_msg = self
270                .inner
271                .as_ref()
272                .map_or(Default::default(), |err| err.to_string());
273
274            if err_msg.is_empty() {
275                write!(f, "{:?}", self.code())
276            } else {
277                write!(f, "{:?}: {}", self.code(), err_msg)
278            }
279        }
280        #[cfg(not(all(feature = "alloc", feature = "backtrace")))]
281        {
282            write!(f, "{:?}", self.code())
283        }
284    }
285}
286
287#[cfg(feature = "defmt")]
288impl defmt::Format for Error {
289    fn format(&self, f: defmt::Formatter<'_>) {
290        defmt::write!(f, "{:?}", self.code())
291    }
292}
293
294impl core::error::Error for Error {
295    #[cfg(all(feature = "alloc", feature = "backtrace"))]
296    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
297        self.inner
298            .as_ref()
299            .map(|e| e.as_ref() as &(dyn core::error::Error + 'static))
300    }
301}
302
303impl embedded_io_async::Error for Error {
304    fn kind(&self) -> embedded_io_async::ErrorKind {
305        embedded_io_async::ErrorKind::Other
306    }
307}