pub(crate) const ALPHABET_LEN: usize = 64;
pub(super) const STANDARD_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
};
pub(super) const URL_SAFE_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
};
pub(super) const BCRYPT_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
};
pub(super) const CRYPT_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
};
pub(super) const PBKDF2_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./",
};
pub const BINHEX_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr",
};
pub(super) const IMAP_MUTF7_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ValidatedAlphabetError {
InvalidLength {
actual: usize,
},
InvalidByte {
index: usize,
byte: u8,
},
PaddingByte {
index: usize,
},
DuplicateByte {
first: usize,
second: usize,
byte: u8,
},
}
impl core::fmt::Display for ValidatedAlphabetError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidLength { actual } => {
write!(
formatter,
"base64 alphabet has length {actual}; expected {ALPHABET_LEN}"
)
}
Self::InvalidByte { index, byte } => {
write!(
formatter,
"invalid base64 alphabet byte 0x{byte:02x} at index {index}"
)
}
Self::PaddingByte { index } => {
write!(
formatter,
"base64 alphabet contains padding byte at index {index}"
)
}
Self::DuplicateByte {
first,
second,
byte,
} => write!(
formatter,
"base64 alphabet byte 0x{byte:02x} is duplicated at indexes \
{first} and {second}"
),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct ValidatedAlphabet {
table: [u8; ALPHABET_LEN],
}
impl ValidatedAlphabet {
pub const fn new(table: [u8; ALPHABET_LEN]) -> Result<Self, ValidatedAlphabetError> {
match validate_table(&table) {
Ok(()) => Ok(Self { table }),
Err(error) => Err(error),
}
}
pub const fn try_from_slice(bytes: &[u8]) -> Result<Self, ValidatedAlphabetError> {
if bytes.len() != ALPHABET_LEN {
return Err(ValidatedAlphabetError::InvalidLength {
actual: bytes.len(),
});
}
let mut table = [0u8; ALPHABET_LEN];
let mut index = 0;
while index < ALPHABET_LEN {
table[index] = bytes[index];
index += 1;
}
Self::new(table)
}
#[must_use]
pub const fn as_array(&self) -> &[u8; ALPHABET_LEN] {
&self.table
}
#[allow(clippy::cast_lossless)]
#[must_use]
pub const fn encode_value(&self, value: u8) -> Option<u8> {
if value < 64 {
Some(self.table[value as usize])
} else {
None
}
}
#[must_use]
pub const fn decode_byte(&self, byte: u8) -> Option<u8> {
let mut index = 0;
let mut candidate = 0u8;
while index < ALPHABET_LEN {
if self.table[index] == byte {
return Some(candidate);
}
index += 1;
candidate += 1;
}
None
}
}
impl TryFrom<[u8; ALPHABET_LEN]> for ValidatedAlphabet {
type Error = ValidatedAlphabetError;
fn try_from(table: [u8; ALPHABET_LEN]) -> Result<Self, Self::Error> {
Self::new(table)
}
}
impl TryFrom<&[u8]> for ValidatedAlphabet {
type Error = ValidatedAlphabetError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::try_from_slice(bytes)
}
}
const fn validate_table(table: &[u8; ALPHABET_LEN]) -> Result<(), ValidatedAlphabetError> {
let mut index = 0;
while index < ALPHABET_LEN {
match validate_position(table, index) {
Ok(()) => {}
Err(error) => return Err(error),
}
index += 1;
}
Ok(())
}
const fn validate_position(
table: &[u8; ALPHABET_LEN],
index: usize,
) -> Result<(), ValidatedAlphabetError> {
let byte = table[index];
if byte < 0x21 || byte > 0x7e {
return Err(ValidatedAlphabetError::InvalidByte { index, byte });
}
if byte == b'=' {
return Err(ValidatedAlphabetError::PaddingByte { index });
}
let mut duplicate = index + 1;
while duplicate < ALPHABET_LEN {
if table[duplicate] == byte {
return Err(ValidatedAlphabetError::DuplicateByte {
first: index,
second: duplicate,
byte,
});
}
duplicate += 1;
}
Ok(())
}
#[cfg(kani)]
pub(crate) fn validate_position_for_proof(
table: &[u8; ALPHABET_LEN],
index: usize,
) -> Result<(), ValidatedAlphabetError> {
validate_position(table, index)
}