pub(super) fn bdew_check_digit(digits: &[u8; 10]) -> u8 {
const WEIGHTS: [u8; 10] = [2, 1, 2, 1, 2, 1, 2, 1, 2, 1];
let sum: u32 = digits
.iter()
.zip(WEIGHTS.iter())
.map(|(&d, &w)| {
let p = u32::from(d) * u32::from(w);
if p >= 10 {
p - 9
} else {
p
}
})
.sum();
((10 - (sum % 10)) % 10) as u8
}
pub(super) fn validate_11digit_bdew(s: &str) -> Result<(), crate::error::IdentifierError> {
use crate::error::{IdentifierError, LengthExpectation};
if s.len() != 11 {
return Err(IdentifierError::InvalidLength {
expected: LengthExpectation::Exact(11),
actual: s.len(),
});
}
let mut digits = [0u8; 11];
for (i, c) in s.chars().enumerate() {
match c.to_digit(10) {
Some(d) => digits[i] = d as u8,
None => {
return Err(IdentifierError::InvalidCharacter {
position: i,
character: c,
})
}
}
}
let expected = bdew_check_digit(
digits[..10]
.try_into()
.expect("slice has exactly 10 elements; checked above"),
);
if digits[10] != expected {
return Err(IdentifierError::InvalidChecksum);
}
Ok(())
}
#[cfg(test)]
pub(super) fn make_valid_11digit(prefix: &[u8; 10]) -> String {
let check = bdew_check_digit(prefix);
prefix
.iter()
.chain(std::iter::once(&check))
.map(|&d| char::from_digit(u32::from(d), 10).unwrap())
.collect()
}
pub(super) fn compute_11digit_from_base(
base: &str,
) -> Result<String, crate::error::IdentifierError> {
use crate::error::{IdentifierError, LengthExpectation};
if base.len() != 10 {
return Err(IdentifierError::InvalidLength {
expected: LengthExpectation::Exact(10),
actual: base.len(),
});
}
let mut digits = [0u8; 10];
for (i, c) in base.chars().enumerate() {
match c.to_digit(10) {
Some(d) => digits[i] = d as u8,
None => {
return Err(IdentifierError::InvalidCharacter {
position: i,
character: c,
})
}
}
}
let check = bdew_check_digit(&digits);
let mut result = base.to_owned();
result.push(char::from_digit(u32::from(check), 10).expect("check digit is 0..=9"));
Ok(result)
}
#[inline]
fn ascii_val(b: u8) -> u32 {
if b.is_ascii_digit() {
u32::from(b - b'0')
} else {
u32::from(b)
}
}
pub(super) fn ascii_check_digit(base: &[u8; 10]) -> u8 {
let odd_sum: u32 = base.iter().step_by(2).map(|&b| ascii_val(b)).sum();
let even_sum: u32 = base.iter().skip(1).step_by(2).map(|&b| ascii_val(b)).sum();
((10 - ((odd_sum + even_sum * 2) % 10)) % 10) as u8
}
pub(super) fn validate_ascii_id(
s: &str,
type_char: u8,
) -> Result<(), crate::error::IdentifierError> {
use crate::error::{IdentifierError, LengthExpectation};
if s.len() != 11 {
return Err(IdentifierError::InvalidLength {
expected: LengthExpectation::Exact(11),
actual: s.len(),
});
}
let bytes = s.as_bytes();
if bytes[0] != type_char {
return Err(IdentifierError::InvalidFormat {
description: format!(
"first character (Codetyp) must be '{}', got '{}'",
type_char as char, bytes[0] as char,
)
.into(),
});
}
for (i, &b) in bytes.iter().enumerate().skip(1).take(9) {
if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
return Err(IdentifierError::InvalidCharacter {
position: i,
character: b as char,
});
}
}
let last = bytes[10];
if !last.is_ascii_digit() {
return Err(IdentifierError::InvalidCharacter {
position: 10,
character: last as char,
});
}
let base_arr: [u8; 10] = bytes[..10].try_into().expect("verified 10 bytes above");
if last - b'0' != ascii_check_digit(&base_arr) {
return Err(IdentifierError::InvalidChecksum);
}
Ok(())
}
pub(super) fn compute_ascii_id_from_base(
base: &str,
type_char: u8,
) -> Result<String, crate::error::IdentifierError> {
use crate::error::{IdentifierError, LengthExpectation};
if base.len() != 10 {
return Err(IdentifierError::InvalidLength {
expected: LengthExpectation::Exact(10),
actual: base.len(),
});
}
let bytes = base.as_bytes();
if bytes[0] != type_char {
return Err(IdentifierError::InvalidFormat {
description: format!(
"base must start with '{}' (Codetyp), got '{}'",
type_char as char, bytes[0] as char,
)
.into(),
});
}
for (i, &b) in bytes.iter().enumerate().skip(1).take(9) {
if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
return Err(IdentifierError::InvalidCharacter {
position: i,
character: b as char,
});
}
}
let arr: [u8; 10] = bytes.try_into().expect("verified 10 bytes above");
let check = ascii_check_digit(&arr);
let mut result = base.to_owned();
result.push(char::from_digit(u32::from(check), 10).expect("check digit is 0..=9"));
Ok(result)
}
#[cfg(test)]
pub(super) fn make_valid_ascii_id(type_char: u8, body: &[u8; 9]) -> String {
let mut base = [0u8; 10];
base[0] = type_char;
base[1..].copy_from_slice(body);
let check = ascii_check_digit(&base);
let mut s = String::with_capacity(11);
for &b in &base {
s.push(b as char);
}
s.push(char::from_digit(u32::from(check), 10).unwrap());
s
}