const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = chunk.get(1).copied().unwrap_or(0) as u32;
let b2 = chunk.get(2).copied().unwrap_or(0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[(n >> 18) as usize & 63] as char);
out.push(ALPHABET[(n >> 12) as usize & 63] as char);
out.push(if chunk.len() > 1 { ALPHABET[(n >> 6) as usize & 63] as char } else { '=' });
out.push(if chunk.len() > 2 { ALPHABET[n as usize & 63] as char } else { '=' });
}
out
}
fn value_of(c: u8) -> Option<u32> {
match c {
b'A'..=b'Z' => Some((c - b'A') as u32),
b'a'..=b'z' => Some((c - b'a') as u32 + 26),
b'0'..=b'9' => Some((c - b'0') as u32 + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
pub fn decode(s: &str) -> Option<Vec<u8>> {
let b = s.as_bytes();
if !b.len().is_multiple_of(4) {
return None;
}
let mut out = Vec::with_capacity(b.len() / 4 * 3);
for (i, chunk) in b.chunks(4).enumerate() {
let last = i == b.len() / 4 - 1;
let pads = chunk.iter().filter(|&&c| c == b'=').count();
if pads > 2 || (!last && pads > 0) {
return None;
}
if pads >= 1 && chunk[3] != b'=' {
return None;
}
if pads == 2 && chunk[2] != b'=' {
return None;
}
let v0 = value_of(chunk[0])?;
let v1 = value_of(chunk[1])?;
let v2 = if pads == 2 { 0 } else { value_of(chunk[2])? };
let v3 = if pads >= 1 { 0 } else { value_of(chunk[3])? };
let n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3;
out.push((n >> 16) as u8);
if pads < 2 {
out.push((n >> 8) as u8);
}
if pads < 1 {
out.push(n as u8);
}
}
Some(out)
}
#[cfg(all(test, feature = "json"))]
mod tests {
use super::*;
use base64::Engine;
const ORACLE: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD;
#[test]
fn encode_parity_with_the_crate() {
let cases: Vec<Vec<u8>> = vec![
vec![],
vec![0],
vec![255],
vec![1, 2],
vec![1, 2, 3],
vec![1, 2, 3, 4],
(0u8..=255).collect(),
vec![7u8; 33],
vec![42u8; 64],
];
for c in cases {
assert_eq!(encode(&c), ORACLE.encode(&c), "case {c:?}");
}
}
#[test]
fn decode_roundtrip_and_parity() {
let data: Vec<u8> = (0u8..=255).collect();
let enc = encode(&data);
assert_eq!(decode(&enc).unwrap(), data);
assert_eq!(ORACLE.decode(&enc).unwrap(), data);
}
#[test]
fn decode_rejects_garbage() {
assert!(decode("abc").is_none()); assert!(decode("ab=c").is_none()); assert!(decode("a bc").is_none()); assert!(decode("ab-_").is_none()); }
}