use crate::error::{Result, VerhoeffError};
const D_TABLE: [[u8; 10]; 10] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
[2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
[3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
[4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
[5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
[6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
[7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
[8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
];
const P_TABLE: [[u8; 10]; 8] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
[5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
[8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
[9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
[4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
[2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
[7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
];
const INV_TABLE: [u8; 10] = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];
fn string_to_digits(s: &str) -> std::result::Result<Vec<u8>, VerhoeffError> {
if s.is_empty() {
return Err(VerhoeffError::EmptyInput);
}
s.chars()
.map(|c| {
c.to_digit(10)
.map(|d| d as u8)
.ok_or(VerhoeffError::InvalidCharacter(c))
})
.collect()
}
pub fn calculate_checksum(input: &str) -> Result<u8> {
let digits = string_to_digits(input)?;
let mut c = 0u8;
for (i, &digit) in digits.iter().rev().enumerate() {
let permuted_index = (i + 1) % 8;
let permuted = P_TABLE[permuted_index][digit as usize];
c = D_TABLE[c as usize][permuted as usize];
}
Ok(INV_TABLE[c as usize])
}
pub fn validate(input: &str) -> Result<bool> {
let digits = string_to_digits(input)?;
let mut c = 0u8;
for (i, &digit) in digits.iter().rev().enumerate() {
let permuted_index = i % 8;
let permuted = P_TABLE[permuted_index][digit as usize];
c = D_TABLE[c as usize][permuted as usize];
}
Ok(c == 0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{MatterPayloadError, VerhoeffError};
#[test]
fn test_calculate_checksum() {
assert_eq!(calculate_checksum("236").unwrap(), 3);
assert_eq!(calculate_checksum("12345").unwrap(), 1);
assert_eq!(calculate_checksum("142857").unwrap(), 0);
}
#[test]
fn test_validate() {
assert!(validate("2363").unwrap());
assert!(validate("123451").unwrap());
assert!(!validate("2364").unwrap());
assert!(!validate("123450").unwrap());
}
#[test]
fn test_invalid_input() {
let result = calculate_checksum("12a45");
let expected = MatterPayloadError::Verhoeff(VerhoeffError::InvalidCharacter('a'));
assert_eq!(result.unwrap_err(), expected);
let result = validate("");
let expected = MatterPayloadError::Verhoeff(VerhoeffError::EmptyInput);
assert_eq!(result.unwrap_err(), expected);
}
}