bitfold_protocol/command_codec/
checksum.rs

1//! CRC32 checksum utilities for data integrity verification.
2
3use std::io;
4
5use crc32fast::Hasher;
6
7/// Appends a CRC32 checksum to the encoded packet data.
8/// Returns a new vector with the checksum appended.
9pub fn append_checksum(data: &[u8]) -> Vec<u8> {
10    let mut hasher = Hasher::new();
11    hasher.update(data);
12    let checksum = hasher.finalize();
13
14    let mut result = Vec::with_capacity(data.len() + 4);
15    result.extend_from_slice(data);
16    result.extend_from_slice(&checksum.to_be_bytes());
17    result
18}
19
20/// Appends a CRC32 checksum to the provided buffer in-place.
21pub fn append_checksum_in_place(data: &mut Vec<u8>) {
22    let mut hasher = Hasher::new();
23    hasher.update(data);
24    let checksum = hasher.finalize();
25    data.extend_from_slice(&checksum.to_be_bytes());
26}
27
28/// Validates and strips the CRC32 checksum from packet data.
29/// Returns the data without checksum if valid, or an error if checksum fails.
30pub fn validate_and_strip_checksum(data: &[u8]) -> io::Result<&[u8]> {
31    if data.len() < 4 {
32        return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for checksum"));
33    }
34
35    let (payload, checksum_bytes) = data.split_at(data.len() - 4);
36    let received_checksum = u32::from_be_bytes([
37        checksum_bytes[0],
38        checksum_bytes[1],
39        checksum_bytes[2],
40        checksum_bytes[3],
41    ]);
42
43    let mut hasher = Hasher::new();
44    hasher.update(payload);
45    let computed_checksum = hasher.finalize();
46
47    if received_checksum != computed_checksum {
48        return Err(io::Error::new(
49            io::ErrorKind::InvalidData,
50            format!(
51                "CRC32 checksum mismatch: expected {}, got {}",
52                computed_checksum, received_checksum
53            ),
54        ));
55    }
56
57    Ok(payload)
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_checksum_append_and_validate() {
66        let data = b"Hello, world!";
67        let with_checksum = append_checksum(data);
68        assert_eq!(with_checksum.len(), data.len() + 4);
69
70        let validated = validate_and_strip_checksum(&with_checksum).unwrap();
71        assert_eq!(validated, data);
72    }
73
74    #[test]
75    fn test_checksum_validation_fails_on_corruption() {
76        let data = b"Hello, world!";
77        let mut with_checksum = append_checksum(data);
78
79        // Corrupt the checksum
80        let len = with_checksum.len();
81        with_checksum[len - 1] ^= 0xFF;
82
83        assert!(validate_and_strip_checksum(&with_checksum).is_err());
84    }
85
86    #[test]
87    fn test_checksum_validation_rejects_short_data() {
88        let data = b"Hi";
89        assert!(validate_and_strip_checksum(data).is_err());
90    }
91
92    #[test]
93    fn test_checksum_with_empty_data() {
94        let data = b"";
95        let with_checksum = append_checksum(data);
96        assert_eq!(with_checksum.len(), 4);
97
98        let validated = validate_and_strip_checksum(&with_checksum).unwrap();
99        assert_eq!(validated, data);
100    }
101
102    #[test]
103    fn test_append_checksum_in_place() {
104        let data = b"Test data";
105        let mut buffer = data.to_vec();
106        append_checksum_in_place(&mut buffer);
107
108        assert_eq!(buffer.len(), data.len() + 4);
109        let validated = validate_and_strip_checksum(&buffer).unwrap();
110        assert_eq!(validated, data);
111    }
112
113    #[test]
114    fn test_checksum_with_large_data() {
115        // Test with 1MB of data
116        let large_data = vec![0x42u8; 1024 * 1024];
117        let with_checksum = append_checksum(&large_data);
118
119        assert_eq!(with_checksum.len(), large_data.len() + 4);
120
121        let validated = validate_and_strip_checksum(&with_checksum).unwrap();
122        assert_eq!(validated, &large_data[..]);
123    }
124
125    #[test]
126    fn test_checksum_detects_payload_corruption() {
127        let data = b"Important data that must not be corrupted";
128        let mut with_checksum = append_checksum(data);
129
130        // Corrupt a byte in the middle of the payload
131        with_checksum[20] ^= 0x01;
132
133        let result = validate_and_strip_checksum(&with_checksum);
134        assert!(result.is_err());
135        assert!(result.unwrap_err().to_string().contains("CRC32 checksum mismatch"));
136    }
137
138    #[test]
139    fn test_checksum_detects_checksum_corruption() {
140        let data = b"Test data";
141        let mut with_checksum = append_checksum(data);
142
143        // Corrupt the checksum bytes themselves
144        let len = with_checksum.len();
145        with_checksum[len - 2] ^= 0x01;
146
147        let result = validate_and_strip_checksum(&with_checksum);
148        assert!(result.is_err());
149    }
150
151    #[test]
152    fn test_checksum_consistency() {
153        // Same data should produce same checksum
154        let data = b"Consistent data";
155        let checksum1 = append_checksum(data);
156        let checksum2 = append_checksum(data);
157
158        assert_eq!(checksum1, checksum2);
159    }
160
161    #[test]
162    fn test_checksum_different_data_different_checksum() {
163        // Different data should produce different checksums
164        let data1 = b"First message";
165        let data2 = b"Second message";
166
167        let checksum1 = append_checksum(data1);
168        let checksum2 = append_checksum(data2);
169
170        // The checksums (last 4 bytes) should be different
171        let cs1 = &checksum1[checksum1.len() - 4..];
172        let cs2 = &checksum2[checksum2.len() - 4..];
173        assert_ne!(cs1, cs2);
174    }
175
176    #[test]
177    fn test_checksum_binary_data() {
178        // Test with binary data (not just text)
179        let binary_data = vec![0x00, 0xFF, 0xAA, 0x55, 0x12, 0x34, 0x56, 0x78];
180        let with_checksum = append_checksum(&binary_data);
181
182        let validated = validate_and_strip_checksum(&with_checksum).unwrap();
183        assert_eq!(validated, &binary_data[..]);
184    }
185
186    #[test]
187    fn test_checksum_single_byte_data() {
188        let data = b"A";
189        let with_checksum = append_checksum(data);
190
191        assert_eq!(with_checksum.len(), 5); // 1 byte data + 4 bytes checksum
192
193        let validated = validate_and_strip_checksum(&with_checksum).unwrap();
194        assert_eq!(validated, data);
195    }
196
197    #[test]
198    fn test_in_place_vs_allocating() {
199        // Verify both methods produce identical results
200        let data = b"Test data for comparison";
201
202        let allocated = append_checksum(data);
203
204        let mut in_place = data.to_vec();
205        append_checksum_in_place(&mut in_place);
206
207        assert_eq!(allocated, in_place);
208    }
209
210    #[test]
211    fn test_checksum_validation_error_message() {
212        let data = b"Test";
213        let mut with_checksum = append_checksum(data);
214
215        // Corrupt it
216        with_checksum[0] ^= 0xFF;
217
218        let result = validate_and_strip_checksum(&with_checksum);
219        assert!(result.is_err());
220
221        let err = result.unwrap_err();
222        let err_msg = err.to_string();
223        assert!(err_msg.contains("CRC32 checksum mismatch"));
224        assert!(err_msg.contains("expected"));
225        assert!(err_msg.contains("got"));
226    }
227}