Skip to main content

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}