Skip to main content

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}