const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn encode(input: &[u8]) -> String {
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.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(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
}
pub fn decode(input: &str) -> Option<Vec<u8>> {
let s = input.trim().as_bytes();
if !s.len().is_multiple_of(4) {
return None;
}
let val = |c: u8| -> Option<u32> {
Some(match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
_ => return None,
} as u32)
};
let mut out = Vec::with_capacity(s.len() / 4 * 3);
let chunks = s.len() / 4;
for (i, c) in s.as_chunks::<4>().0.iter().enumerate() {
let last = i + 1 == chunks;
let pad = if last {
c.iter().rev().take_while(|&&b| b == b'=').count()
} else {
0
};
if pad > 2 || (!last && c.contains(&b'=')) {
return None;
}
let mut n = 0u32;
for (j, &b) in c.iter().enumerate() {
let v = if j >= 4 - pad { 0 } else { val(b)? };
n = n << 6 | v;
}
out.push((n >> 16) as u8);
if pad < 2 {
out.push((n >> 8) as u8);
}
if pad < 1 {
out.push(n as u8);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc4648_vectors() {
for (raw, enc) in [
("", ""),
("f", "Zg=="),
("fo", "Zm8="),
("foo", "Zm9v"),
("foob", "Zm9vYg=="),
("fooba", "Zm9vYmE="),
("foobar", "Zm9vYmFy"),
] {
assert_eq!(encode(raw.as_bytes()), enc);
assert_eq!(decode(enc).unwrap(), raw.as_bytes());
}
assert!(decode("Zm9").is_none());
assert!(decode("Z=9v").is_none());
assert!(decode("Zm9v!A==").is_none());
}
#[test]
fn ocpp_basic_auth_example() {
let mut creds = b"AL1000:".to_vec();
creds.extend_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7]);
creds.extend_from_slice(&[0xFF; 12]);
assert_eq!(encode(&creds), "QUwxMDAwOgABAgMEBQYH////////////////");
}
}