atecc608b/crc.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//! CRC-16 as used by the ATECC608B protocol.
17//!
18//! Parameters of the algorithm (Microchip uses the name "CRC-16/DNP" loosely.
19//! The chip's variant matches none of the standard catalog entries exactly,
20//! the parameters below are the source of truth):
21//!
22//! - Polynomial : `0x8005`
23//! - Initial value : `0x0000`
24//! - Reflect input : false (MSB first)
25//! - Reflect output: false
26//! - XOR output : `0x0000`
27//! - Byte order : low byte first when serialized on the wire
28//!
29//! The implementation is a faithful translation of the bit-by-bit routine in
30//! `lib/calib/calib_command.c` of Microchip's `CryptoAuthLib`. We deliberately
31//! avoid a table-driven approach. The driver only computes a CRC over short
32//! packets at human time scales (microseconds), so the gain would be
33//! negligible while flash usage would grow.
34
35/// Compute the CRC-16 over `data`.
36///
37/// Returns the CRC as a `u16` in native byte order. To write it into a
38/// packet, use [`crc16_to_bytes`] which lays it out low byte first as the
39/// protocol expects.
40#[must_use]
41pub fn crc16(data: &[u8]) -> u16
42{
43 const POLY: u16 = 0x8005;
44
45 let mut crc: u16 = 0x0000;
46
47 for byte in data
48 {
49 for bit_index in 0..8u8
50 {
51 let data_bit = (byte >> bit_index) & 0x01;
52 let crc_bit = ((crc >> 15) & 0x0001) as u8;
53
54 crc <<= 1;
55
56 if data_bit != crc_bit
57 {
58 crc ^= POLY;
59 }
60 }
61 }
62
63 crc
64}
65
66/// Serialize a CRC into the two-byte little-endian form expected on the wire.
67///
68/// The chip transmits and expects the low byte first, then the high byte.
69#[must_use]
70pub fn crc16_to_bytes(crc: u16) -> [u8; 2]
71{
72 [(crc & 0xFF) as u8, ((crc >> 8) & 0xFF) as u8]
73}
74
75/// Verify the trailing two bytes of `frame` against a freshly computed CRC of
76/// everything before them.
77///
78/// Returns `true` if the CRC matches. Returns `false` if `frame` has fewer
79/// than 3 bytes (no room for at least one payload byte plus 2 CRC bytes).
80#[must_use]
81pub(crate) fn verify_trailing_crc(frame: &[u8]) -> bool
82{
83 if frame.len() < 3
84 {
85 return false;
86 }
87
88 let split = frame.len() - 2;
89 let computed = crc16(&frame[..split]);
90 let received = u16::from_le_bytes([frame[split], frame[split + 1]]);
91
92 computed == received
93}
94
95#[cfg(test)]
96mod tests
97{
98 use super::*;
99
100 /// An Info command frame, without word address and without the trailing
101 /// CRC. Length byte is 7 (count includes itself and the CRC). Opcode is
102 /// 0x30, param1=0, param2=0x0000.
103 ///
104 /// The reference CRC for this exact frame, cross-checked against the C
105 /// implementation in `CryptoAuthLib` by running it on the same bytes, is
106 /// 0x5D03. On the wire it is serialized as `0x03 0x5D`.
107 const INFO_FRAME_NO_CRC: [u8; 5] = [0x07, 0x30, 0x00, 0x00, 0x00];
108 const INFO_FRAME_CRC: u16 = 0x5D03;
109
110 /// A Random command frame (opcode 0x1B), same shape, returns 32 random
111 /// bytes. Reference CRC computed the same way.
112 const RANDOM_FRAME_NO_CRC: [u8; 5] = [0x07, 0x1B, 0x00, 0x00, 0x00];
113 const RANDOM_FRAME_CRC: u16 = 0xCD24;
114
115 /// The 4-byte chip wake response `04 11 33 43`. The two trailing bytes
116 /// `33 43` are the CRC of `04 11` on the wire (low byte first), which
117 /// means the raw CRC value of `[0x04, 0x11]` is `0x4333`.
118 const WAKE_RESPONSE_PREFIX: [u8; 2] = [0x04, 0x11];
119 const WAKE_RESPONSE_CRC: u16 = 0x4333;
120
121 #[test]
122 fn empty_input_yields_zero()
123 {
124 assert_eq!(crc16(&[]), 0x0000);
125 }
126
127 #[test]
128 fn single_zero_byte()
129 {
130 assert_eq!(crc16(&[0x00]), 0x0000);
131 }
132
133 #[test]
134 fn info_command_frame()
135 {
136 assert_eq!(crc16(&INFO_FRAME_NO_CRC), INFO_FRAME_CRC);
137 }
138
139 #[test]
140 fn random_command_frame()
141 {
142 assert_eq!(crc16(&RANDOM_FRAME_NO_CRC), RANDOM_FRAME_CRC);
143 }
144
145 #[test]
146 fn wake_response_crc()
147 {
148 assert_eq!(crc16(&WAKE_RESPONSE_PREFIX), WAKE_RESPONSE_CRC);
149 }
150
151 #[test]
152 fn crc_to_bytes_is_little_endian()
153 {
154 assert_eq!(crc16_to_bytes(0x5D03), [0x03, 0x5D]);
155 assert_eq!(crc16_to_bytes(0x0000), [0x00, 0x00]);
156 assert_eq!(crc16_to_bytes(0xFFFF), [0xFF, 0xFF]);
157 }
158
159 #[test]
160 fn verify_accepts_valid_info_frame()
161 {
162 // Append CRC bytes to the Info frame and check.
163 let mut full = [0u8; 7];
164 full[..5].copy_from_slice(&INFO_FRAME_NO_CRC);
165 full[5..].copy_from_slice(&crc16_to_bytes(INFO_FRAME_CRC));
166 assert!(verify_trailing_crc(&full));
167 }
168
169 #[test]
170 fn verify_rejects_corrupted_frame()
171 {
172 let mut full = [0u8; 7];
173 full[..5].copy_from_slice(&INFO_FRAME_NO_CRC);
174 full[5..].copy_from_slice(&crc16_to_bytes(INFO_FRAME_CRC));
175 // Flip a bit in the payload, CRC should no longer match.
176 full[1] ^= 0x01;
177 assert!(!verify_trailing_crc(&full));
178 }
179
180 #[test]
181 fn verify_rejects_too_short_frame()
182 {
183 assert!(!verify_trailing_crc(&[]));
184 assert!(!verify_trailing_crc(&[0xAB]));
185 assert!(!verify_trailing_crc(&[0xAB, 0xCD]));
186 }
187
188 #[test]
189 fn crc_is_deterministic()
190 {
191 // Same input must always yield the same output.
192 let bytes = [0xDE, 0xAD, 0xBE, 0xEF];
193 let a = crc16(&bytes);
194 let b = crc16(&bytes);
195 let c = crc16(&bytes);
196 assert_eq!(a, b);
197 assert_eq!(b, c);
198 }
199}