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