Skip to main content

atecc608b/
error.rs

1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Error types for the ATECC608B driver.
17//!
18//! Each layer maps its specific failure modes to a variant of [`AteccError`].
19//! The error type is generic over the HAL error so callers can match on the
20//! exact reason without losing typing information.
21//!
22//! Upper layers (the firmware HID dispatcher, the host CLI) often need to
23//! distinguish *categories* of failure (chip-side vs. communication-side)
24//! without naming the concrete `HalError` type. [`AteccErrorKind`] is the
25//! non-generic projection of [`AteccError`] intended for that use: it carries
26//! the same shape information (which variant fired) but no HAL payload, so it
27//! can travel through generic-erased layers and be serialized as a single
28//! `u8` for transport over the USB-HID protocol.
29
30use core::fmt::Debug;
31
32/// Error returned by every public method of [`crate::Atecc`].
33#[derive(Debug)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub enum AteccError<HalError>
36where
37    HalError: Debug,
38{
39    /// The HAL itself reported an error (I2C bus, GPIO, etc).
40    Hal(HalError),
41
42    /// The wake response did not match `04 11 33 43`. The chip is either
43    /// unpowered, miswired, or in self-test failure mode.
44    WakeFailed,
45
46    /// The chip returned the self-test-failure pattern `04 07 C4 40` after
47    /// wake. Hardware is unusable until power-cycle.
48    SelfTestFailure,
49
50    /// CRC-16 of a received frame did not match. The frame is discarded. The
51    /// caller may retry.
52    BadCrc,
53
54    /// The chip returned a 4-byte status frame indicating an error.
55    Chip(ChipError),
56
57    /// Polling for a command response exceeded the watchdog window (~1.3 s
58    /// nominal, 2.5 s upper bound).
59    Timeout,
60
61    /// A received frame has an inconsistent length byte.
62    MalformedResponse,
63
64    /// A caller-supplied buffer is too small for the response.
65    BufferTooSmall,
66}
67
68impl<HalError> AteccError<HalError>
69where
70    HalError: Debug,
71{
72    /// Project this error onto its non-generic [`AteccErrorKind`].
73    ///
74    /// Useful for layers that must report the error over a wire format
75    /// without leaking the concrete `HalError` type. The returned value
76    /// preserves the variant identity and, for [`AteccError::Chip`], the
77    /// inner [`ChipError`].
78    #[must_use]
79    pub fn kind(&self) -> AteccErrorKind
80    {
81        match self
82        {
83            AteccError::Hal(_)            => AteccErrorKind::Hal,
84            AteccError::WakeFailed        => AteccErrorKind::WakeFailed,
85            AteccError::SelfTestFailure   => AteccErrorKind::SelfTestFailure,
86            AteccError::BadCrc            => AteccErrorKind::BadCrc,
87            AteccError::Chip(err)         => AteccErrorKind::Chip(*err),
88            AteccError::Timeout           => AteccErrorKind::Timeout,
89            AteccError::MalformedResponse => AteccErrorKind::MalformedResponse,
90            AteccError::BufferTooSmall    => AteccErrorKind::BufferTooSmall,
91        }
92    }
93}
94
95impl<HalError> From<HalError> for AteccError<HalError>
96where
97    HalError: Debug,
98{
99    fn from(err: HalError) -> Self
100    {
101        AteccError::Hal(err)
102    }
103}
104
105/// Non-generic projection of [`AteccError`].
106///
107/// Carries the same variant identity (and, for [`AteccErrorKind::Chip`], the
108/// inner [`ChipError`]) but drops the HAL payload. Cheap to copy and safe to
109/// pass across layers that don't want to be generic over `HalError`.
110///
111/// The non-`Chip` variants are encoded as stable `u8` sub-codes by
112/// [`AteccErrorKind::as_sub_code`], used by the USB-HID protocol to expose
113/// the failure category to the host CLI.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[cfg_attr(feature = "defmt", derive(defmt::Format))]
116pub enum AteccErrorKind
117{
118    /// HAL error (I2C nack, GPIO failure, etc).
119    Hal,
120    /// Chip did not return the expected wake-response pattern.
121    WakeFailed,
122    /// Chip returned the self-test-failure pattern after wake.
123    SelfTestFailure,
124    /// CRC-16 of a received frame did not match.
125    BadCrc,
126    /// Chip-side error status. The inner [`ChipError`] carries the raw byte.
127    Chip(ChipError),
128    /// Polling exceeded the watchdog window.
129    Timeout,
130    /// A received frame has an inconsistent length byte.
131    MalformedResponse,
132    /// A caller-supplied buffer was too small for the response.
133    BufferTooSmall,
134}
135
136/// Stable wire-format sub-code for the HAL-error variant.
137pub const ATECC_ERR_SUB_HAL:                u8 = 0x01;
138/// Stable wire-format sub-code for the wake-failed variant.
139pub const ATECC_ERR_SUB_WAKE_FAILED:        u8 = 0x02;
140/// Stable wire-format sub-code for the self-test-failure variant.
141pub const ATECC_ERR_SUB_SELF_TEST_FAILURE:  u8 = 0x03;
142/// Stable wire-format sub-code for the bad-CRC variant.
143pub const ATECC_ERR_SUB_BAD_CRC:            u8 = 0x04;
144/// Stable wire-format sub-code for the timeout variant.
145pub const ATECC_ERR_SUB_TIMEOUT:            u8 = 0x05;
146/// Stable wire-format sub-code for the malformed-response variant.
147pub const ATECC_ERR_SUB_MALFORMED_RESPONSE: u8 = 0x06;
148/// Stable wire-format sub-code for the buffer-too-small variant.
149pub const ATECC_ERR_SUB_BUFFER_TOO_SMALL:   u8 = 0x07;
150
151impl AteccErrorKind
152{
153    /// Return the stable `u8` sub-code identifying this variant.
154    ///
155    /// `Chip(_)` is not assigned a sub-code here because chip errors carry
156    /// their own dedicated status byte (the chip's raw response byte,
157    /// available via [`ChipError::as_status_byte`]) and are routed to a
158    /// distinct top-level HID status. The fallback value `0x00` is reserved
159    /// for that case and indicates "see the dedicated chip-error path".
160    #[must_use]
161    pub const fn as_sub_code(self) -> u8
162    {
163        match self
164        {
165            AteccErrorKind::Hal               => ATECC_ERR_SUB_HAL,
166            AteccErrorKind::WakeFailed        => ATECC_ERR_SUB_WAKE_FAILED,
167            AteccErrorKind::SelfTestFailure   => ATECC_ERR_SUB_SELF_TEST_FAILURE,
168            AteccErrorKind::BadCrc            => ATECC_ERR_SUB_BAD_CRC,
169            AteccErrorKind::Timeout           => ATECC_ERR_SUB_TIMEOUT,
170            AteccErrorKind::MalformedResponse => ATECC_ERR_SUB_MALFORMED_RESPONSE,
171            AteccErrorKind::BufferTooSmall    => ATECC_ERR_SUB_BUFFER_TOO_SMALL,
172            AteccErrorKind::Chip(_)           => 0x00,
173        }
174    }
175
176    /// Map a wire-format sub-code back to a non-`Chip` variant.
177    ///
178    /// Returns `None` for `0x00` (reserved for the chip-error path) and for
179    /// any byte outside the assigned range. Callers reconstructing an error
180    /// from the wire should handle the chip path through
181    /// [`ChipError::from_status_byte`] separately.
182    #[must_use]
183    pub const fn from_sub_code(byte: u8) -> Option<Self>
184    {
185        match byte
186        {
187            ATECC_ERR_SUB_HAL                => Some(Self::Hal),
188            ATECC_ERR_SUB_WAKE_FAILED        => Some(Self::WakeFailed),
189            ATECC_ERR_SUB_SELF_TEST_FAILURE  => Some(Self::SelfTestFailure),
190            ATECC_ERR_SUB_BAD_CRC            => Some(Self::BadCrc),
191            ATECC_ERR_SUB_TIMEOUT            => Some(Self::Timeout),
192            ATECC_ERR_SUB_MALFORMED_RESPONSE => Some(Self::MalformedResponse),
193            ATECC_ERR_SUB_BUFFER_TOO_SMALL   => Some(Self::BufferTooSmall),
194            _                                => None,
195        }
196    }
197}
198
199/// Errors reported by the chip itself via a 4-byte response frame.
200///
201/// Status byte values are taken from the Microchip `CryptoAuthLib`
202/// `isATCAError()` function.
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204#[cfg_attr(feature = "defmt", derive(defmt::Format))]
205pub enum ChipError
206{
207    /// `0x01` - `CheckMac` or Verify failed.
208    CheckMacOrVerifyFailed,
209
210    /// `0x03` - Command byte length, opcode or parameter was illegal.
211    ParseError,
212
213    /// `0x05` - Computation error during ECC processing causing invalid
214    /// results.
215    EccFault,
216
217    /// `0x07` - Chip is in self test failure mode.
218    SelfTestFailed,
219
220    /// `0x08` - RNG health test error.
221    HealthTestFailed,
222
223    /// `0x0F` - Unspecified execution error.
224    ExecutionError,
225
226    /// `0xEE` - Watchdog about to expire - command not executed.
227    WatchdogAboutToExpire,
228
229    /// `0xFF` - CRC or other communication error on the command sent to the
230    /// chip.
231    CommandCrcError,
232
233    /// An unknown status byte. The raw value is preserved for diagnosis.
234    Unknown(u8),
235}
236
237impl ChipError
238{
239    /// Map a raw status byte returned by the chip to a [`ChipError`].
240    ///
241    /// Returns `None` for `0x00` which means success. Exposed as `pub` so
242    /// host-side decoders (e.g. the CLI) can reconstruct the variant from
243    /// the wire byte received in the USB-HID payload.
244    #[must_use]
245    pub const fn from_status_byte(byte: u8) -> Option<Self>
246    {
247        match byte
248        {
249            0x00 => None,
250            0x01 => Some(Self::CheckMacOrVerifyFailed),
251            0x03 => Some(Self::ParseError),
252            0x05 => Some(Self::EccFault),
253            0x07 => Some(Self::SelfTestFailed),
254            0x08 => Some(Self::HealthTestFailed),
255            0x0F => Some(Self::ExecutionError),
256            0xEE => Some(Self::WatchdogAboutToExpire),
257            0xFF => Some(Self::CommandCrcError),
258            other => Some(Self::Unknown(other)),
259        }
260    }
261
262    /// Return the raw status byte this variant came from.
263    ///
264    /// Inverse of [`Self::from_status_byte`]. For
265    /// [`ChipError::Unknown`] returns the preserved raw byte.
266    #[must_use]
267    pub const fn as_status_byte(self) -> u8
268    {
269        match self
270        {
271            Self::CheckMacOrVerifyFailed => 0x01,
272            Self::ParseError             => 0x03,
273            Self::EccFault               => 0x05,
274            Self::SelfTestFailed         => 0x07,
275            Self::HealthTestFailed       => 0x08,
276            Self::ExecutionError         => 0x0F,
277            Self::WatchdogAboutToExpire  => 0xEE,
278            Self::CommandCrcError        => 0xFF,
279            Self::Unknown(byte)          => byte,
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests
286{
287    use super::*;
288
289    #[test]
290    fn kind_strips_hal_payload()
291    {
292        let err: AteccError<&'static str> = AteccError::Hal("nack");
293        assert_eq!(err.kind(), AteccErrorKind::Hal);
294    }
295
296    #[test]
297    fn kind_preserves_chip_variant()
298    {
299        let err: AteccError<()> = AteccError::Chip(ChipError::ParseError);
300        assert_eq!(err.kind(), AteccErrorKind::Chip(ChipError::ParseError));
301    }
302
303    #[test]
304    fn sub_codes_are_distinct_and_stable()
305    {
306        let codes =
307        [
308            AteccErrorKind::Hal.as_sub_code(),
309            AteccErrorKind::WakeFailed.as_sub_code(),
310            AteccErrorKind::SelfTestFailure.as_sub_code(),
311            AteccErrorKind::BadCrc.as_sub_code(),
312            AteccErrorKind::Timeout.as_sub_code(),
313            AteccErrorKind::MalformedResponse.as_sub_code(),
314            AteccErrorKind::BufferTooSmall.as_sub_code(),
315        ];
316        // None of them clash with the reserved Chip sentinel `0x00`.
317        for c in codes
318        {
319            assert_ne!(c, 0x00);
320        }
321        // All distinct.
322        for (i, a) in codes.iter().enumerate()
323        {
324            for b in &codes[i + 1..]
325            {
326                assert_ne!(a, b);
327            }
328        }
329    }
330
331    #[test]
332    fn sub_code_round_trip()
333    {
334        for kind in
335        [
336            AteccErrorKind::Hal,
337            AteccErrorKind::WakeFailed,
338            AteccErrorKind::SelfTestFailure,
339            AteccErrorKind::BadCrc,
340            AteccErrorKind::Timeout,
341            AteccErrorKind::MalformedResponse,
342            AteccErrorKind::BufferTooSmall,
343        ]
344        {
345            assert_eq!(AteccErrorKind::from_sub_code(kind.as_sub_code()), Some(kind));
346        }
347    }
348
349    #[test]
350    fn sub_code_zero_is_reserved_for_chip()
351    {
352        assert_eq!(AteccErrorKind::from_sub_code(0x00), None);
353        assert_eq!(AteccErrorKind::Chip(ChipError::ParseError).as_sub_code(), 0x00);
354    }
355
356    #[test]
357    fn chip_error_status_byte_round_trip()
358    {
359        for byte in [0x01u8, 0x03, 0x05, 0x07, 0x08, 0x0F, 0xEE, 0xFF, 0x42]
360        {
361            let err = ChipError::from_status_byte(byte).unwrap();
362            assert_eq!(err.as_status_byte(), byte);
363        }
364        assert!(ChipError::from_status_byte(0x00).is_none());
365    }
366}