use crate::{ct_mask_eq_u8, ct_mask_lt_u8};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AlphabetError {
InvalidByte {
index: usize,
byte: u8,
},
PaddingByte {
index: usize,
},
DuplicateByte {
first: usize,
second: usize,
byte: u8,
},
}
impl core::fmt::Display for AlphabetError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidByte { index, byte } => {
write!(
f,
"invalid base64 alphabet byte 0x{byte:02x} at index {index}"
)
}
Self::PaddingByte { index } => {
write!(f, "base64 alphabet contains padding byte at index {index}")
}
Self::DuplicateByte {
first,
second,
byte,
} => write!(
f,
"base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for AlphabetError {}
#[macro_export]
macro_rules! define_alphabet {
($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
$(#[$meta])*
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
$vis struct $name;
impl $crate::Alphabet for $name {
const ENCODE: [u8; 64] = *$encode;
#[inline]
fn decode(byte: u8) -> Option<u8> {
$crate::decode_alphabet_byte(byte, &Self::ENCODE)
}
}
const _: [(); 1] = [(); match $crate::validate_alphabet(
&<$name as $crate::Alphabet>::ENCODE,
) {
Ok(()) => 1,
Err(_) => 0,
}];
};
}
pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
let mut index = 0;
while index < encode.len() {
let byte = encode[index];
if !is_visible_ascii(byte) {
return Err(AlphabetError::InvalidByte { index, byte });
}
if byte == b'=' {
return Err(AlphabetError::PaddingByte { index });
}
let mut duplicate = index + 1;
while duplicate < encode.len() {
if encode[duplicate] == byte {
return Err(AlphabetError::DuplicateByte {
first: index,
second: duplicate,
byte,
});
}
duplicate += 1;
}
index += 1;
}
Ok(())
}
#[must_use]
pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
let mut index = 0;
let mut candidate = 0;
let mut decoded = 0;
let mut valid = 0;
while index < encode.len() {
let matches = ct_mask_eq_u8(byte, encode[index]);
decoded |= candidate & matches;
valid |= matches;
index += 1;
candidate += 1;
}
if valid == 0 { None } else { Some(decoded) }
}
pub trait Alphabet {
const ENCODE: [u8; 64];
#[must_use]
fn encode(value: u8) -> u8 {
encode_alphabet_value(value, &Self::ENCODE)
}
fn decode(byte: u8) -> Option<u8>;
}
const fn is_visible_ascii(byte: u8) -> bool {
byte >= 0x21 && byte <= 0x7e
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Standard;
impl Alphabet for Standard {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[inline]
fn encode(value: u8) -> u8 {
encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
}
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UrlSafe;
impl Alphabet for UrlSafe {
const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
#[inline]
fn encode(value: u8) -> u8 {
encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
}
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Bcrypt;
impl Alphabet for Bcrypt {
const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_alphabet_byte(byte, &Self::ENCODE)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Crypt;
impl Alphabet for Crypt {
const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
#[inline]
fn decode(byte: u8) -> Option<u8> {
decode_alphabet_byte(byte, &Self::ENCODE)
}
}
#[inline]
pub(crate) const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
encode_alphabet_value(value, &A::ENCODE)
}
#[inline]
pub(crate) fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
A::encode(value)
}
#[inline]
const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
let mut output = 0;
let mut index = 0;
let mut candidate = 0;
while index < encode.len() {
output |= encode[index] & ct_mask_eq_u8(value, candidate);
index += 1;
candidate += 1;
}
output
}
#[inline]
const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
let upper = ct_mask_lt_u8(value, 26);
let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
let value_62 = ct_mask_eq_u8(value, 0x3e);
let value_63 = ct_mask_eq_u8(value, 0x3f);
(value.wrapping_add(b'A') & upper)
| (value.wrapping_sub(26).wrapping_add(b'a') & lower)
| (value.wrapping_sub(52).wrapping_add(b'0') & digit)
| (value_62_byte & value_62)
| (value_63_byte & value_63)
}
#[inline]
fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
let value_62 = ct_mask_eq_u8(byte, value_62_byte);
let value_63 = ct_mask_eq_u8(byte, value_63_byte);
let valid = upper | lower | digit | value_62 | value_63;
let decoded = (byte.wrapping_sub(b'A') & upper)
| (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
| (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
| (0x3e & value_62)
| (0x3f & value_63);
if valid == 0 { None } else { Some(decoded) }
}