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