pub(super) fn encode(data: &[u8]) -> String {
const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = (b[0] as u32) << 16 | (b[1] as u32) << 8 | b[2] as u32;
out.push(A[(n >> 18 & 63) as usize] as char);
out.push(A[(n >> 12 & 63) as usize] as char);
out.push(if chunk.len() > 1 {
A[(n >> 6 & 63) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
A[(n & 63) as usize] as char
} else {
'='
});
}
out
}
pub(super) fn decode(s: &str) -> Option<Vec<u8>> {
fn val(c: u8) -> Option<u8> {
match c {
b'A'..=b'Z' => Some(c - b'A'),
b'a'..=b'z' => Some(c - b'a' + 26),
b'0'..=b'9' => Some(c - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
let mut bits: u32 = 0;
let mut nbits = 0;
let mut out = Vec::with_capacity(s.len() * 3 / 4);
for &c in s.as_bytes() {
if c == b'=' || c.is_ascii_whitespace() {
continue;
}
bits = (bits << 6) | val(c)? as u32;
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!(encode(b"user:pass"), "dXNlcjpwYXNz");
assert_eq!(
encode(b"Aladdin:open sesame"),
"QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
}
#[test]
fn decode_round_trips_and_tolerates_padding() {
assert_eq!(decode("dXNlcjpwYXNz").as_deref(), Some(&b"user:pass"[..]));
assert_eq!(decode("Zm9v").as_deref(), Some(&b"foo"[..]));
assert_eq!(decode("Zm 9v\n").as_deref(), Some(&b"foo"[..]));
assert!(decode("****").is_none());
}
}