const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn encode(data: &[u8]) -> String {
let mut out = String::with_capacity((data.len() + 2) / 3 * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[((n >> 18) & 0x3f) as usize] as char);
out.push(ALPHABET[((n >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(ALPHABET[((n >> 6) & 0x3f) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(ALPHABET[(n & 0x3f) as usize] as char);
} else {
out.push('=');
}
}
out
}
fn val(c: u8) -> Option<u32> {
match c {
b'A'..=b'Z' => Some((c - b'A') as u32),
b'a'..=b'z' => Some((c - b'a' + 26) as u32),
b'0'..=b'9' => Some((c - b'0' + 52) as u32),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
pub fn decode(s: &str) -> Option<Vec<u8>> {
let mut buf = 0u32;
let mut bits = 0u32;
let mut out = Vec::with_capacity(s.len() / 4 * 3);
for &c in s.as_bytes() {
match c {
b'=' => break,
b'\n' | b'\r' | b' ' | b'\t' => continue,
_ => {
let v = val(c)?;
buf = (buf << 6) | v;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((buf >> bits) as u8);
}
}
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
for input in [
&b""[..],
b"f",
b"fo",
b"foo",
b"foob",
b"fooba",
b"foobar",
&[0u8, 1, 2, 253, 254, 255],
] {
let enc = encode(input);
assert_eq!(decode(&enc).unwrap(), input);
}
}
#[test]
fn known_vectors() {
assert_eq!(encode(b"foobar"), "Zm9vYmFy");
assert_eq!(decode("Zm9vYmFy").unwrap(), b"foobar");
assert_eq!(
encode(b"any carnal pleasure."),
"YW55IGNhcm5hbCBwbGVhc3VyZS4="
);
}
}