mod error;
pub use error::DecodeError;
static ALPHABET: [char; 32] = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q',
'r', 's', 't', 'v', 'w', 'x', 'y', 'z',
];
const LOOKUP_TABLE_LENGTH: u8 = 38;
static REVERSE_ALPHABET: [u8; LOOKUP_TABLE_LENGTH as usize] = [
24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 255, 10, 11, 12, 13, 14,
15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23
];
pub const SHORT_LENGTH: usize = 22;
#[inline]
fn lookup(value: u8) -> u8 {
let value = value % LOOKUP_TABLE_LENGTH;
REVERSE_ALPHABET[value as usize]
}
pub fn base32_decode(input: &str) -> Result<[u8; 16], DecodeError> {
if input.len() != 27 {
return Err(DecodeError::InvalidLength);
}
let input: [u8; 27] = input.as_bytes().try_into().unwrap();
if !input[22] == b'_' {
return Err(DecodeError::NoSeparator);
}
let mut intermediate = [0u8; 26];
for i in 0..22 {
intermediate[i] = lookup(input[i]);
}
for i in 23..27 {
intermediate[i - 1] = lookup(input[i]);
}
let mut combined = 0u8;
for &c in &intermediate {
combined |= c;
}
let has_invalid = combined == 255;
if has_invalid {
let mut idx255 = intermediate.iter().position(|&c| c == 255).unwrap();
if idx255 >= 22 {
idx255 += 1;
}
let c = input[idx255];
return Err(DecodeError::InvalidCharacter(c as char));
}
let mut result = [0u8; 16];
for i in 0..3 {
let j = i * 8;
let k = i * 5;
let d0 = intermediate[j];
let d1 = intermediate[j + 1];
let d2 = intermediate[j + 2];
let d3 = intermediate[j + 3];
let d4 = intermediate[j + 4];
let d5 = intermediate[j + 5];
let d6 = intermediate[j + 6];
let d7 = intermediate[j + 7];
let d8 = intermediate[j + 8];
result[k] = d0 << 5 | d1;
result[k + 1] = d2 << 3 | (d3 >> 2);
result[k + 2] = d3 << 6 | (d4 << 1) | (d5 >> 4);
result[k + 3] = d5 << 4 | (d6 >> 1);
result[k + 4] = d6 << 7 | (d7 << 2) | (d8 >> 3);
}
result[15] = intermediate[24] << 5 | intermediate[25];
Ok(result)
}
#[inline]
fn alphabet(i: u8) -> char {
ALPHABET[(i & 0x1F) as usize]
}
pub fn base32_encode(data: [u8; 16]) -> String {
let mut encoded = String::with_capacity(27);
encoded.push(alphabet(data[0] >> 5));
encoded.push(alphabet(data[0]));
encoded.push(alphabet(data[1] >> 3));
encoded.push(alphabet(data[1] << 2 | data[2] >> 6));
encoded.push(alphabet(data[2] >> 1));
encoded.push(alphabet(data[2] << 4 | data[3] >> 4));
encoded.push(alphabet(data[3] << 1 | data[4] >> 7));
encoded.push(alphabet(data[4] >> 2));
encoded.push(alphabet(data[4] << 3 | data[5] >> 5));
encoded.push(alphabet(data[5]));
encoded.push(alphabet(data[6] >> 3));
encoded.push(alphabet(data[6] << 2 | data[7] >> 6));
encoded.push(alphabet(data[7] >> 1));
encoded.push(alphabet(data[7] << 4 | data[8] >> 4));
encoded.push(alphabet(data[8] << 1 | data[9] >> 7));
encoded.push(alphabet(data[9] >> 2));
encoded.push(alphabet(data[9] << 3 | data[10] >> 5));
encoded.push(alphabet(data[10]));
encoded.push(alphabet(data[11] >> 3));
encoded.push(alphabet(data[11] << 2 | data[12] >> 6));
encoded.push(alphabet(data[12] >> 1));
encoded.push(alphabet(data[12] << 4 | data[13] >> 4));
encoded.push('_');
encoded.push(alphabet(data[13] << 1 | data[14] >> 7));
encoded.push(alphabet(data[14] >> 2));
encoded.push(alphabet(data[14] << 3 | data[15] >> 5));
encoded.push(alphabet(data[15]));
encoded
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_rand() {
use rand::RngCore;
let mut rng = rand::thread_rng();
for _ in 0..3000000 {
let mut s = [0u8; 16];
rng.fill_bytes(&mut s);
let mut t = [0u8; 16];
rng.fill_bytes(&mut t);
let s_enc = base32_encode(s);
let dec = base32_decode(&s_enc).unwrap();
assert_eq!(dec, s);
let t_enc = base32_encode(t);
let dec = base32_decode(&t_enc).unwrap();
assert_eq!(dec, t);
if s < t {
assert_eq!(s_enc < t_enc, true);
} else {
assert_eq!(s_enc > t_enc, true);
}
}
}
}