#![no_std]
#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![allow(clippy::cast_possible_truncation)]
use core::fmt::{Debug, Display, Formatter};
use core::result::Result;
use crc::{Crc, Table, Width};
use sort_const::const_quicksort;
#[derive(Clone)]
pub struct CrcCorrector<const L: usize, W: Width> {
crc: Crc<W, Table<1>>,
zero_crc: W,
table: [W; L],
}
macro_rules! crc_reflect {
($crc_error_bit:tt, u16) => {
match $crc_error_bit >> 3 {
0 => $crc_error_bit += 8,
1 => $crc_error_bit -= 8,
_ => unreachable!(),
}
};
($crc_error_bit:tt, u32) => {
match $crc_error_bit >> 3 {
0 => $crc_error_bit += 24,
1 => $crc_error_bit += 8,
2 => $crc_error_bit -= 8,
3 => $crc_error_bit -= 24,
_ => unreachable!(),
}
};
($crc_error_bit:tt, u64) => {
match $crc_error_bit >> 3 {
0 => $crc_error_bit += 56,
1 => $crc_error_bit += 40,
2 => $crc_error_bit += 24,
3 => $crc_error_bit += 8,
4 => $crc_error_bit -= 8,
5 => $crc_error_bit -= 24,
6 => $crc_error_bit -= 40,
7 => $crc_error_bit -= 56,
_ => unreachable!(),
}
};
}
macro_rules! corrector_impl {
($uint:tt) => {
impl<const L: usize> CrcCorrector<L, $uint> {
#[must_use]
pub const fn new(crc: Crc<$uint, Table<1>>) -> Self {
let mut table = [$uint::MIN; L];
if table.len() % 8 != 0 {
panic!("CrcCorrector message length must be a multiple of 8!");
}
if table.len() > ($uint::MAX - ($uint::BITS as $uint)) as usize {
panic!(
"CrcCorrector message length is too large to compute a correction lookup table!"
);
}
let msg_arr = &mut [0u8; L];
let msg = msg_arr.split_at_mut(table.len() >> 3).0;
let zero_crc = crc.checksum(msg);
let mut i = 0;
while i < L {
let byte = i >> 3;
let bit = 1u8 << (i & 7) as u8;
msg[byte] = bit;
let csum = crc.checksum(msg);
table[i] = csum;
msg[byte] = 0;
i += 1;
}
let mut i = 0;
let sorted_table: &[$uint] = &const_quicksort!(table);
while i < (sorted_table.len() - 1) {
if sorted_table[i] == sorted_table[i + 1] || sorted_table[i] == zero_crc {
panic!(
"Provided CRC algorithm is insufficient for single-bit error correction. Either increase the CRC bit length or choose a different polynomial"
);
}
i += 1;
}
Self {
crc,
zero_crc,
table,
}
}
pub fn correct(&self, data: &mut [u8], mut crc: $uint) -> Result<Correction<$uint>, Error> {
if (data.len() << 3) + ($uint::BITS as usize) > self.table.len() {
return Err(Error::DataTooLong);
}
let crc2 = self.crc2(data, crc);
if crc2 == self.zero_crc {
return Err(Error::NoError);
}
let mut i = 0;
let mut error_bit = None;
while i < self.table.len() {
if crc2 == self.table[i] {
error_bit = Some(i as $uint);
break;
}
i += 1;
}
let Some(error_bit) = error_bit else {
return Err(Error::MoreThanOneBitCorrupted);
};
let msg_bit_len = (data.len() << 3) as $uint;
let offset_byte = (error_bit >> 3) as usize;
let offset_bit = 1u8 << (error_bit & 7) as u8;
let mut crc_error_bit = error_bit.wrapping_sub(msg_bit_len);
if error_bit >= msg_bit_len {
if !self.crc.algorithm.refout {
crc_reflect!(crc_error_bit, $uint);
}
let crc_offset_bit = 1 << (crc_error_bit as u8);
crc ^= crc_offset_bit;
} else {
data[offset_byte] ^= offset_bit;
}
let crc2 = self.crc2(&data, crc);
if crc2 != self.zero_crc {
if error_bit < msg_bit_len {
data[offset_byte] ^= offset_bit;
}
return Err(Error::CorrectionFailed);
}
if error_bit >= msg_bit_len {
Ok(Correction::CRC { error_bit: crc_error_bit })
} else {
Ok(Correction::Data { error_bit })
}
}
fn crc2(&self, data: &[u8], mut crc: $uint) -> $uint {
if self.crc.algorithm.refout {
crc = crc.swap_bytes();
}
crc ^= self.crc.algorithm.xorout;
let crc_bytes = crc.to_be_bytes();
let mut digest = self.crc.digest_with_initial(0);
digest.update(&data);
digest.update(&crc_bytes);
let mut remaining_bits = (self.table.len() - (data.len() << 3)) - ($uint::BITS as usize);
while remaining_bits >= 128 {
digest.update(&[0u8; 16]);
remaining_bits -= 128
}
while remaining_bits > 0 {
digest.update(&[0u8; 1]);
remaining_bits -= 8;
}
digest.finalize()
}
}
}
}
corrector_impl!(u16);
corrector_impl!(u32);
corrector_impl!(u64);
#[derive(Debug, PartialEq, Eq)]
pub enum Correction<W: Width> {
CRC {
error_bit: W,
},
Data {
error_bit: W,
},
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
NoError,
MoreThanOneBitCorrupted,
DataTooLong,
CorrectionFailed,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result<(), core::fmt::Error> {
match self {
Self::NoError => {
write!(f, "No error, CRC matches expected")
}
Self::MoreThanOneBitCorrupted => {
write!(
f,
"Unable to correct data with CRC, more than one bit has been corrupted"
)
}
Self::DataTooLong => {
write!(
f,
"Message is too large for CRC correction with this CRC corrector"
)
}
Self::CorrectionFailed => {
write!(
f,
"Unable to correct data with CRC. This is bug in `crc-correction`."
)
}
}
}
}
impl core::error::Error for Error {}