use once_cell::sync::Lazy;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum ChecksumError {
#[error("Input data is too short")]
InputDataTooShort,
#[error("Invalid checksum")]
InvalidChecksum,
}
pub const CHECKSUM_BYTES: usize = 1;
const COEFFICIENTS: [u8; 3] = [4, 3, 1];
static MASK: Lazy<u8> = Lazy::new(|| {
let mut mask = 1u8;
for bit in COEFFICIENTS {
let shift = 1u8.checked_shl(u32::from(bit)).unwrap();
mask = mask.checked_add(shift).unwrap();
}
mask
});
pub fn compute_checksum(data: &[u8]) -> u8 {
let mut result = 0u8;
for digit in data {
result ^= *digit; let overflow = (result & (1 << 7)) != 0;
result <<= 1; if overflow {
result ^= *MASK;
}
}
result
}
pub fn validate_checksum(data: &[u8]) -> Result<&[u8], ChecksumError> {
if data.len() < 2 {
return Err(ChecksumError::InputDataTooShort);
}
match compute_checksum(data) {
0u8 => Ok(data.get(..data.len() - 1).expect("Length is checked")),
_ => Err(ChecksumError::InvalidChecksum),
}
}
#[cfg(test)]
mod test {
#![allow(clippy::indexing_slicing)]
use rand::Rng;
use crate::dammsum::*;
#[test]
fn no_mask_panic() {
let _mask = *MASK;
}
#[test]
fn checksum_validate() {
const SIZE: usize = 33;
let mut rng = rand::thread_rng();
let data: Vec<u8> = (0..SIZE).map(|_| rng.gen::<u8>()).collect();
let mut data_with_checksum = data.clone();
data_with_checksum.push(compute_checksum(&data));
assert_eq!(validate_checksum(&data_with_checksum).unwrap(), data);
}
#[test]
fn identical_checksum() {
const SIZE: usize = 33;
let mut rng = rand::thread_rng();
let data_0: Vec<u8> = (0..SIZE).map(|_| rng.gen::<u8>()).collect();
let check_0 = compute_checksum(&data_0);
let data_1 = data_0;
let check_1 = compute_checksum(&data_1);
assert_eq!(check_0, check_1);
}
#[test]
fn distinct_checksum() {
let data_0 = vec![0u8];
let data_1 = vec![1u8];
let check_0 = compute_checksum(&data_0);
let check_1 = compute_checksum(&data_1);
assert!(check_0 != check_1);
}
#[test]
fn failure_modes_validate() {
let mut data: Vec<u8> = vec![];
assert_eq!(validate_checksum(&data), Err(ChecksumError::InputDataTooShort));
data = vec![0u8];
assert_eq!(validate_checksum(&data), Err(ChecksumError::InputDataTooShort));
}
#[test]
fn substitutions() {
const SIZE: usize = 33;
let mut rng = rand::thread_rng();
let mut data: Vec<u8> = (0..SIZE).map(|_| rng.gen::<u8>()).collect();
data.push(compute_checksum(&data));
assert!(validate_checksum(&data).is_ok());
for j in 0..data.len() {
let mut data_ = data.clone();
for i in 0..=u8::MAX {
if data[j] == i {
continue;
}
data_[j] = i;
assert_eq!(validate_checksum(&data_), Err(ChecksumError::InvalidChecksum));
}
}
}
#[test]
fn transpositions() {
const SIZE: usize = 33;
let mut rng = rand::thread_rng();
let mut data: Vec<u8> = (0..SIZE).map(|_| rng.gen::<u8>()).collect();
data.push(compute_checksum(&data));
assert!(validate_checksum(&data).is_ok());
for j in 0..(data.len() - 1) {
if data[j] == data[j + 1] {
continue;
}
let mut data_ = data.clone();
data_.swap(j, j + 1);
assert_eq!(validate_checksum(&data_), Err(ChecksumError::InvalidChecksum));
}
}
#[test]
fn known_checksum() {
const SIZE: usize = 33;
assert_eq!(compute_checksum(&[0u8; SIZE]), 0u8);
}
}