Skip to main content

atecc608b/command/
counter.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//! `Counter` command.
17//!
18//! Reads or increments one of the chip's two 21-bit monotonic counters
19//! (Counter0 and Counter1). Counters never decrease and cannot be reset
20//! once the data zone is locked.
21//!
22//! In this project's slot model:
23//!
24//! - Counter0 backs Slot 5 (PIN hash). It is bumped by 1 on every `CheckMac`
25//!   against slot 5, with the convention that the driver rounds the counter
26//!   up to the next multiple of 5 on successful verification so the user
27//!   always gets a fresh batch of 5 attempts.
28//! - Counter1 backs Slot 6 (PUK hash). Same mechanism with batches of 10.
29//!
30//! Reference: `CryptoAuthLib` `lib/calib/calib_counter.c`, constants
31//! `COUNTER_MODE_READ` (0x00), `COUNTER_MODE_INCREMENT` (0x01).
32
33use crate::driver::AteccChannel;
34use crate::error::AteccError;
35use crate::hal::AteccHal;
36use crate::opcodes::{EXEC_TIME_COUNTER_MS, OP_COUNTER};
37
38/// `param1` mode bits: read the counter without modifying it.
39const COUNTER_MODE_READ: u8 = 0x00;
40
41/// `param1` mode bits: increment the counter by 1, then return the new
42/// value.
43const COUNTER_MODE_INCREMENT: u8 = 0x01;
44
45/// One of the chip's two monotonic counters.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub enum CounterId
49{
50    /// Counter0. Used for PIN attempt tracking in this project.
51    Counter0,
52    /// Counter1. Used for PUK attempt tracking in this project.
53    Counter1,
54}
55
56impl CounterId
57{
58    /// Numeric value used as `param2`.
59    const fn as_param2(self) -> u16
60    {
61        match self
62        {
63            CounterId::Counter0 => 0x0000,
64            CounterId::Counter1 => 0x0001,
65        }
66    }
67}
68
69impl<H> AteccChannel<'_, H>
70where
71    H: AteccHal,
72{
73    /// Read the current value of a counter without modifying it.
74    ///
75    /// # Errors
76    /// See [`AteccChannel::execute_command`].
77    pub async fn counter_read
78    (
79        &mut self,
80        counter: CounterId,
81    ) -> Result<u32, AteccError<H::Error>>
82    {
83        self.counter_internal(COUNTER_MODE_READ, counter).await
84    }
85
86    /// Increment a counter by 1 and return its new value.
87    ///
88    /// # Errors
89    /// See [`AteccChannel::execute_command`]. The chip returns
90    /// [`crate::error::ChipError::ExecutionError`] when the counter has
91    /// reached its maximum value of `2^21 - 1`.
92    pub async fn counter_increment
93    (
94        &mut self,
95        counter: CounterId,
96    ) -> Result<u32, AteccError<H::Error>>
97    {
98        self.counter_internal(COUNTER_MODE_INCREMENT, counter).await
99    }
100
101    async fn counter_internal
102    (
103        &mut self,
104        mode: u8,
105        counter: CounterId,
106    ) -> Result<u32, AteccError<H::Error>>
107    {
108        // Response: count(1) + 4 little-endian counter + crc(2) = 7 bytes.
109        let mut response_buf = [0u8; 1 + 4 + 2];
110        let payload = self
111            .execute_command
112            (
113                OP_COUNTER,
114                mode,
115                counter.as_param2(),
116                &[],
117                EXEC_TIME_COUNTER_MS,
118                &mut response_buf,
119            )
120            .await?;
121
122        let bytes: &[u8; 4] = payload
123            .try_into()
124            .map_err(|_| AteccError::MalformedResponse)?;
125        Ok(u32::from_le_bytes(*bytes))
126    }
127}
128
129#[cfg(test)]
130mod tests
131{
132    use super::*;
133
134    #[test]
135    fn counter_modes_match_cryptoauthlib_constants()
136    {
137        assert_eq!(COUNTER_MODE_READ, 0x00);
138        assert_eq!(COUNTER_MODE_INCREMENT, 0x01);
139    }
140
141    #[test]
142    fn counter_id_encodes_as_param2()
143    {
144        assert_eq!(CounterId::Counter0.as_param2(), 0x0000);
145        assert_eq!(CounterId::Counter1.as_param2(), 0x0001);
146    }
147}