use core::convert::{Infallible, TryFrom};
use core::{fmt, num, ops};
#[cfg(all(test, mutate))]
use mutagen::mutate;
use crate::error::write_err;
#[rustfmt::skip]
const LOG: [isize; 32] = [
0, 0, 1, 14, 2, 28, 15, 22,
3, 5, 29, 26, 16, 7, 23, 11,
4, 25, 6, 10, 30, 13, 27, 21,
17, 18, 8, 19, 24, 9, 12, 20,
];
#[rustfmt::skip]
const LOG_INV: [u8; 31] = [
1, 2, 4, 8, 16, 9, 18, 13,
26, 29, 19, 15, 30, 21, 3, 6,
12, 24, 25, 27, 31, 23, 7, 14,
28, 17, 11, 22, 5, 10, 20,
];
#[rustfmt::skip]
const CHARS_LOWER: [char; 32] = [
'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', 'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', ];
#[rustfmt::skip]
const CHARS_INV: [i8; 128] = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Fe32(pub(crate) u8);
impl Fe32 {
pub const Q: Fe32 = Fe32(0);
pub const P: Fe32 = Fe32(1);
pub const Z: Fe32 = Fe32(2);
pub const R: Fe32 = Fe32(3);
pub const Y: Fe32 = Fe32(4);
pub const _9: Fe32 = Fe32(5);
pub const X: Fe32 = Fe32(6);
pub const _8: Fe32 = Fe32(7);
pub const G: Fe32 = Fe32(8);
pub const F: Fe32 = Fe32(9);
pub const _2: Fe32 = Fe32(10);
pub const T: Fe32 = Fe32(11);
pub const V: Fe32 = Fe32(12);
pub const D: Fe32 = Fe32(13);
pub const W: Fe32 = Fe32(14);
pub const _0: Fe32 = Fe32(15);
pub const S: Fe32 = Fe32(16);
pub const _3: Fe32 = Fe32(17);
pub const J: Fe32 = Fe32(18);
pub const N: Fe32 = Fe32(19);
pub const _5: Fe32 = Fe32(20);
pub const _4: Fe32 = Fe32(21);
pub const K: Fe32 = Fe32(22);
pub const H: Fe32 = Fe32(23);
pub const C: Fe32 = Fe32(24);
pub const E: Fe32 = Fe32(25);
pub const _6: Fe32 = Fe32(26);
pub const M: Fe32 = Fe32(27);
pub const U: Fe32 = Fe32(28);
pub const A: Fe32 = Fe32(29);
pub const _7: Fe32 = Fe32(30);
pub const L: Fe32 = Fe32(31);
#[inline]
pub fn iter_alpha() -> impl Iterator<Item = Fe32> {
[
Fe32::A,
Fe32::C,
Fe32::D,
Fe32::E,
Fe32::F,
Fe32::G,
Fe32::H,
Fe32::J,
Fe32::K,
Fe32::L,
Fe32::M,
Fe32::N,
Fe32::P,
Fe32::Q,
Fe32::R,
Fe32::S,
Fe32::T,
Fe32::U,
Fe32::V,
Fe32::W,
Fe32::X,
Fe32::Y,
Fe32::Z,
Fe32::_0,
Fe32::_2,
Fe32::_3,
Fe32::_4,
Fe32::_5,
Fe32::_6,
Fe32::_7,
Fe32::_8,
Fe32::_9,
]
.iter()
.copied()
}
#[inline]
pub fn from_char(c: char) -> Result<Fe32, FromCharError> {
use FromCharError::*;
let byte = i8::try_from(u32::from(c)).map_err(|_| NotAscii(c))?;
let ascii = byte as usize;
let u5 = u8::try_from(CHARS_INV[ascii]).map_err(|_| Invalid(c))?;
Ok(Fe32(u5))
}
pub fn from_char_unchecked(c: u8) -> Fe32 { Fe32(CHARS_INV[usize::from(c)] as u8) }
#[inline]
pub fn to_char(self) -> char {
CHARS_LOWER[usize::from(self.0)]
}
#[inline]
pub fn to_u8(self) -> u8 { self.0 }
fn _add(self, other: Fe32) -> Fe32 { Fe32(self.0 ^ other.0) }
fn _sub(self, other: Fe32) -> Fe32 { self + other }
#[cfg_attr(all(test, mutate), mutate)]
fn _mul(self, other: Fe32) -> Fe32 {
if self.0 == 0 || other.0 == 0 {
Fe32(0)
} else {
let log1 = LOG[self.0 as usize];
let log2 = LOG[other.0 as usize];
Fe32(LOG_INV[((log1 + log2) % 31) as usize])
}
}
#[cfg_attr(all(test, mutate), mutate)]
fn _div(self, other: Fe32) -> Fe32 {
if self.0 == 0 {
Fe32(0)
} else if other.0 == 0 {
panic!("Attempt to divide {} by 0 in GF32", self);
} else {
let log1 = LOG[self.0 as usize];
let log2 = LOG[other.0 as usize];
Fe32(LOG_INV[((31 + log1 - log2) % 31) as usize])
}
}
}
impl fmt::Display for Fe32 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.to_char(), f) }
}
impl From<Fe32> for u8 {
#[inline]
fn from(v: Fe32) -> u8 { v.0 }
}
macro_rules! impl_try_from {
($($ty:ident)+) => {
$(
impl TryFrom<$ty> for Fe32 {
type Error = TryFromError;
#[inline]
fn try_from(value: $ty) -> Result<Self, Self::Error> {
let byte = u8::try_from(value)?;
if byte > 31 {
Err(TryFromError::InvalidByte(byte))?;
}
Ok(Fe32(byte))
}
}
)+
}
}
impl_try_from!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128);
impl AsRef<u8> for Fe32 {
#[inline]
fn as_ref(&self) -> &u8 { &self.0 }
}
macro_rules! impl_op_matrix {
($op:ident, $op_fn:ident, $call_fn:ident) => {
impl ops::$op<Fe32> for Fe32 {
type Output = Fe32;
#[inline]
fn $op_fn(self, other: Fe32) -> Fe32 { self.$call_fn(other) }
}
impl ops::$op<Fe32> for &Fe32 {
type Output = Fe32;
#[inline]
fn $op_fn(self, other: Fe32) -> Fe32 { self.$call_fn(other) }
}
impl ops::$op<&Fe32> for Fe32 {
type Output = Fe32;
#[inline]
fn $op_fn(self, other: &Fe32) -> Fe32 { self.$call_fn(*other) }
}
impl ops::$op<&Fe32> for &Fe32 {
type Output = Fe32;
#[inline]
fn $op_fn(self, other: &Fe32) -> Fe32 { self.$call_fn(*other) }
}
};
}
impl_op_matrix!(Add, add, _add);
impl_op_matrix!(Sub, sub, _sub);
impl_op_matrix!(Mul, mul, _mul);
impl_op_matrix!(Div, div, _div);
impl ops::AddAssign for Fe32 {
#[inline]
fn add_assign(&mut self, other: Fe32) { *self = *self + other; }
}
impl ops::SubAssign for Fe32 {
#[inline]
fn sub_assign(&mut self, other: Fe32) { *self = *self - other; }
}
impl ops::MulAssign for Fe32 {
#[inline]
fn mul_assign(&mut self, other: Fe32) { *self = *self * other; }
}
impl ops::DivAssign for Fe32 {
#[inline]
fn div_assign(&mut self, other: Fe32) { *self = *self / other; }
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum FromCharError {
NotAscii(char),
Invalid(char),
}
impl fmt::Display for FromCharError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FromCharError::*;
match *self {
NotAscii(c) => write!(f, "non-ascii char in field element: {}", c),
Invalid(c) => write!(f, "invalid char in field element: {}", c),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromCharError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use FromCharError::*;
match *self {
NotAscii(_) | Invalid(_) => None,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum TryFromError {
NotAByte(num::TryFromIntError),
InvalidByte(u8),
}
impl fmt::Display for TryFromError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use TryFromError::*;
match *self {
NotAByte(ref e) => write_err!(f, "invalid field element"; e),
InvalidByte(ref b) => write!(f, "invalid byte in field element: {:#04x}", b),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TryFromError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use TryFromError::*;
match *self {
NotAByte(ref e) => Some(e),
InvalidByte(_) => None,
}
}
}
impl From<num::TryFromIntError> for TryFromError {
#[inline]
fn from(e: num::TryFromIntError) -> Self { Self::NotAByte(e) }
}
impl From<Infallible> for TryFromError {
#[inline]
fn from(i: Infallible) -> Self { match i {} }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn numeric_string() {
let s: String = (0..32).map(Fe32).map(Fe32::to_char).collect();
assert_eq!(s, "qpzry9x8gf2tvdw0s3jn54khce6mua7l");
}
#[test]
fn translation_wheel() {
let logbase = Fe32(20);
let mut init = Fe32(1);
let mut s = String::new();
for _ in 0..31 {
s.push(init.to_char());
init *= logbase;
}
assert_eq!(s, "p529kt3uw8hlmecvxr470na6djfsgyz");
let logbase = Fe32(20);
let mut init = Fe32(1);
let mut s = String::new();
for _ in 0..31 {
s.push(init.to_char());
init /= logbase;
}
assert_eq!(s, "pzygsfjd6an074rxvcemlh8wu3tk925");
}
#[test]
fn recovery_wheel() {
let logbase = Fe32(10);
let mut init = Fe32(1);
let mut s = String::new();
for _ in 0..31 {
s.push((init + Fe32(16)).to_char());
init *= logbase;
}
assert_eq!(s, "36xp78tgk9ldaecjy4mvh0funwr2zq5");
}
#[test]
fn reverse_charset() {
fn get_char_value(c: char) -> i8 {
let charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
match charset.find(c.to_ascii_lowercase()) {
Some(x) => x as i8,
None => -1,
}
}
let expected_rev_charset =
(0u8..128).map(|i| get_char_value(i as char)).collect::<Vec<_>>();
assert_eq!(&(CHARS_INV[..]), expected_rev_charset.as_slice());
}
#[test]
fn from_char() {
for c in &CHARS_LOWER[..] {
assert!(Fe32::from_char(*c).is_ok())
}
}
#[test]
fn from_upper_char() {
let lower = Fe32::from_char('q').expect("failed to create fe32 from lowercase ascii char");
let upper = Fe32::from_char('Q').expect("failed to create fe32 from uppercase ascii char");
assert_eq!(lower, upper);
}
#[test]
fn mul_zero() {
for c in &CHARS_LOWER[..] {
let fe = Fe32::from_char(*c).unwrap();
assert_eq!(fe._mul(Fe32::Q), Fe32::Q) }
}
#[test]
#[should_panic]
fn div_zero() {
let _ = Fe32::P / Fe32::Q; }
#[test]
fn div_self_zero() {
let fe = Fe32::Z; assert_eq!(Fe32::Q / fe, Fe32::Q) }
#[test]
fn mul_one() {
for c in &CHARS_LOWER[..] {
let fe = Fe32::from_char(*c).unwrap();
assert_eq!(fe * Fe32::P, fe) }
}
}
#[cfg(kani)]
mod verification {
use super::*;
#[kani::proof]
fn check_char_conversion() {
let any: char = kani::any();
if let Ok(fe) = Fe32::from_char(any) {
let got = fe.to_char();
let want = any.to_ascii_lowercase();
assert_eq!(got, want);
}
}
}