use super::limits::MAX_BASE32_INPUT_LEN;
use zeroize::Zeroize;
pub const BASE32_DECODE_TABLE: [u8; 256] = build_base32_table();
pub const CROCKFORD_DECODE_TABLE: [u8; 256] = build_crockford_table();
const fn build_base32_table() -> [u8; 256] {
let mut table = [0xFF; 256];
let mut i = 0;
while i < 26 {
table[(b'A' + i) as usize] = i;
table[(b'a' + i) as usize] = i;
i += 1;
}
let mut j = 0;
while j < 6 {
table[(b'2' + j) as usize] = 26 + j;
j += 1;
}
table
}
const fn build_crockford_table() -> [u8; 256] {
let mut table = [0xFF; 256];
let mut i = 0;
while i < 10 {
table[(b'0' + i) as usize] = i;
i += 1;
}
let mut c = 0;
while c < 8 {
table[(b'A' + c) as usize] = 10 + c;
table[(b'a' + c) as usize] = 10 + c;
c += 1;
}
table[b'J' as usize] = 18;
table[b'j' as usize] = 18;
table[b'K' as usize] = 19;
table[b'k' as usize] = 19;
table[b'M' as usize] = 20;
table[b'm' as usize] = 20;
table[b'N' as usize] = 21;
table[b'n' as usize] = 21;
let mut p = 0;
while p < 5 {
table[(b'P' + p) as usize] = 22 + p;
table[(b'p' + p) as usize] = 22 + p;
p += 1;
}
let mut v = 0;
while v < 5 {
table[(b'V' + v) as usize] = 27 + v;
table[(b'v' + v) as usize] = 27 + v;
v += 1;
}
table[b'O' as usize] = 0;
table[b'o' as usize] = 0;
table[b'I' as usize] = 1;
table[b'i' as usize] = 1;
table[b'L' as usize] = 1;
table[b'l' as usize] = 1;
table
}
#[allow(clippy::result_unit_err)]
pub fn base32_decode(input: &str) -> Result<Vec<u8>, ()> {
if input.len() > MAX_BASE32_INPUT_LEN {
return Err(());
}
if input.is_empty() {
return Ok(Vec::new());
}
let bytes = input.as_bytes();
let (data, padding_len) = match bytes.iter().position(|&b| b == b'=') {
Some(pos) => {
if !input.len().is_multiple_of(8) {
return Err(());
}
let pad_count = input.len() - pos;
if !matches!(pad_count, 1 | 3 | 4 | 6) {
return Err(());
}
if !bytes[pos..].iter().all(|&b| b == b'=') {
return Err(());
}
(&bytes[..pos], pad_count)
}
None => (bytes, 0),
};
let rem_len = data.len() % 8;
if !matches!(rem_len, 0 | 2 | 4 | 5 | 7) {
return Err(());
}
if padding_len > 0 {
let expected_rem = match padding_len {
1 => 7,
3 => 5,
4 => 4,
6 => 2,
_ => return Err(()),
};
if rem_len != expected_rem {
return Err(());
}
}
decode_base32_slice(data, &BASE32_DECODE_TABLE)
}
#[allow(clippy::result_unit_err)]
pub fn crockford_base32_decode(input: &str) -> Result<Vec<u8>, ()> {
if input.len() > MAX_BASE32_INPUT_LEN {
return Err(());
}
if !input.as_bytes().contains(&b'-') {
return decode_base32_slice(input.as_bytes(), &CROCKFORD_DECODE_TABLE);
}
if input.len() <= 256 {
let mut buf = [0u8; 256];
let mut len = 0usize;
for &b in input.as_bytes() {
if b != b'-' {
buf[len] = b;
len += 1;
}
}
let res = decode_base32_slice(&buf[..len], &CROCKFORD_DECODE_TABLE);
buf.zeroize();
res
} else {
let mut cleaned = Vec::with_capacity(input.len());
for &b in input.as_bytes() {
if b != b'-' {
cleaned.push(b);
}
}
let res = decode_base32_slice(&cleaned, &CROCKFORD_DECODE_TABLE);
cleaned.zeroize();
res
}
}
fn decode_base32_slice(data: &[u8], table: &[u8; 256]) -> Result<Vec<u8>, ()> {
let rem_len = data.len() % 8;
if !matches!(rem_len, 0 | 2 | 4 | 5 | 7) {
return Err(());
}
let full_blocks = data.len() / 8;
let tail_bytes = match rem_len {
0 => 0,
2 => 1,
4 => 2,
5 => 3,
7 => 4,
_ => return Err(()),
};
let total_len = full_blocks * 5 + tail_bytes;
let mut out = Vec::with_capacity(total_len);
for chunk in data.chunks_exact(8) {
let v0 = table[chunk[0] as usize];
let v1 = table[chunk[1] as usize];
let v2 = table[chunk[2] as usize];
let v3 = table[chunk[3] as usize];
let v4 = table[chunk[4] as usize];
let v5 = table[chunk[5] as usize];
let v6 = table[chunk[6] as usize];
let v7 = table[chunk[7] as usize];
if (v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7) & 0xE0 != 0 {
out.zeroize();
return Err(());
}
let combined = ((v0 as u64) << 35)
| ((v1 as u64) << 30)
| ((v2 as u64) << 25)
| ((v3 as u64) << 20)
| ((v4 as u64) << 15)
| ((v5 as u64) << 10)
| ((v6 as u64) << 5)
| (v7 as u64);
out.push((combined >> 32) as u8);
out.push((combined >> 24) as u8);
out.push((combined >> 16) as u8);
out.push((combined >> 8) as u8);
out.push(combined as u8);
}
let rem = data.chunks_exact(8).remainder();
match rem.len() {
0 => Ok(out),
2 => {
let v0 = table[rem[0] as usize];
let v1 = table[rem[1] as usize];
if (v0 | v1) & 0xE0 != 0 {
out.zeroize();
return Err(());
}
let combined = ((v0 as u16) << 5) | (v1 as u16);
if combined & 0x03 != 0 {
out.zeroize();
return Err(());
}
out.push((combined >> 2) as u8);
Ok(out)
}
4 => {
let v0 = table[rem[0] as usize];
let v1 = table[rem[1] as usize];
let v2 = table[rem[2] as usize];
let v3 = table[rem[3] as usize];
if (v0 | v1 | v2 | v3) & 0xE0 != 0 {
out.zeroize();
return Err(());
}
let combined =
((v0 as u32) << 15) | ((v1 as u32) << 10) | ((v2 as u32) << 5) | (v3 as u32);
if combined & 0x0F != 0 {
out.zeroize();
return Err(());
}
out.push((combined >> 12) as u8);
out.push((combined >> 4) as u8);
Ok(out)
}
5 => {
let v0 = table[rem[0] as usize];
let v1 = table[rem[1] as usize];
let v2 = table[rem[2] as usize];
let v3 = table[rem[3] as usize];
let v4 = table[rem[4] as usize];
if (v0 | v1 | v2 | v3 | v4) & 0xE0 != 0 {
out.zeroize();
return Err(());
}
let combined = ((v0 as u32) << 20)
| ((v1 as u32) << 15)
| ((v2 as u32) << 10)
| ((v3 as u32) << 5)
| (v4 as u32);
if combined & 0x01 != 0 {
out.zeroize();
return Err(());
}
out.push((combined >> 17) as u8);
out.push((combined >> 9) as u8);
out.push((combined >> 1) as u8);
Ok(out)
}
7 => {
let v0 = table[rem[0] as usize];
let v1 = table[rem[1] as usize];
let v2 = table[rem[2] as usize];
let v3 = table[rem[3] as usize];
let v4 = table[rem[4] as usize];
let v5 = table[rem[5] as usize];
let v6 = table[rem[6] as usize];
if (v0 | v1 | v2 | v3 | v4 | v5 | v6) & 0xE0 != 0 {
out.zeroize();
return Err(());
}
let combined = ((v0 as u64) << 30)
| ((v1 as u64) << 25)
| ((v2 as u64) << 20)
| ((v3 as u64) << 15)
| ((v4 as u64) << 10)
| ((v5 as u64) << 5)
| (v6 as u64);
if combined & 0x07 != 0 {
out.zeroize();
return Err(());
}
out.push((combined >> 27) as u8);
out.push((combined >> 19) as u8);
out.push((combined >> 11) as u8);
out.push((combined >> 3) as u8);
Ok(out)
}
_ => {
out.zeroize();
Err(())
}
}
}