use alloc::string::String;
use alloc::vec::Vec;
const ALPHABET: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
pub fn decode(s: &str) -> Option<Vec<u8>> {
let mut body = Vec::with_capacity(s.len());
for &b in s.as_bytes() {
if b == b'=' {
continue;
}
let v = match b {
b'a'..=b'z' => b - b'a',
b'A'..=b'Z' => b - b'A',
b'2'..=b'7' => b - b'2' + 26,
_ => return None,
};
body.push(v);
}
if body.is_empty() {
return Some(Vec::new());
}
let mut out = Vec::with_capacity(body.len() * 5 / 8);
let mut acc: u32 = 0;
let mut bits: u32 = 0;
for &c in &body {
acc = (acc << 5) | c as u32;
bits += 5;
if bits >= 8 {
bits -= 8;
out.push((acc >> bits) as u8);
acc &= (1u32 << bits).wrapping_sub(1);
}
}
if acc != 0 {
return None;
}
Some(out)
}
pub fn encode(data: &[u8]) -> String {
let mut out = String::with_capacity((data.len() * 8).div_ceil(5));
let mut acc: u32 = 0;
let mut bits: u32 = 0;
for &b in data {
acc = (acc << 8) | b as u32;
bits += 8;
while bits >= 5 {
bits -= 5;
out.push(ALPHABET[((acc >> bits) & 0x1f) as usize] as char);
}
acc &= (1u32 << bits).wrapping_sub(1);
}
if bits > 0 {
out.push(ALPHABET[((acc << (5 - bits)) & 0x1f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn rfc4648_vectors() {
let cases: &[(&str, &str)] = &[
("", ""),
("f", "my"),
("fo", "mzxq"),
("foo", "mzxw6"),
("foob", "mzxw6yq"),
("fooba", "mzxw6ytb"),
("foobar", "mzxw6ytboi"),
];
for (plain, enc) in cases {
assert_eq!(encode(plain.as_bytes()), enc.to_string(), "enc {plain:?}");
assert_eq!(decode(enc).unwrap(), plain.as_bytes(), "dec {enc:?}");
}
}
#[test]
fn roundtrip() {
for len in 0..=64 {
let data: Vec<u8> = (0..len).map(|i| (i * 29 + 3) as u8).collect();
let enc = encode(&data);
assert_eq!(decode(&enc).unwrap(), data, "len={len}");
assert_eq!(
decode(&enc.to_uppercase()).unwrap(),
data,
"upper len={len}"
);
}
}
#[test]
fn rejects_invalid() {
assert!(decode("mzxw6ytb1").is_none()); assert!(decode("!!!").is_none());
assert!(decode("mzxw6ytb==").is_some());
}
}