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 {}