const CRC_POLY: u32 = 0x00FF_F409;
const CRC_TABLE: [u32; 256] = build_table();
const fn build_table() -> [u32; 256] {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
#[allow(
clippy::cast_possible_truncation,
reason = "i is always < 256 here, fits u32"
)]
let mut c = (i as u32) << 16;
let mut bit = 0;
while bit < 8 {
c = if c & 0x0080_0000 != 0 {
(c << 1) ^ CRC_POLY
} else {
c << 1
};
bit += 1;
}
table[i] = c & 0x00FF_FFFF;
i += 1;
}
table
}
#[must_use]
pub fn remainder(frame: &[u8; 14]) -> u32 {
let mut crc: u32 = 0;
for &byte in &frame[..11] {
let index = ((crc >> 16) ^ u32::from(byte)) & 0xFF;
crc = ((crc << 8) & 0x00FF_FFFF) ^ CRC_TABLE[index as usize];
}
let parity = (u32::from(frame[11]) << 16) | (u32::from(frame[12]) << 8) | u32::from(frame[13]);
(crc ^ parity) & 0x00FF_FFFF
}
#[must_use]
pub fn verify(frame: &[u8; 14]) -> bool {
remainder(frame) == 0
}
#[allow(
clippy::cast_possible_truncation,
reason = "crc is masked to 24 bits above, so each byte slice fits u8"
)]
pub fn write_parity(frame: &mut [u8; 14]) {
let mut crc: u32 = 0;
for &byte in &frame[..11] {
let index = ((crc >> 16) ^ u32::from(byte)) & 0xFF;
crc = ((crc << 8) & 0x00FF_FFFF) ^ CRC_TABLE[index as usize];
}
frame[11] = (crc >> 16) as u8;
frame[12] = (crc >> 8) as u8;
frame[13] = crc as u8;
}
#[cfg(test)]
mod tests {
use super::*;
fn hex_to_frame(hex: &str) -> [u8; 14] {
let mut frame = [0u8; 14];
for i in 0..14 {
frame[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap();
}
frame
}
#[test]
fn verifies_a_real_captured_frame() {
let frame = hex_to_frame("8D406B902015A678D4D220AA4BDA");
assert_eq!(remainder(&frame), 0);
assert!(verify(&frame));
}
#[test]
fn detects_a_corrupted_frame() {
let mut frame = hex_to_frame("8D406B902015A678D4D220AA4BDA");
frame[5] ^= 0x01;
assert!(!verify(&frame));
}
#[test]
fn write_parity_produces_a_verifiable_frame() {
let original = hex_to_frame("8D406B902015A678D4D220AA4BDA");
let mut frame = original;
frame[11] = 0;
frame[12] = 0;
frame[13] = 0;
write_parity(&mut frame);
assert_eq!(frame, original);
assert!(verify(&frame));
}
}