pub(crate) fn multiply(a: u8, b: u8) -> u8 {
debug_assert!(a < 64);
debug_assert!(b < 64);
let mut prod: u16 = 0;
let mut a16 = a as u16;
let mut b16 = b as u16;
while b16 != 0 {
if b16 & 1 != 0 {
prod ^= a16;
}
a16 <<= 1;
if a16 & 0x40 != 0 {
a16 ^= 0x43; }
b16 >>= 1;
}
(prod & 0x3f) as u8
}
pub(crate) fn generator_poly_4() -> [u8; 5] {
let mut poly: [u8; 5] = [1, 0, 0, 0, 0];
let mut alpha: u8 = 1; for _root in 1..=4 {
alpha <<= 1;
if alpha & 0x40 != 0 {
alpha ^= 0x43;
}
for j in (1..=4_usize).rev() {
poly[j] = poly[j - 1] ^ multiply(poly[j], alpha);
}
poly[0] = multiply(poly[0], alpha);
}
poly
}
pub(crate) fn encode_4(data: &[u8]) -> [u8; 4] {
let gen = generator_poly_4();
let n = data.len();
let mut codes = vec![0u8; n + 4];
for (i, &d) in data.iter().enumerate() {
codes[n + 4 - 1 - i] = d;
}
for pos in (0..=codes.len() - 5).rev() {
let pivot = codes[pos + 4];
for j in 0..=4 {
codes[pos + j] ^= multiply(gen[j], pivot);
}
}
[codes[0], codes[1], codes[2], codes[3]]
}
pub(crate) fn generator_poly(k: usize) -> Vec<u8> {
let mut poly = vec![0u8; k + 1];
poly[0] = 1;
let mut alpha: u8 = 1;
for _root in 1..=k {
alpha <<= 1;
if alpha & 0x40 != 0 {
alpha ^= 0x43;
}
for j in (1..=k).rev() {
poly[j] = poly[j - 1] ^ multiply(poly[j], alpha);
}
poly[0] = multiply(poly[0], alpha);
}
poly
}
pub(crate) fn encode_k(data: &[u8], k: usize) -> Vec<u8> {
assert!(k > 0, "k must be positive");
let gen = generator_poly(k);
let n = data.len();
let mut codes = vec![0u8; n + k];
for (i, &d) in data.iter().enumerate() {
codes[n + k - 1 - i] = d;
}
for pos in (0..=codes.len() - k - 1).rev() {
let pivot = codes[pos + k];
for j in 0..=k {
codes[pos + j] ^= multiply(gen[j], pivot);
}
}
codes[..k].to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multiply_known_values() {
assert_eq!(multiply(1, 1), 1);
assert_eq!(multiply(2, 1), 2);
assert_eq!(multiply(2, 2), 4);
assert_eq!(multiply(2, 32), multiply(32, 2));
assert_eq!(multiply(2, 32), 3);
assert_eq!(multiply(0, 17), 0);
assert_eq!(multiply(17, 0), 0);
}
#[test]
fn multiply_is_commutative() {
for a in 0u8..64 {
for b in 0u8..64 {
assert_eq!(
multiply(a, b),
multiply(b, a),
"comm failed for a={a} b={b}"
);
}
}
}
#[test]
fn generator_poly_matches_bwip_js() {
let g = generator_poly_4();
assert_eq!(g, [48, 17, 29, 30, 1]);
}
#[test]
fn auspost_check_matches_bwip_js() {
let data = [4u8, 17, 9, 5, 26, 9, 43];
let check = encode_4(&data);
assert_eq!(check, [15, 19, 44, 26]);
}
#[test]
fn generator_poly_for_k_matches_bwip_js() {
assert_eq!(generator_poly(4), vec![48, 17, 29, 30, 1]);
assert_eq!(
generator_poly(10),
vec![46, 44, 49, 3, 2, 57, 42, 39, 28, 31, 1],
);
assert_eq!(
generator_poly(20),
vec![59, 23, 19, 31, 33, 38, 17, 22, 48, 15, 36, 57, 37, 22, 8, 27, 33, 11, 44, 23, 1],
);
}
#[test]
fn encode_k_round_trips_with_encode_4() {
let data = [4u8, 17, 9, 5, 26, 9, 43];
let check_4 = encode_4(&data);
let check_k = encode_k(&data, 4);
assert_eq!(check_k, check_4.to_vec());
}
#[test]
fn multiply_gf64_identity_and_zero() {
assert_eq!(multiply(1, 0), 0);
assert_eq!(multiply(1, 5), 5);
assert_eq!(multiply(1, 63), 63);
assert_eq!(multiply(5, 1), 5);
assert_eq!(multiply(0, 0), 0);
assert_eq!(multiply(0, 63), 0);
assert_eq!(multiply(63, 0), 0);
assert_eq!(multiply(1, 1), 1);
assert_eq!(multiply(2, 2), 4);
assert_eq!(multiply(4, 4), 16);
assert_eq!(multiply(8, 8), 0x40 ^ 0x43); assert_eq!(multiply(5, 7), multiply(7, 5));
assert_eq!(multiply(13, 41), multiply(41, 13));
}
#[test]
fn generator_poly_4_matches_general_form() {
let g4 = generator_poly_4();
let gk4 = generator_poly(4);
assert_eq!(g4.len(), 5);
assert_eq!(gk4.len(), 5);
for i in 0..5 {
assert_eq!(g4[i], gk4[i], "coeff {i}: g4={} vs gk4={}", g4[i], gk4[i]);
}
assert_eq!(g4[4], 1);
assert_eq!(generator_poly(0), vec![1]);
}
}