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