crc_correction/lib.rs
1//! # CRC Correction
2//!
3//! Attempt to correct corrupted data with a CRC. This library is able to correct single bit errors in
4//! data so long as the CRC algorithm is known and the data is less than a pre-defined length. Single
5//! bit errors in the CRC are also fixable.
6//!
7//! Uses the [crc](https://crates.io/crates/crc) crate for the actual CRC implementations. We support
8//! all 16, 32 and 64 bit CRC algorithms from [crc](https://crates.io/crates/crc).
9//!
10//! ### Example
11//!
12//! ```rust
13//! use crc::{Crc, Table, CRC_32_CKSUM};
14//! use crc_correction::{CrcCorrector, Error};
15//!
16//! // Maximum message length, in bits, including CRC bits.
17//! const MAX_MSG_LEN: usize = 256;
18//!
19//! // CRC instance to use
20//! const CRC: Crc<u32, Table<1>> =
21//! Crc::<u32, Table<1>>::new(&CRC_32_CKSUM);
22//!
23//! // Corrector instance. Note that this generates a lookup
24//! // table for correction at compile time, so runtime
25//! // checks are faster.
26//! const CORRECTOR: CrcCorrector::<MAX_MSG_LEN, u32> =
27//! CrcCorrector::<MAX_MSG_LEN, u32>::new(CRC);
28//!
29//! fn main() {
30//! // Note that the length leaves 4 bytes room for CRC
31//! // compared to MAX_MSG_LEN
32//! let mut msg = [123u8; 28];
33//! let crc = 0u32;
34//!
35//! let result = CORRECTOR.correct(&mut msg, crc);
36//!
37//! // Since we didn't calculate a CRC in this example
38//! assert_eq!(result, Err(Error::MoreThanOneBitCorrupted));
39//! }
40//! ```
41//!
42//! ### Counter Examples
43//!
44//! ```compile_fail
45//! # use crc::{Crc, Table};
46//! # use crc_correction::CrcCorrector;
47//! const CRC: Crc<u16, Table<1>> =
48//! Crc::<u16, Table<1>>::new(&crc::CRC_16_DNP);
49//!
50//! // Fails to compile, this CRC does not perform well for
51//! // error correction
52//! const CORRECTOR: CrcCorrector::<256, u16> =
53//! CrcCorrector::<256, u16>::new(CRC);
54//! ```
55//!
56//! ```compile_fail
57//! # use crc::{Crc, Table};
58//! # use crc_correction::CrcCorrector;
59//! const CRC: Crc<u16, Table<1>> =
60//! Crc::<u16, Table<1>>::new(&crc::CRC_16_DECT_X);
61//!
62//! // Fails to compile, this message length is too large
63//! // for this CRC for error correction. Select another 16
64//! // bit CRC (or a larger) one to fix this.
65//! # #[allow(long_running_const_eval)]
66//! const CORRECTOR: CrcCorrector::<2048, u16> =
67//! CrcCorrector::<2048, u16>::new(CRC);
68//! ```
69//!
70//! ```compile_fail
71//! # use crc::{Crc, Table};
72//! # use crc_correction::CrcCorrector;
73//! const CRC: Crc<u16, Table<1>> =
74//! Crc::<u16, Table<1>>::new(&crc::CRC_16_DNP);
75//!
76//! // Fails to compile, the message is not a multiple of 8
77//! const CORRECTOR: CrcCorrector::<122, u16> =
78//! CrcCorrector::<122, u16>::new(CRC);
79//! ```
80//!
81//! ```compile_fail
82//! # use crc::{Crc, Table};
83//! # use crc_correction::CrcCorrector;
84//! const CRC: Crc<u16, Table<1>> =
85//! Crc::<u16, Table<1>>::new(&crc::CRC_16_DNP);
86//!
87//! // Fails to compile, the message length is unreasonably
88//! // large for a CRC
89//! const CORRECTOR: CrcCorrector::<65536, u16> =
90//! CrcCorrector::<65536, u16>::new(CRC);
91//! ```
92//!
93//! ### Compile Times
94//!
95//! A lookup table is generated containing a CRC for every bit in the desired maximum message length.
96//! This can take some time to generate. It is recommended to use another form of error correction for
97//! very long messages. If the compiler complains about very long constant evaluation you may generate
98//! the table at runtime by initializing `CrcCorrector` on the heap, or disable the compiler lint as
99//! follows:
100//!
101//! ```rust
102//! use crc::{Crc, Table, CRC_32_CKSUM};
103//! use crc_correction::CrcCorrector;
104//!
105//! const MAX_MSG_LEN: usize = 256;
106//! const CRC: Crc<u32, Table<1>> =
107//! Crc::<u32, Table<1>>::new(&CRC_32_CKSUM);
108//!
109//! // Allow the corrector table generation to take a long
110//! // time during compilation
111//! #[allow(long_running_const_eval)]
112//! const CORRECTOR: CrcCorrector::<MAX_MSG_LEN, u32> =
113//! CrcCorrector::<MAX_MSG_LEN, u32>::new(CRC);
114//! ```
115#![no_std]
116#![forbid(unsafe_code)]
117#![forbid(missing_docs)]
118#![deny(clippy::unwrap_used)]
119#![deny(clippy::expect_used)]
120#![allow(clippy::cast_possible_truncation)]
121
122use core::fmt::{Debug, Display, Formatter};
123use core::result::Result;
124
125use crc::{Crc, Table, Width};
126use sort_const::const_quicksort;
127
128/// CRC Corrector
129///
130/// Associated const `CrcCorrector::L` refers to message length in bits including an appended CRC
131#[derive(Clone)]
132pub struct CrcCorrector<const L: usize, W: Width> {
133 crc: Crc<W, Table<1>>,
134 // CRC of an empty message of bit length L
135 zero_crc: W,
136 // List of CRC's computed for messages of length L (bits) with each entry at index i in the
137 // table corresponding to a message with the bit at i set to 1 and all other bits to 0.
138 table: [W; L],
139}
140
141macro_rules! crc_reflect {
142 ($crc_error_bit:tt, u16) => {
143 match $crc_error_bit >> 3 {
144 0 => $crc_error_bit += 8,
145 1 => $crc_error_bit -= 8,
146 _ => unreachable!(),
147 }
148 };
149 ($crc_error_bit:tt, u32) => {
150 match $crc_error_bit >> 3 {
151 0 => $crc_error_bit += 24,
152 1 => $crc_error_bit += 8,
153 2 => $crc_error_bit -= 8,
154 3 => $crc_error_bit -= 24,
155 _ => unreachable!(),
156 }
157 };
158 ($crc_error_bit:tt, u64) => {
159 match $crc_error_bit >> 3 {
160 0 => $crc_error_bit += 56,
161 1 => $crc_error_bit += 40,
162 2 => $crc_error_bit += 24,
163 3 => $crc_error_bit += 8,
164 4 => $crc_error_bit -= 8,
165 5 => $crc_error_bit -= 24,
166 6 => $crc_error_bit -= 40,
167 7 => $crc_error_bit -= 56,
168 _ => unreachable!(),
169 }
170 };
171}
172
173macro_rules! corrector_impl {
174 ($uint:tt) => {
175 impl<const L: usize> CrcCorrector<L, $uint> {
176 /// Construct CRC Corrector, including lookup table, for a given CRC algorithm and message
177 /// length.
178 ///
179 /// # Panics
180 /// This function will panic if
181 ///
182 /// * The message length is not a multiple of 8. (We expect byte data)
183 /// * The requested table length is too large
184 /// * The CRC algorithm cannot reliably perform single bit correction with the required
185 /// message length
186 ///
187 /// In the last case the problem is similar to a hash collision. Either choose a longer
188 /// CRC (64 bit vs 32 bit vs 16 bit) or a different algorithm. Ultimately there is a
189 /// limit to the message length a CRC can correct errors for.
190 #[must_use]
191 pub const fn new(crc: Crc<$uint, Table<1>>) -> Self {
192 // Ensure table length is within bounds
193 let mut table = [$uint::MIN; L];
194 if table.len() % 8 != 0 {
195 panic!("CrcCorrector message length must be a multiple of 8!");
196 }
197 if table.len() > ($uint::MAX - ($uint::BITS as $uint)) as usize {
198 panic!(
199 "CrcCorrector message length is too large to compute a correction lookup table!"
200 );
201 }
202
203 // Hack to work around no const-generic-exprs
204 let msg_arr = &mut [0u8; L];
205 let msg = msg_arr.split_at_mut(table.len() >> 3).0;
206
207 let zero_crc = crc.checksum(msg);
208
209 // Generate lookup table for given message length and CRC algorithm
210 let mut i = 0;
211 while i < L {
212 let byte = i >> 3;
213 let bit = 1u8 << (i & 7) as u8;
214
215 msg[byte] = bit;
216
217 let csum = crc.checksum(msg);
218 table[i] = csum;
219
220 msg[byte] = 0;
221 i += 1;
222 }
223
224 // Verify all table entries are unique, ensuring that this CrcCorrection instance is valid
225 // for single bit errors
226 let mut i = 0;
227 let sorted_table: &[$uint] = &const_quicksort!(table);
228 while i < (sorted_table.len() - 1) {
229 if sorted_table[i] == sorted_table[i + 1] || sorted_table[i] == zero_crc {
230 panic!(
231 "Provided CRC algorithm is insufficient for single-bit error correction. Either increase the CRC bit length or choose a different polynomial"
232 );
233 }
234 i += 1;
235 }
236
237 Self {
238 crc,
239 zero_crc,
240 table,
241 }
242 }
243
244 /// Correct message with a single bit of corruption in either the provided message or
245 /// the provided CRC. This method mutates the message to correct corruption but will
246 /// not mutate it if correction fails.
247 ///
248 /// If a correction is applied the CRC is validated again to ensure that the integrity
249 /// of the data is okay. This isn't strictly necessary, but guards against any bugs
250 /// which would incorrectly 'correct' data.
251 ///
252 /// # Errors
253 ///
254 /// This method will either return an error if correction could not be applied, or will
255 /// mutate the data with a single bit correction and return an indication of which bit
256 /// was the issue.
257 pub fn correct(&self, data: &mut [u8], mut crc: $uint) -> Result<Correction<$uint>, Error> {
258 if (data.len() << 3) + ($uint::BITS as usize) > self.table.len() {
259 return Err(Error::DataTooLong);
260 }
261
262 // If crc(data + padding + crc) = zero of an all zero's message then the data is fine
263 let crc2 = self.crc2(data, crc);
264 if crc2 == self.zero_crc {
265 return Err(Error::NoError);
266 }
267
268 // Find this CRC in the table
269 let mut i = 0;
270 let mut error_bit = None;
271 while i < self.table.len() {
272 if crc2 == self.table[i] {
273 error_bit = Some(i as $uint);
274 break;
275 }
276 i += 1;
277 }
278
279 // If the CRC isn't in the table then the data is corrupted in more than one bit, and we
280 // can't correct it with this algorithm.
281 let Some(error_bit) = error_bit else {
282 return Err(Error::MoreThanOneBitCorrupted);
283 };
284
285 let msg_bit_len = (data.len() << 3) as $uint;
286 let offset_byte = (error_bit >> 3) as usize;
287 let offset_bit = 1u8 << (error_bit & 7) as u8;
288 let mut crc_error_bit = error_bit.wrapping_sub(msg_bit_len);
289
290 // If the error bit is larger than data length then the error is in the CRC
291 if error_bit >= msg_bit_len {
292 // If the CRC algorithm has reflect the CRC we need to reflect it back to
293 // correct the right bit in the input
294 if !self.crc.algorithm.refout {
295 crc_reflect!(crc_error_bit, $uint);
296 }
297
298 let crc_offset_bit = 1 << (crc_error_bit as u8);
299 crc ^= crc_offset_bit;
300 } else {
301 // Flip erroneous bit in data
302 data[offset_byte] ^= offset_bit;
303 }
304
305 // Check if the correction worked with another CRC of data + appended CRC
306 let crc2 = self.crc2(&data, crc);
307 if crc2 != self.zero_crc {
308 // Correction failed, flip back the changed bit in input before returning
309 if error_bit < msg_bit_len {
310 data[offset_byte] ^= offset_bit;
311 }
312 return Err(Error::CorrectionFailed);
313 }
314
315 // If error bit is bigger than data bit length the error is in the CRC
316 if error_bit >= msg_bit_len {
317 Ok(Correction::CRC { error_bit: crc_error_bit })
318 } else {
319 Ok(Correction::Data { error_bit })
320 }
321 }
322
323 // Calculate CRC of data + appended CRC
324 fn crc2(&self, data: &[u8], mut crc: $uint) -> $uint {
325 // Before calculating the CRC of (data + crc) we need to
326 // 1. Modify the CRC so that it's not reflected or XOR'd
327 // 2. Insert padding zeros to extend the message to length L
328
329 // Reflect and XOR the CRC if the algorithm requires it
330 if self.crc.algorithm.refout {
331 crc = crc.swap_bytes();
332 }
333 crc ^= self.crc.algorithm.xorout;
334 let crc_bytes = crc.to_be_bytes();
335
336 let mut digest = self.crc.digest_with_initial(0);
337 digest.update(&data);
338 digest.update(&crc_bytes);
339
340 // Extend data with zeros to bit length L
341 let mut remaining_bits = (self.table.len() - (data.len() << 3)) - ($uint::BITS as usize);
342 while remaining_bits >= 128 {
343 digest.update(&[0u8; 16]);
344 remaining_bits -= 128
345 }
346 while remaining_bits > 0 {
347 digest.update(&[0u8; 1]);
348 remaining_bits -= 8;
349 }
350
351 digest.finalize()
352 }
353 }
354 }
355}
356
357corrector_impl!(u16);
358corrector_impl!(u32);
359corrector_impl!(u64);
360
361/// Type of correction applied to the data
362#[derive(Debug, PartialEq, Eq)]
363pub enum Correction<W: Width> {
364 /// A single bit in CRC was corrupted
365 CRC {
366 /// Bit offset within the CRC
367 error_bit: W,
368 },
369 /// A single bit was the data was corrupted
370 Data {
371 /// Bit offset within the data
372 error_bit: W,
373 },
374}
375
376/// CRC Correction Error
377#[derive(Debug, PartialEq, Eq)]
378pub enum Error {
379 /// No corruption was found in the provided data. This is an error because the expectation when
380 /// using CRC error correction is that the provided data has been corrupted.
381 NoError,
382 /// We currently only support error correction for one bit. This error indicates that more than
383 /// one bit was corrupted in the provided data.
384 MoreThanOneBitCorrupted,
385 /// Provided data is too long for the `CrcCorrector`. Make sure to set `CrcCorrector::L`
386 /// and `crc::Algorithm` appropriately.
387 DataTooLong,
388 /// Failed to correct the data. This indicates a bug in the CRC or CRC correction code. It will
389 /// only be returned if a correction is applied mistakenly and the integrity double-check has
390 /// caught the problem. The data passed in will have been returned to its original state.
391 ///
392 /// Please raise an issue on GitHub if you see this error.
393 CorrectionFailed,
394}
395
396impl Display for Error {
397 fn fmt(&self, f: &mut Formatter) -> Result<(), core::fmt::Error> {
398 match self {
399 Self::NoError => {
400 write!(f, "No error, CRC matches expected")
401 }
402 Self::MoreThanOneBitCorrupted => {
403 write!(
404 f,
405 "Unable to correct data with CRC, more than one bit has been corrupted"
406 )
407 }
408 Self::DataTooLong => {
409 write!(
410 f,
411 "Message is too large for CRC correction with this CRC corrector"
412 )
413 }
414 Self::CorrectionFailed => {
415 write!(
416 f,
417 "Unable to correct data with CRC. This is bug in `crc-correction`."
418 )
419 }
420 }
421 }
422}
423
424impl core::error::Error for Error {}