1const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7
8#[must_use]
10pub fn encode(bytes: &[u8]) -> String {
11 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
12 for chunk in bytes.chunks(3) {
13 let mut block = [0u8; 3];
14 block[..chunk.len()].copy_from_slice(chunk);
15 let n = u32::from_be_bytes([0, block[0], block[1], block[2]]);
16 for i in 0..4 {
17 if i <= chunk.len() {
18 out.push(ALPHABET[((n >> (18 - 6 * i)) & 63) as usize] as char);
19 } else {
20 out.push('=');
21 }
22 }
23 }
24 out
25}
26
27fn value(symbol: u8) -> Option<u32> {
28 let v = match symbol {
29 b'A'..=b'Z' => symbol - b'A',
30 b'a'..=b'z' => symbol - b'a' + 26,
31 b'0'..=b'9' => symbol - b'0' + 52,
32 b'+' => 62,
33 b'/' => 63,
34 _ => return None,
35 };
36 Some(u32::from(v))
37}
38
39#[must_use]
42pub fn decode(text: &str) -> Option<Vec<u8>> {
43 let bytes = text.as_bytes();
44 if bytes.len() % 4 != 0 {
45 return None;
46 }
47 let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
48 for (i, chunk) in bytes.chunks_exact(4).enumerate() {
49 let last = i == bytes.len() / 4 - 1;
50 let padding = chunk.iter().rev().take_while(|b| **b == b'=').count();
51 if padding > 2 || (padding > 0 && !last) {
52 return None;
53 }
54 let mut n = 0u32;
55 for symbol in &chunk[..4 - padding] {
56 n = (n << 6) | value(*symbol)?;
57 }
58 n <<= 6 * padding as u32;
59 let block = n.to_be_bytes();
60 let keep = 3 - padding;
61 if n & ((1u32 << (6 * padding as u32)) - 1) != 0 {
64 return None;
65 }
66 if padding > 0 && block[1 + keep..].iter().any(|b| *b != 0) {
67 return None;
68 }
69 out.extend_from_slice(&block[1..1 + keep]);
70 }
71 Some(out)
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn the_rfc_4648_vectors() {
80 let vectors = [
81 ("", ""),
82 ("f", "Zg=="),
83 ("fo", "Zm8="),
84 ("foo", "Zm9v"),
85 ("foob", "Zm9vYg=="),
86 ("fooba", "Zm9vYmE="),
87 ("foobar", "Zm9vYmFy"),
88 ];
89 for (plain, encoded) in vectors {
90 assert_eq!(encode(plain.as_bytes()), encoded);
91 assert_eq!(decode(encoded).unwrap(), plain.as_bytes());
92 }
93 }
94
95 #[test]
96 fn only_the_canonical_text_decodes() {
97 assert_eq!(decode("Zh=="), None);
99 assert_eq!(decode("Zm9="), None);
100 assert_eq!(decode("Zg="), None);
101 assert_eq!(decode("Zg"), None);
102 assert_eq!(decode("Zg==Zg=="), None);
103 assert_eq!(decode("Zm9v!A=="), None);
104 assert_eq!(decode("===="), None);
105 let root = "CsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2I=";
106 let bytes = decode(root).unwrap();
107 assert_eq!(bytes.len(), 32);
108 assert_eq!(encode(&bytes), root);
109 }
110}