#![allow(clippy::inline_always)]
use crate::field::tables::{EXP, LOG};
const fn gf_mul_polynomial(mut a: u8, mut b: u8) -> u8 {
let mut product = 0u8;
let mut bit = 0;
while bit < 8 {
if b & 1 != 0 {
product ^= a;
}
let high = a & 0x80;
a <<= 1;
if high != 0 {
a ^= 0x1B;
}
b >>= 1;
bit += 1;
}
product
}
const fn build_nibble_tables() -> [([u8; 16], [u8; 16]); 256] {
let mut tables = [([0u8; 16], [0u8; 16]); 256];
let mut coefficient = 0usize;
while coefficient < 256 {
let mut nibble = 0usize;
while nibble < 16 {
tables[coefficient].0[nibble] = gf_mul_polynomial(coefficient as u8, nibble as u8);
tables[coefficient].1[nibble] =
gf_mul_polynomial(coefficient as u8, (nibble as u8) << 4);
nibble += 1;
}
coefficient += 1;
}
tables
}
static NIBBLE_TABLES: [([u8; 16], [u8; 16]); 256] = build_nibble_tables();
#[inline]
#[must_use]
#[allow(dead_code)]
pub fn make_nibble_tables(c: u8) -> ([u8; 16], [u8; 16]) {
NIBBLE_TABLES[c as usize]
}
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub fn gf_mul(a: u8, b: u8) -> u8 {
if a == 0 || b == 0 {
return 0;
}
EXP[LOG[a as usize] as usize + LOG[b as usize] as usize]
}
pub fn axpy(c: u8, x: &[u8], y: &mut [u8]) {
debug_assert_eq!(x.len(), y.len(), "axpy: length mismatch");
if c == 0 {
return;
}
if c == 1 {
for (yi, &xi) in y.iter_mut().zip(x.iter()) {
*yi ^= xi;
}
return;
}
let (lo, hi) = &NIBBLE_TABLES[c as usize];
for (yi, &xi) in y.iter_mut().zip(x.iter()) {
*yi ^= lo[(xi & 0x0F) as usize] ^ hi[(xi >> 4) as usize];
}
}
pub fn scale(c: u8, x: &[u8], y: &mut [u8]) {
debug_assert_eq!(x.len(), y.len(), "scale: length mismatch");
if c == 0 {
for yi in y.iter_mut() {
*yi = 0;
}
return;
}
if c == 1 {
y.copy_from_slice(x);
return;
}
let (lo, hi) = &NIBBLE_TABLES[c as usize];
for (yi, &xi) in y.iter_mut().zip(x.iter()) {
*yi = lo[(xi & 0x0F) as usize] ^ hi[(xi >> 4) as usize];
}
}
pub fn scale_inplace(c: u8, y: &mut [u8]) {
if c == 0 {
for yi in y.iter_mut() {
*yi = 0;
}
return;
}
if c == 1 {
return;
}
let (lo, hi) = &NIBBLE_TABLES[c as usize];
for yi in y.iter_mut() {
*yi = lo[(*yi & 0x0F) as usize] ^ hi[(*yi >> 4) as usize];
}
}
#[inline]
#[allow(dead_code)]
pub fn xor_assign(x: &[u8], y: &mut [u8]) {
debug_assert_eq!(x.len(), y.len());
for (yi, &xi) in y.iter_mut().zip(x.iter()) {
*yi ^= xi;
}
}
#[must_use]
pub fn dot(a: &[u8], b: &[u8]) -> u8 {
debug_assert_eq!(a.len(), b.len(), "dot: length mismatch");
let mut acc: u8 = 0;
for (&ai, &bi) in a.iter().zip(b.iter()) {
if ai != 0 && bi != 0 {
acc ^= EXP[LOG[ai as usize] as usize + LOG[bi as usize] as usize];
}
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn axpy_c_zero_noop() {
let x = [0x12u8, 0x34, 0x56];
let mut y = [0xAAu8, 0xBB, 0xCC];
let expected = y;
axpy(0, &x, &mut y);
assert_eq!(y, expected);
}
#[test]
fn axpy_c_one_is_xor() {
let x = [0x12u8, 0x34, 0x56];
let mut y = [0xAAu8, 0xBB, 0xCC];
axpy(1, &x, &mut y);
assert_eq!(y, [0xAAu8 ^ 0x12, 0xBB ^ 0x34, 0xCC ^ 0x56]);
}
#[test]
fn axpy_known_vector() {
let c = 0x03u8;
let x = [0x01u8, 0x02, 0x03, 0x04];
let mut y = [0u8; 4];
axpy(c, &x, &mut y);
for i in 0..4 {
assert_eq!(y[i], gf_mul(c, x[i]));
}
}
#[test]
fn scale_vs_axpy() {
let c = 0x53u8;
let x: Vec<u8> = (1u8..=64).collect();
let mut by_scale = vec![0u8; 64];
let mut by_axpy = vec![0u8; 64];
scale(c, &x, &mut by_scale);
axpy(c, &x, &mut by_axpy);
assert_eq!(by_scale, by_axpy);
}
#[test]
fn dot_product() {
let a = [0x01u8, 0x02, 0x03];
let b = [0x01u8, 0x02, 0x03];
let expected = gf_mul(1, 1) ^ gf_mul(2, 2) ^ gf_mul(3, 3);
assert_eq!(dot(&a, &b), expected);
}
#[test]
fn nibble_tables_correctness() {
for c in 0u8..=255 {
let (lo, hi) = make_nibble_tables(c);
for x in 0u8..=255 {
let xlo = (x & 0x0F) as usize;
let xhi = ((x >> 4) & 0x0F) as usize;
let result = lo[xlo] ^ hi[xhi];
assert_eq!(result, gf_mul(c, x), "c=0x{c:02x}, x=0x{x:02x}");
}
}
}
}