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}