const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub(crate) fn base64_encode(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(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(TABLE[((n >> 18) & 63) as usize] as char);
out.push(TABLE[((n >> 12) & 63) as usize] as char);
out.push(if chunk.len() > 1 {
TABLE[((n >> 6) & 63) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
TABLE[(n & 63) as usize] as char
} else {
'='
});
}
out
}
pub(crate) fn base64_decode(s: &str) -> Option<Vec<u8>> {
let mut bits: u32 = 0;
let mut nbits = 0u32;
let mut out = Vec::with_capacity(s.len() / 4 * 3 + 3);
for &b in s.as_bytes() {
let val = match b {
b'A'..=b'Z' => b - b'A',
b'a'..=b'z' => b - b'a' + 26,
b'0'..=b'9' => b - b'0' + 52,
b'+' => 62,
b'/' => 63,
b'=' => break,
b'\r' | b'\n' | b' ' | b'\t' => continue,
_ => return None,
} as u32;
bits = (bits << 6) | val;
nbits += 6;
if nbits >= 8 {
nbits -= 8;
out.push((bits >> nbits) as u8);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_known_vectors() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
}
#[test]
fn decode_known_vectors() {
assert_eq!(base64_decode("").unwrap(), b"");
assert_eq!(base64_decode("Zg==").unwrap(), b"f");
assert_eq!(base64_decode("Zm8=").unwrap(), b"fo");
assert_eq!(base64_decode("Zm9v").unwrap(), b"foo");
assert_eq!(base64_decode("Zm9vYg==").unwrap(), b"foob");
assert_eq!(base64_decode("aGVsbG8=").unwrap(), b"hello");
}
#[test]
fn decode_roundtrip_and_lenient() {
let data: Vec<u8> = (0u8..=255).collect();
let enc = base64_encode(&data);
assert_eq!(base64_decode(&enc).unwrap(), data);
assert_eq!(base64_decode("aGVs\nbG8").unwrap(), b"hello");
}
#[test]
fn decode_rejects_invalid() {
assert!(base64_decode("!!!!").is_none());
}
}