darkfi/util/encoding/
base64.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 * Copyright (C) 2021-2023 Kavan Mevada (MIT) (https://github.com/kavanmevada/base64cr)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#![forbid(unsafe_code)]
21
22macro_rules! seq4 {
23    [$($e:expr),*] => { [$($e,$e,$e,$e,)*] }
24}
25
26macro_rules! rep4 {
27    [$($e:expr),*] => { [$($e,)*$($e,)*$($e,)*$($e,)*] }
28}
29
30static E0: [char; 256] = seq4![
31    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
32    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
33    'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
34    '5', '6', '7', '8', '9', '+', '/'
35];
36
37static E1: [char; 256] = rep4![
38    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
39    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
40    'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
41    '5', '6', '7', '8', '9', '+', '/'
42];
43
44static E2: [char; 256] = E1;
45
46const FF: u32 = 33554431;
47
48static D0: [u32; 256] = [
49    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
50    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 248, FF, FF, FF,
51    252, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, FF, FF, FF, FF, FF, FF, FF, 0, 4, 8, 12,
52    16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, FF,
53    FF, FF, FF, FF, FF, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160,
54    164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
55    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
56    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
57    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
58    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
59    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
60    FF, FF, FF,
61];
62
63static D1: [u32; 256] = [
64    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
65    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 57347, FF, FF, FF,
66    61443, 16387, 20483, 24579, 28675, 32771, 36867, 40963, 45059, 49155, 53251, FF, FF, FF, FF,
67    FF, FF, FF, 0, 4096, 8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864, 40960, 45056,
68    49152, 53248, 57344, 61440, 1, 4097, 8193, 12289, 16385, 20481, 24577, 28673, 32769, 36865, FF,
69    FF, FF, FF, FF, FF, 40961, 45057, 49153, 53249, 57345, 61441, 2, 4098, 8194, 12290, 16386,
70    20482, 24578, 28674, 32770, 36866, 40962, 45058, 49154, 53250, 57346, 61442, 3, 4099, 8195,
71    12291, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
72    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
73    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
74    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
75    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
76    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
77];
78
79static D2: [u32; 256] = [
80    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
81    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 8392448, FF, FF,
82    FF, 12586752, 3328, 4197632, 8391936, 12586240, 3584, 4197888, 8392192, 12586496, 3840,
83    4198144, FF, FF, FF, FF, FF, FF, FF, 0, 4194304, 8388608, 12582912, 256, 4194560, 8388864,
84    12583168, 512, 4194816, 8389120, 12583424, 768, 4195072, 8389376, 12583680, 1024, 4195328,
85    8389632, 12583936, 1280, 4195584, 8389888, 12584192, 1536, 4195840, FF, FF, FF, FF, FF, FF,
86    8390144, 12584448, 1792, 4196096, 8390400, 12584704, 2048, 4196352, 8390656, 12584960, 2304,
87    4196608, 8390912, 12585216, 2560, 4196864, 8391168, 12585472, 2816, 4197120, 8391424, 12585728,
88    3072, 4197376, 8391680, 12585984, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
89    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
90    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
91    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
92    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
93    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
94];
95
96static D3: [u32; 256] = [
97    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
98    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 4063232, FF, FF,
99    FF, 4128768, 3407872, 3473408, 3538944, 3604480, 3670016, 3735552, 3801088, 3866624, 3932160,
100    3997696, FF, FF, FF, FF, FF, FF, FF, 0, 65536, 131072, 196608, 262144, 327680, 393216, 458752,
101    524288, 589824, 655360, 720896, 786432, 851968, 917504, 983040, 1048576, 1114112, 1179648,
102    1245184, 1310720, 1376256, 1441792, 1507328, 1572864, 1638400, FF, FF, FF, FF, FF, FF, 1703936,
103    1769472, 1835008, 1900544, 1966080, 2031616, 2097152, 2162688, 2228224, 2293760, 2359296,
104    2424832, 2490368, 2555904, 2621440, 2686976, 2752512, 2818048, 2883584, 2949120, 3014656,
105    3080192, 3145728, 3211264, 3276800, 3342336, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
106    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
107    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
108    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
109    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
110    FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
111    FF,
112];
113
114/// Encode a byte slice into a base64 string
115pub fn encode(data: &[u8]) -> String {
116    let len = data.len();
117
118    let mut dest = vec![0u8; ((4 * len / 3) + 3) & !3];
119
120    let mut i = 0;
121    let mut j = 0;
122
123    if len > 2 {
124        while i < len - 2 {
125            let t1 = data[i];
126            let t2 = data[i + 1];
127            let t3 = data[i + 2];
128
129            dest[j] = E0[t1 as usize] as u8;
130            dest[j + 1] = E1[(((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)) as usize] as u8;
131            dest[j + 2] = E1[(((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)) as usize] as u8;
132            dest[j + 3] = E2[t3 as usize] as u8;
133
134            i += 3;
135            j += 4;
136        }
137    }
138    match len - i {
139        0 => {}
140        1 => {
141            let t1 = data[i];
142
143            dest[j] = E0[t1 as usize] as u8;
144            dest[j + 1] = E1[((t1 & 0x03) << 4) as usize] as u8;
145            dest[j + 2] = b'=';
146            dest[j + 3] = b'=';
147        }
148        _ => {
149            /* case 2 */
150            let t1 = data[i] as usize;
151            let t2 = data[i + 1] as usize;
152
153            dest[j] = E0[t1] as u8;
154            dest[j + 1] = E1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)] as u8;
155            dest[j + 2] = E2[(t2 & 0x0F) << 2] as u8;
156            dest[j + 3] = b'=';
157        }
158    }
159
160    String::from_utf8(dest).unwrap()
161}
162
163/// Tries to decode a base64 string into a byte vector.
164/// Returns `None` if something fails.
165pub fn decode(data: &str) -> Option<Vec<u8>> {
166    if !data.is_ascii() || data.is_empty() {
167        return None
168    }
169
170    let data = match data.len() % 4 {
171        1 => return None, // Invalid input
172        2 => format!("{data}=="),
173        3 => format!("{data}="),
174        _ => data.to_string(), // Full or already padded
175    };
176
177    let data = data.as_bytes();
178
179    let mut padding_count = 0;
180    for (i, &byte) in data.iter().enumerate() {
181        let valid = byte.is_ascii_uppercase() ||
182            byte.is_ascii_lowercase() ||
183            byte.is_ascii_digit() ||
184            byte == b'+' ||
185            byte == b'/' ||
186            byte == b'=';
187
188        if !valid {
189            return None
190        }
191
192        if byte == b'=' {
193            padding_count += 1;
194            if i < data.len() - 2 {
195                return None
196            }
197        } else if padding_count > 0 {
198            return None
199        }
200    }
201
202    if padding_count > 2 {
203        return None
204    }
205
206    let mut len = data.len();
207
208    if data[len - 1] == b'=' {
209        len -= 1;
210        if data[len - 1] == b'=' {
211            len -= 1;
212        }
213    }
214
215    let mut dest = vec![0u8; (3 * (data.len() / 4)) - (data.len() - len)];
216
217    let leftover = len % 4;
218    let chunks = if leftover == 0 { len / 4 - 1 } else { len / 4 };
219
220    let mut j = 0;
221    let mut k = 0;
222    for _ in 0..chunks {
223        let x: u32 = D0[data[k] as usize] |
224            D1[data[1 + k] as usize] |
225            D2[data[2 + k] as usize] |
226            D3[data[3 + k] as usize];
227
228        dest[j] = x as u8;
229        dest[j + 1] = (x >> 8) as u8;
230        dest[j + 2] = (x >> 16) as u8;
231
232        j += 3;
233        k += 4;
234    }
235
236    match leftover {
237        0 => {
238            let x: u32 = D0[data[k] as usize] |
239                D1[data[1 + k] as usize] |
240                D2[data[2 + k] as usize] |
241                D3[data[3 + k] as usize];
242
243            dest[j] = x as u8;
244            dest[j + 1] = (x >> 8) as u8;
245            dest[j + 2] = (x >> 16) as u8;
246
247            // (chunks + 1) * 3)
248            return Some(dest)
249        }
250        1 => {
251            /* with padding this is an impossible case */
252            let x: u32 = D0[data[k] as usize]; // i.e. first char/byte in int
253            dest[j] = x as u8;
254        }
255        2 => {
256            // * case 2, 1  output byte */
257            let x: u32 = D0[data[k] as usize] | D1[data[1 + k] as usize]; // i.e. first char
258            dest[j] = x as u8;
259        }
260        _ => {
261            let x: u32 = D0[data[k] as usize] | D1[data[1 + k] as usize] | D2[data[2 + k] as usize]; /* 0x3c */
262            dest[j] = x as u8;
263            dest[j + 1] = (x >> 8) as u8;
264        }
265    }
266
267    // 3 * chunks + (6 * leftover) / 8
268    Some(dest)
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    pub fn b64_encdec() {
277        const EXAMPLES: [(&[u8], &str); 3] = [
278            (b"abc123!?$*&()'-=@~", "YWJjMTIzIT8kKiYoKSctPUB+"),
279            (b"gm world", "Z20gd29ybGQ="),
280            (
281                b"Man is distinguished, not only by his reason, but by this singular passion from \
282            other animals, which is a lust of the mind, that by a perseverance of delight \
283            in the continued and indefatigable generation of knowledge, exceeds the short \
284            vehemence of any carnal pleasure.",
285                "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\
286            IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\
287            dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\
288            dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\
289            ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
290            ),
291        ];
292
293        for &(input, answer) in EXAMPLES.iter() {
294            let res = encode(input);
295            assert_eq!(answer, res);
296
297            let res = decode(answer).unwrap();
298            assert_eq!(input, res);
299        }
300
301        // Unpadded input checks
302        assert_eq!(b"a".to_vec(), decode("YQ").unwrap());
303        assert_eq!(b"a".to_vec(), decode("YQ=").unwrap());
304        assert_eq!(b"a0".to_vec(), decode("YTA").unwrap());
305        assert_eq!(b"gm world".to_vec(), decode("Z20gd29ybGQ").unwrap());
306
307        // Malformed decode input checks
308        assert!(decode("").is_none());
309        assert!(decode("a").is_none());
310        assert!(decode("a=").is_none());
311        assert!(decode("a==").is_none());
312        assert!(decode("a===").is_none());
313        assert!(decode("=").is_none());
314        assert!(decode("==").is_none());
315        assert!(decode("===").is_none());
316        assert!(decode("====").is_none());
317        assert!(decode("This is a random string.").is_none());
318    }
319}