1const ALPHABET: [u8; 64] = [
2 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
3 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
4 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
5 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,
6];
7
8const INDEX: [u8; 123] = [
9 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
10 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
11 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
12 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
13 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
14 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
15 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
16 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33,
17];
18
19pub fn encode(value: &str) -> String {
20 let mut data = String::new();
21 let mut bytes = value.bytes();
22
23 let len = bytes.len() / 3;
24 let rem = bytes.len() % 3;
25
26 for _ in 0..len {
27 let chunk: u32 = (bytes.next().unwrap() as u32) << 0x10
28 | (bytes.next().unwrap() as u32) << 0x08
29 | bytes.next().unwrap() as u32;
30
31 data.push(ALPHABET[(chunk >> 0x12 & 0x3f) as usize] as char);
32 data.push(ALPHABET[(chunk >> 0x0c & 0x3f) as usize] as char);
33 data.push(ALPHABET[(chunk >> 0x06 & 0x3f) as usize] as char);
34 data.push(ALPHABET[(chunk & 0x3f) as usize] as char);
35 }
36
37 if rem == 1 {
38 let chunk: u32 = (bytes.next().unwrap() as u32) << 0x04;
39
40 data.push(ALPHABET[(chunk >> 0x06 & 0x3f) as usize] as char);
41 data.push(ALPHABET[(chunk & 0x3f) as usize] as char);
42 data.push('=');
43 data.push('=');
44 } else if rem == 2 {
45 let chunk = (bytes.next().unwrap() as u32) << 0x0a | (bytes.next().unwrap() as u32) << 0x02;
46
47 data.push(ALPHABET[(chunk >> 0x0c & 0x3f) as usize] as char);
48 data.push(ALPHABET[(chunk >> 0x06 & 0x3f) as usize] as char);
49 data.push(ALPHABET[(chunk & 0x3f) as usize] as char);
50 data.push('=');
51 }
52
53 data
54}
55
56pub fn decode(value: &str) -> String {
57 let mut data = vec![];
58
59 let len = &value.len();
60 let padding = if &value[len - 2..] == "==" {
61 2
62 } else if &value[len - 1..] == "=" {
63 1
64 } else {
65 0
66 };
67
68 let mut bytes = value.bytes();
69 let len = (bytes.len() - padding) / 4;
70
71 for _ in 0..len {
72 let chunk: u32 = ((INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f) << 0x12
73 | (INDEX[bytes.next().unwrap() as usize] as u32) << 0x0c
74 | ((INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f) << 0x06
75 | (INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f;
76
77 data.push((chunk >> 0x10) as u8);
78 data.push((chunk >> 0x08) as u8);
79 data.push(chunk as u8);
80 }
81
82 if padding == 1 {
83 let chunk = ((INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f) << 0x0c
84 | ((INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f) << 0x06
85 | (INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f;
86
87 data.push((chunk >> 0x0a) as u8);
88 data.push((chunk >> 0x02) as u8);
89 } else if padding == 2 {
90 let chunk = ((INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f) << 0x06
91 | (INDEX[bytes.next().unwrap() as usize] as u32) & 0x3f;
92
93 data.push((chunk >> 0x04) as u8);
94 }
95
96 String::from_utf8(data).unwrap()
97}
98
99#[cfg(test)]
100mod test {
101 use super::{decode, encode, ALPHABET, INDEX};
102
103 struct B64Pair(pub &'static str, pub &'static str);
104
105 fn test_data() -> Vec<B64Pair> {
106 vec![
107 B64Pair("Test String", "VGVzdCBTdHJpbmc="),
108 B64Pair("bea is cool", "YmVhIGlzIGNvb2w="),
109 B64Pair("🥺", "8J+lug=="),
110 B64Pair("おはいよう!私の名前はBea!元気ですか?", "44GK44Gv44GE44KI44GG77yB56eB44Gu5ZCN5YmN44GvQmVhIeWFg+awl+OBp+OBmeOBi++8nw=="),
111 B64Pair("These sentences feel random but they're not, I promise. I am just making sure to test as many different silly things I can!", "VGhlc2Ugc2VudGVuY2VzIGZlZWwgcmFuZG9tIGJ1dCB0aGV5J3JlIG5vdCwgSSBwcm9taXNlLiBJIGFtIGp1c3QgbWFraW5nIHN1cmUgdG8gdGVzdCBhcyBtYW55IGRpZmZlcmVudCBzaWxseSB0aGluZ3MgSSBjYW4h"),
112 B64Pair("I need to test padding of each length, you see!!", "SSBuZWVkIHRvIHRlc3QgcGFkZGluZyBvZiBlYWNoIGxlbmd0aCwgeW91IHNlZSEh"),
113 ]
114 }
115
116 #[test]
117 fn encode_string() {
118 test_data()
119 .iter()
120 .for_each(|B64Pair(i, o)| assert_eq!(encode(i), *o));
121 }
122
123 #[test]
124 fn decode_string() {
125 test_data()
126 .iter()
127 .for_each(|B64Pair(i, o)| assert_eq!(decode(o), *i));
128 }
129
130 #[test]
131 fn lookup_tables() {
132 for i in 0..ALPHABET.len() {
133 assert_eq!(INDEX[ALPHABET[i] as usize], i as u8);
134 }
135 }
136}