#![allow(clippy::inline_always)]
#![allow(clippy::suspicious_arithmetic_impl)]
#![allow(clippy::suspicious_op_assign_impl)]
use super::tables::{EXP, INV, LOG};
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
#[repr(transparent)]
pub struct Gf8(pub u8);
impl Gf8 {
pub const ZERO: Self = Gf8(0);
pub const ONE: Self = Gf8(1);
#[inline(always)]
#[must_use]
pub const fn new(v: u8) -> Self {
Gf8(v)
}
#[inline(always)]
#[must_use]
pub const fn value(self) -> u8 {
self.0
}
#[inline(always)]
#[must_use]
pub fn inv(self) -> Self {
Gf8(INV[self.0 as usize])
}
#[inline]
#[must_use]
pub fn pow(self, exp: u32) -> Self {
if self.0 == 0 {
return if exp == 0 { Gf8::ONE } else { Gf8::ZERO };
}
let l = u32::from(LOG[self.0 as usize]);
let idx = ((l * (exp % 255)) % 255) as usize;
Gf8(EXP[idx])
}
#[inline(always)]
#[must_use]
pub fn is_zero(self) -> bool {
self.0 == 0
}
}
impl Add for Gf8 {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Gf8(self.0 ^ rhs.0)
}
}
impl AddAssign for Gf8 {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.0 ^= rhs.0;
}
}
impl Sub for Gf8 {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Gf8(self.0 ^ rhs.0)
}
}
impl SubAssign for Gf8 {
#[inline(always)]
fn sub_assign(&mut self, rhs: Self) {
self.0 ^= rhs.0;
}
}
impl Neg for Gf8 {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
self
}
}
impl Mul for Gf8 {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
if self.0 == 0 || rhs.0 == 0 {
return Gf8(0);
}
let l = LOG[self.0 as usize] as usize + LOG[rhs.0 as usize] as usize;
Gf8(EXP[l])
}
}
impl MulAssign for Gf8 {
#[inline(always)]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl Div for Gf8 {
type Output = Self;
#[inline(always)]
fn div(self, rhs: Self) -> Self {
assert!(rhs.0 != 0, "division by zero in GF(2^8)");
self * rhs.inv()
}
}
impl core::fmt::Debug for Gf8 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Gf8(0x{:02x})", self.0)
}
}
impl core::fmt::Display for Gf8 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "0x{:02x}", self.0)
}
}
impl From<u8> for Gf8 {
#[inline(always)]
fn from(v: u8) -> Self {
Gf8(v)
}
}
impl From<Gf8> for u8 {
#[inline(always)]
fn from(g: Gf8) -> u8 {
g.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_is_xor() {
assert_eq!((Gf8(0x53) + Gf8(0xca)).0, 0x53 ^ 0xca);
}
#[test]
fn sub_equals_add() {
let a = Gf8(0xAB);
let b = Gf8(0xCD);
assert_eq!(a - b, a + b);
}
#[test]
fn mul_by_zero() {
for x in 0u8..=255 {
assert_eq!(Gf8(x) * Gf8::ZERO, Gf8::ZERO);
}
}
#[test]
fn mul_by_one() {
for x in 0u8..=255 {
assert_eq!(Gf8(x) * Gf8::ONE, Gf8(x));
}
}
#[test]
fn mul_commutativity() {
for a in 0u8..=15 {
for b in 0u8..=15 {
assert_eq!(Gf8(a) * Gf8(b), Gf8(b) * Gf8(a));
}
}
}
#[test]
fn mul_associativity() {
let a = Gf8(0x53);
let b = Gf8(0x7e);
let c = Gf8(0x2d);
assert_eq!((a * b) * c, a * (b * c));
}
#[test]
fn distributivity() {
let a = Gf8(0x53);
let b = Gf8(0x7e);
let c = Gf8(0x2d);
assert_eq!(a * (b + c), (a * b) + (a * c));
}
#[test]
fn inv_roundtrip() {
for x in 1u8..=255 {
assert_eq!(Gf8(x) * Gf8(x).inv(), Gf8::ONE, "failed at x=0x{x:02x}");
}
}
#[test]
fn div_roundtrip() {
let a = Gf8(0xAB);
let b = Gf8(0xCD);
assert_eq!((a / b) * b, a);
}
#[test]
fn pow_known_values() {
assert_eq!(Gf8::ZERO.pow(0), Gf8::ONE);
assert_eq!(Gf8::ZERO.pow(1), Gf8::ZERO);
assert_eq!(Gf8(0x03).pow(1), Gf8(0x03));
for x in 1u8..=255 {
assert_eq!(Gf8(x).pow(255), Gf8::ONE, "x^255 != 1 for x=0x{x:02x}");
}
}
#[test]
#[should_panic(expected = "division by zero")]
fn division_by_zero_panics_in_all_profiles() {
let _ = Gf8::ONE / Gf8::ZERO;
}
}