use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serializer};
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&encode(bytes))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
decode(encoded.as_bytes()).map_err(D::Error::custom)
}
fn encode(bytes: &[u8]) -> String {
let tail = match bytes.len() % 3 {
0 => 0,
1 => 2,
_ => 3,
};
let capacity = bytes.len() / 3 * 4 + tail;
let mut encoded = String::with_capacity(capacity);
for chunk in bytes.chunks(3) {
let first = chunk[0];
let second = chunk.get(1).copied().unwrap_or(0);
let third = chunk.get(2).copied().unwrap_or(0);
encoded.push(char::from(ALPHABET[usize::from(first >> 2)]));
encoded.push(char::from(ALPHABET[usize::from(((first & 0x03) << 4) | (second >> 4))]));
if chunk.len() > 1 {
encoded.push(char::from(ALPHABET[usize::from(((second & 0x0f) << 2) | (third >> 6))]));
}
if chunk.len() > 2 {
encoded.push(char::from(ALPHABET[usize::from(third & 0x3f)]));
}
}
encoded
}
fn decode(encoded: &[u8]) -> Result<Vec<u8>, &'static str> {
if encoded.len() % 4 == 1 {
return Err("invalid base64 length");
}
let capacity = encoded.len().checked_mul(3).ok_or("base64 length overflow")? / 4;
let mut decoded = Vec::with_capacity(capacity);
let (chunks, remainder) = encoded.as_chunks::<4>();
for chunk in chunks {
let first = value(chunk[0])?;
let second = value(chunk[1])?;
let third = value(chunk[2])?;
let fourth = value(chunk[3])?;
decoded.push((first << 2) | (second >> 4));
decoded.push((second << 4) | (third >> 2));
decoded.push((third << 6) | fourth);
}
match remainder {
[] => {}
[first, second] => {
let first = value(*first)?;
let second = value(*second)?;
if second & 0x0f != 0 {
return Err("non-canonical base64 tail");
}
decoded.push((first << 2) | (second >> 4));
}
[first, second, third] => {
let first = value(*first)?;
let second = value(*second)?;
let third = value(*third)?;
if third & 0x03 != 0 {
return Err("non-canonical base64 tail");
}
decoded.push((first << 2) | (second >> 4));
decoded.push((second << 4) | (third >> 2));
}
_ => return Err("invalid base64 tail"),
}
Ok(decoded)
}
fn value(encoded: u8) -> Result<u8, &'static str> {
match encoded {
b'A'..=b'Z' => Ok(encoded - b'A'),
b'a'..=b'z' => Ok(encoded - b'a' + 26),
b'0'..=b'9' => Ok(encoded - b'0' + 52),
b'-' => Ok(62),
b'_' => Ok(63),
_ => Err("invalid base64 character"),
}
}