pub fn letter_from_nickel(value: u8) -> u8
{
const ALPHABET: [u8; 32] = [
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b',
b'c', b'd', b'e', b'f', b'g', b'h', b'j', b'k', b'm', b'n', b'p', b'q',
b'r', b's', b't', b'v', b'w', b'x', b'y', b'z',
];
debug_assert!(value <= 31);
ALPHABET[value as usize]
}
pub fn nickel_from_letter(x: u8) -> Result<u8, DecodeError>
{
match x
{
b'0'..=b'9' => Ok(x - b'0'),
b'a'..=b'h' => Ok(x - b'a' + 10),
b'j'..=b'k' => Ok(x - b'j' + 18),
b'm'..=b'n' => Ok(x - b'm' + 20),
b'p'..=b't' => Ok(x - b'p' + 22),
b'v'..=b'z' => Ok(x - b'v' + 27),
b'A'..=b'H' => Ok(x - b'A' + 10),
b'J'..=b'K' => Ok(x - b'J' + 18),
b'M'..=b'N' => Ok(x - b'M' + 20),
b'P'..=b'T' => Ok(x - b'P' + 22),
b'V'..=b'Z' => Ok(x - b'V' + 27),
b'i' | b'I' | b'l' | b'L' => Ok(1),
b'o' | b'O' => Ok(0),
b'u' | b'U' => Ok(27),
_ => Err(DecodeError::InvalidLetter { letter: x }),
}
}
pub fn encode(data: &[u8]) -> String
{
let datalength = data.len();
let wordlength = (datalength * 8 + 4) / 5;
let mut word = vec![0u8; wordlength];
let mut buffer: u16 = 0;
let mut nbits = (5 - (datalength * 8) % 5) % 5;
let mut datapos = 0;
for character in word.iter_mut()
{
if nbits < 5
{
buffer |= (data[datapos] as u16) << (8 - nbits);
datapos += 1;
nbits += 8;
}
let nickel = (buffer >> 11) as u8;
buffer <<= 5;
nbits -= 5;
*character = letter_from_nickel(nickel);
}
debug_assert!(datapos == datalength);
String::from_utf8(word).unwrap()
}
pub fn decode(word: &str) -> Result<Vec<u8>, DecodeError>
{
if !word.is_ascii()
{
return Err(DecodeError::NonAscii {
source: word.to_string(),
});
}
let wordlength = word.len();
let datalength = (wordlength * 5) / 8;
let mut data = vec![0u8; datalength];
let mut discarded = (wordlength * 5) % 8;
let mut nbits: i8 = 0;
let mut buffer: u16 = 0;
let mut datapos = 0;
for x in word.bytes()
{
let value: u8 = nickel_from_letter(x)?;
debug_assert!(value <= 31);
if discarded >= 5
{
if value > 0
{
return Err(DecodeError::NonZeroLeadingBits {
source: word.to_string(),
});
}
discarded -= 5;
continue;
}
else if discarded > 0
{
if value >= 1 << (5 - discarded)
{
return Err(DecodeError::NonZeroLeadingBits {
source: word.to_string(),
});
}
nbits -= discarded as i8;
discarded = 0;
}
buffer |= (value as u16) << (11 - nbits);
nbits += 5;
if nbits >= 8
{
data[datapos] = (buffer >> 8) as u8;
datapos += 1;
buffer <<= 8;
nbits -= 8;
}
}
debug_assert!(datapos == datalength);
Ok(data)
}
#[derive(Debug)]
pub enum DecodeError
{
InvalidLetter
{
letter: u8
},
NonZeroLeadingBits
{
source: String
},
WordTooLong
{
source: String,
max_length_in_bits: usize,
},
WordTooShort
{
source: String,
min_length_in_bits: usize,
},
NonAscii
{
source: String
},
}
impl std::error::Error for DecodeError {}
impl std::fmt::Display for DecodeError
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
{
match self
{
DecodeError::InvalidLetter { letter } =>
{
write!(f, "invalid non-Base32 character '{}'", letter)
}
DecodeError::NonZeroLeadingBits { source } =>
{
write!(f, "non-zero leading bits in '{}'", source)
}
DecodeError::WordTooLong {
source,
max_length_in_bits,
} => write!(
f,
"too many characters in '{}' for {} bits of data",
source, max_length_in_bits
),
DecodeError::WordTooShort {
source,
min_length_in_bits,
} => write!(
f,
"not enough characters in '{}' for {} bits of data",
source, min_length_in_bits
),
DecodeError::NonAscii { source } =>
{
write!(f, "non-ASCII characters in '{}'", source)
}
}
}
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_inverse() -> Result<(), DecodeError>
{
for nickel in 0..=31
{
assert_eq!(nickel_from_letter(letter_from_nickel(nickel))?, nickel);
}
Ok(())
}
#[test]
fn test_case_insensitivity() -> Result<(), DecodeError>
{
for letter in b'a'..=b'z'
{
let upper = letter.to_ascii_uppercase();
assert_eq!(nickel_from_letter(upper)?, nickel_from_letter(letter)?);
}
Ok(())
}
#[test]
fn test_confusion() -> Result<(), DecodeError>
{
assert_eq!(nickel_from_letter(b'i')?, nickel_from_letter(b'1')?);
assert_eq!(nickel_from_letter(b'l')?, nickel_from_letter(b'1')?);
assert_eq!(nickel_from_letter(b'o')?, nickel_from_letter(b'0')?);
assert_eq!(nickel_from_letter(b'u')?, nickel_from_letter(b'v')?);
Ok(())
}
#[test]
fn test_empty() -> Result<(), DecodeError>
{
let encoded = encode(&[]);
assert_eq!(encoded.len(), 0);
let decoded = decode(&encoded)?;
assert_eq!(decoded.len(), 0);
Ok(())
}
#[test]
fn test_len() -> Result<(), DecodeError>
{
for len in 1..=20
{
let mut data = vec![0u8; len];
for x in 0..=255
{
data[0] = x;
let encoded = encode(&data);
let decoded = decode(&encoded)?;
assert_eq!(decoded, data, "(encoded = {})", encoded);
assert_eq!(encode(&decoded), encoded,);
}
}
Ok(())
}
#[test]
fn test_inverse_len() -> Result<(), DecodeError>
{
let len = 256;
let text = "0".repeat(len);
for n in 0..=len
{
decode(&text[0..n])?;
}
Ok(())
}
#[test]
fn test_garbage()
{
assert!(decode("abc ").is_err());
assert!(decode("abc\0").is_err());
}
#[test]
fn test_leading_zeroes() -> Result<(), DecodeError>
{
{
let decoded = decode("0")?;
assert_eq!(decoded.len(), 0);
}
{
let decoded = decode("7z")?;
assert_eq!(decoded, [255u8]);
}
{
let decoded = decode("07z")?;
assert_eq!(decoded, [255u8]);
}
Ok(())
}
#[test]
fn test_nonzero_leading_bits()
{
assert!(decode("a").is_err());
assert!(decode("zz").is_err());
}
}