1pub fn encode(bytes: &[u8]) -> String {
5 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
7 for chunk in bytes.chunks(3) {
8 let n = u32::from(chunk[0]) << 16
9 | u32::from(chunk.get(1).copied().unwrap_or(0)) << 8
10 | u32::from(chunk.get(2).copied().unwrap_or(0));
11 out.push(TABLE[(n >> 18) as usize & 63] as char);
12 out.push(TABLE[(n >> 12) as usize & 63] as char);
13 out.push(if chunk.len() > 1 { TABLE[(n >> 6) as usize & 63] as char } else { '=' });
14 out.push(if chunk.len() > 2 { TABLE[n as usize & 63] as char } else { '=' });
15 }
16 out
17}
18
19pub fn decode(text: &str) -> Option<Vec<u8>> {
21 let mut out = Vec::with_capacity(text.len() / 4 * 3);
22 let (mut buffer, mut bits) = (0_u32, 0_u32);
23 let mut padding = 0;
24 for byte in text.bytes() {
25 let value = match byte {
26 b'A'..=b'Z' => byte - b'A',
27 b'a'..=b'z' => byte - b'a' + 26,
28 b'0'..=b'9' => byte - b'0' + 52,
29 b'+' => 62,
30 b'/' => 63,
31 b'=' => {
32 padding += 1;
33 continue;
34 }
35 b' ' | b'\n' | b'\r' | b'\t' => continue,
36 _ => return None,
37 };
38 if padding > 0 {
39 return None;
40 }
41 buffer = (buffer << 6) | u32::from(value);
42 bits += 6;
43 if bits >= 8 {
44 bits -= 8;
45 out.push((buffer >> bits) as u8);
46 buffer &= (1 << bits) - 1;
47 }
48 }
49 Some(out)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn matches_the_standard() {
58 assert_eq!(encode(b""), "");
59 assert_eq!(encode(b"f"), "Zg==");
60 assert_eq!(encode(b"fo"), "Zm8=");
61 assert_eq!(encode(b"foo"), "Zm9v");
62 assert_eq!(encode(b"foobar"), "Zm9vYmFy");
63 }
64
65 #[test]
66 fn decoding_undoes_encoding_and_refuses_bad_text() {
67 for bytes in [&b""[..], b"f", b"fo", b"foo", b"foobar", &[0, 255, 128, 7]] {
68 assert_eq!(decode(&encode(bytes)).as_deref(), Some(bytes));
69 }
70 assert_eq!(decode("Zm 9v\n").as_deref(), Some(&b"foo"[..]), "whitespace is ignored");
71 assert_eq!(decode("Zm9v!"), None);
72 assert_eq!(decode("Zg==Zg"), None, "nothing after the padding");
73 }
74}