pub fn decode(input: &str) -> Result<Vec<u8>, crate::BoxError> {
const LOOKUP: [u8; 256] = const {
let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut lut = [255u8; 256];
let mut i = 0;
while i < 64 {
lut[table[i] as usize] = i as u8;
i += 1;
}
lut
};
let input = input.as_bytes();
let mut out = Vec::with_capacity(input.len() * 3 / 4);
let mut buf: u32 = 0;
let mut bits: u32 = 0;
for &b in input {
if matches!(b, b'=' | b'\n' | b'\r' | b' ' | b'\t') {
continue;
}
let val = LOOKUP[b as usize];
if val == 255 {
return Err(format!("Invalid base64 character: {}", b as char).into());
}
buf = (buf << 6) | u32::from(val);
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((buf >> bits) as u8);
buf &= (1 << bits) - 1;
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::decode;
#[test]
fn decodes_ascii() {
assert_eq!(decode("aGVsbG8=").unwrap(), b"hello");
assert_eq!(decode("Zm9vYmFy").unwrap(), b"foobar");
}
#[test]
fn decodes_empty() {
assert_eq!(decode("").unwrap(), Vec::<u8>::new());
}
#[test]
fn ignores_whitespace_and_padding() {
assert_eq!(decode("aGVs\nbG8=").unwrap(), b"hello");
assert_eq!(decode(" aGVsbG8= ").unwrap(), b"hello");
assert_eq!(decode("aGVs\tbG8=\r\n").unwrap(), b"hello");
}
#[test]
fn roundtrip_all_byte_values() {
let bytes: Vec<u8> = (0..=255).collect();
let encoded = reference_encode(&bytes);
assert_eq!(decode(&encoded).unwrap(), bytes);
}
#[test]
fn rejects_invalid_char() {
assert!(decode("aGVsbG8!").is_err());
}
fn reference_encode(data: &[u8]) -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in data.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
out.push(ALPHABET[((n >> 18) & 63) as usize] as char);
out.push(ALPHABET[((n >> 12) & 63) as usize] as char);
out.push(if chunk.len() > 1 { ALPHABET[((n >> 6) & 63) as usize] as char } else { '=' });
out.push(if chunk.len() > 2 { ALPHABET[(n & 63) as usize] as char } else { '=' });
}
out
}
}