bitfold_protocol/command_codec/
checksum.rs1use std::io;
4
5use crc32fast::Hasher;
6
7pub 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
20pub 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
28pub 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 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 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 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 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 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 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 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 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); 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 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 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}