use crate::error::PolicyError;
pub const DEFAULT_ALPHABET: &[u8] = b"ABCDEFGHJKMNPQRSTUVWXYZ23456789";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Alphabet {
symbols: Vec<u8>,
}
impl Alphabet {
pub fn new(symbols: &[u8]) -> Result<Self, PolicyError> {
if symbols.len() < 2 {
return Err(PolicyError::AlphabetTooSmall);
}
if !symbols.iter().all(u8::is_ascii) {
return Err(PolicyError::AlphabetNotAscii);
}
for (i, &b) in symbols.iter().enumerate() {
if symbols[i + 1..].contains(&b) {
return Err(PolicyError::AlphabetNotUnique);
}
}
Ok(Self {
symbols: symbols.to_vec(),
})
}
#[must_use]
pub fn unambiguous() -> Self {
Self {
symbols: DEFAULT_ALPHABET.to_vec(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.symbols.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
#[must_use]
pub fn symbols(&self) -> &[u8] {
&self.symbols
}
#[must_use]
pub fn contains(&self, byte: u8) -> bool {
self.symbols.contains(&byte)
}
#[must_use]
pub fn unbiased_ceiling(&self) -> usize {
let n = self.symbols.len();
256 - (256 % n)
}
#[must_use]
pub(crate) fn symbol_for_byte(&self, byte: u8) -> u8 {
self.symbols[byte as usize % self.symbols.len()]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_excludes_ambiguous_characters() {
let a = Alphabet::unambiguous();
for &c in b"01OIL" {
assert!(
!a.contains(c),
"default alphabet contains ambiguous '{}'",
c as char
);
}
assert_eq!(a.len(), 31);
}
#[test]
fn ceiling_is_248_for_default() {
assert_eq!(Alphabet::unambiguous().unbiased_ceiling(), 248);
}
#[test]
fn all_accepted_bytes_map_into_alphabet() {
let a = Alphabet::unambiguous();
for b in 0..a.unbiased_ceiling() {
let sym = a.symbol_for_byte(b as u8);
assert!(a.contains(sym));
}
}
#[test]
fn rejects_small_duplicate_and_non_ascii() {
assert_eq!(Alphabet::new(b"A"), Err(PolicyError::AlphabetTooSmall));
assert_eq!(Alphabet::new(b"AAB"), Err(PolicyError::AlphabetNotUnique));
assert_eq!(
Alphabet::new(&[b'A', 0x80]),
Err(PolicyError::AlphabetNotAscii)
);
assert!(Alphabet::new(b"AB").is_ok());
}
}