b64_rs/
lib.rs

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}