sbf_tools/crc.rs
1//! CRC-16-CCITT implementation for SBF block validation
2//!
3//! SBF uses CRC-16-CCITT with polynomial 0x1021 and initial value 0.
4//! The CRC covers ID + Length + Body (excludes sync bytes and CRC field itself).
5
6/// CRC-16-CCITT lookup table (polynomial 0x1021)
7const CRC_TABLE: [u16; 256] = compute_crc_table();
8
9/// Compute the CRC lookup table at compile time
10const fn compute_crc_table() -> [u16; 256] {
11 let mut table = [0u16; 256];
12 let mut i = 0;
13 while i < 256 {
14 let mut crc = (i as u16) << 8;
15 let mut j = 0;
16 while j < 8 {
17 if crc & 0x8000 != 0 {
18 crc = (crc << 1) ^ 0x1021;
19 } else {
20 crc <<= 1;
21 }
22 j += 1;
23 }
24 table[i] = crc;
25 i += 1;
26 }
27 table
28}
29
30/// Calculate CRC-16-CCITT checksum
31///
32/// # Arguments
33/// * `data` - The data to calculate CRC over
34///
35/// # Returns
36/// The 16-bit CRC value
37pub fn crc16_ccitt(data: &[u8]) -> u16 {
38 let mut crc: u16 = 0;
39 for &byte in data {
40 let table_index = ((crc >> 8) ^ (byte as u16)) as usize;
41 crc = (crc << 8) ^ CRC_TABLE[table_index];
42 }
43 crc
44}
45
46/// Validate an SBF block's CRC
47///
48/// # Arguments
49/// * `block_data` - Complete block data starting from sync bytes (0x24 0x40)
50///
51/// # Returns
52/// `true` if the CRC is valid, `false` otherwise
53///
54/// # Block Structure
55/// ```text
56/// [Sync: 2] [CRC: 2] [ID: 2] [Length: 2] [Body: Length-8]
57/// ```
58/// CRC is calculated over [ID + Length + Body], i.e., from offset 4 to end
59pub fn validate_block(block_data: &[u8]) -> bool {
60 if block_data.len() < 8 {
61 return false;
62 }
63
64 // Check sync bytes
65 if block_data[0] != 0x24 || block_data[1] != 0x40 {
66 return false;
67 }
68
69 // Extract stored CRC (bytes 2-3, little-endian)
70 let stored_crc = u16::from_le_bytes([block_data[2], block_data[3]]);
71
72 // Get length from bytes 6-7
73 let length = u16::from_le_bytes([block_data[6], block_data[7]]) as usize;
74
75 // Validate we have enough data
76 if block_data.len() < length {
77 return false;
78 }
79
80 // Calculate CRC over ID + Length + Body (offset 4 to length)
81 // Note: length includes the 8-byte header, so data is from offset 4 to (4 + length - 4) = length
82 let calculated_crc = crc16_ccitt(&block_data[4..length]);
83
84 stored_crc == calculated_crc
85}
86
87/// Calculate CRC for block data (ID + Length + Body) and return expected CRC
88///
89/// # Arguments
90/// * `id_length_body` - Block data starting from ID field (excludes sync and CRC)
91///
92/// # Returns
93/// The calculated CRC-16 value
94pub fn calculate_block_crc(id_length_body: &[u8]) -> u16 {
95 crc16_ccitt(id_length_body)
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_crc_empty() {
104 assert_eq!(crc16_ccitt(&[]), 0);
105 }
106
107 #[test]
108 fn test_crc_known_value() {
109 // Test vector: "123456789" gives 0x31C3 for CRC-16-CCITT with init=0
110 // (0x29B1 is for CRC-16-CCITT-FALSE with init=0xFFFF)
111 let data = b"123456789";
112 assert_eq!(crc16_ccitt(data), 0x31C3);
113 }
114
115 #[test]
116 fn test_crc_table_generation() {
117 // Verify table was generated correctly
118 assert_eq!(CRC_TABLE[0], 0x0000);
119 assert_eq!(CRC_TABLE[1], 0x1021);
120 assert_eq!(CRC_TABLE[255], 0x1EF0);
121 }
122
123 #[test]
124 fn test_validate_block_too_short() {
125 let short_data = [0x24, 0x40, 0x00, 0x00];
126 assert!(!validate_block(&short_data));
127 }
128
129 #[test]
130 fn test_validate_block_wrong_sync() {
131 let bad_sync = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00];
132 assert!(!validate_block(&bad_sync));
133 }
134}