edit/
base64.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Base64 facilities.
5
6use crate::arena::ArenaString;
7
8const CHARSET: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
10/// One aspect of base64 is that the encoded length can be
11/// calculated accurately in advance, which is what this returns.
12#[inline]
13pub fn encode_len(src_len: usize) -> usize {
14    src_len.div_ceil(3) * 4
15}
16
17/// Encodes the given bytes as base64 and appends them to the destination string.
18pub fn encode(dst: &mut ArenaString, src: &[u8]) {
19    unsafe {
20        let mut inp = src.as_ptr();
21        let mut remaining = src.len();
22        let dst = dst.as_mut_vec();
23
24        let out_len = encode_len(src.len());
25        // ... we can then use this fact to reserve space all at once.
26        dst.reserve(out_len);
27
28        // SAFETY: Getting a pointer to the reserved space is only safe
29        // *after* calling `reserve()` as it may change the pointer.
30        let mut out = dst.as_mut_ptr().add(dst.len());
31
32        if remaining != 0 {
33            // Translate chunks of 3 source bytes into 4 base64-encoded bytes.
34            while remaining > 3 {
35                // SAFETY: Thanks to `remaining > 3`, reading 4 bytes at once is safe.
36                // This improves performance massively over a byte-by-byte approach,
37                // because it allows us to byte-swap the read and use simple bit-shifts below.
38                let val = u32::from_be((inp as *const u32).read_unaligned());
39                inp = inp.add(3);
40                remaining -= 3;
41
42                *out = CHARSET[(val >> 26) as usize];
43                out = out.add(1);
44                *out = CHARSET[(val >> 20) as usize & 0x3f];
45                out = out.add(1);
46                *out = CHARSET[(val >> 14) as usize & 0x3f];
47                out = out.add(1);
48                *out = CHARSET[(val >> 8) as usize & 0x3f];
49                out = out.add(1);
50            }
51
52            // Convert the remaining 1-3 bytes.
53            let mut in1 = 0;
54            let mut in2 = 0;
55
56            // We can simplify the following logic by assuming that there's only 1
57            // byte left. If there's >1 byte left, these two '=' will be overwritten.
58            *out.add(3) = b'=';
59            *out.add(2) = b'=';
60
61            if remaining >= 3 {
62                in2 = inp.add(2).read() as usize;
63                *out.add(3) = CHARSET[in2 & 0x3f];
64            }
65
66            if remaining >= 2 {
67                in1 = inp.add(1).read() as usize;
68                *out.add(2) = CHARSET[(in1 << 2 | in2 >> 6) & 0x3f];
69            }
70
71            let in0 = inp.add(0).read() as usize;
72            *out.add(1) = CHARSET[(in0 << 4 | in1 >> 4) & 0x3f];
73            *out.add(0) = CHARSET[in0 >> 2];
74        }
75
76        dst.set_len(dst.len() + out_len);
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::encode;
83    use crate::arena::{Arena, ArenaString};
84
85    #[test]
86    fn test_basic() {
87        let arena = Arena::new(4 * 1024).unwrap();
88        let enc = |s: &[u8]| {
89            let mut dst = ArenaString::new_in(&arena);
90            encode(&mut dst, s);
91            dst
92        };
93        assert_eq!(enc(b""), "");
94        assert_eq!(enc(b"a"), "YQ==");
95        assert_eq!(enc(b"ab"), "YWI=");
96        assert_eq!(enc(b"abc"), "YWJj");
97        assert_eq!(enc(b"abcd"), "YWJjZA==");
98        assert_eq!(enc(b"abcde"), "YWJjZGU=");
99        assert_eq!(enc(b"abcdef"), "YWJjZGVm");
100        assert_eq!(enc(b"abcdefg"), "YWJjZGVmZw==");
101        assert_eq!(enc(b"abcdefgh"), "YWJjZGVmZ2g=");
102        assert_eq!(enc(b"abcdefghi"), "YWJjZGVmZ2hp");
103        assert_eq!(enc(b"abcdefghij"), "YWJjZGVmZ2hpag==");
104        assert_eq!(enc(b"abcdefghijk"), "YWJjZGVmZ2hpams=");
105        assert_eq!(enc(b"abcdefghijkl"), "YWJjZGVmZ2hpamts");
106        assert_eq!(enc(b"abcdefghijklm"), "YWJjZGVmZ2hpamtsbQ==");
107        assert_eq!(enc(b"abcdefghijklmN"), "YWJjZGVmZ2hpamtsbU4=");
108        assert_eq!(enc(b"abcdefghijklmNO"), "YWJjZGVmZ2hpamtsbU5P");
109        assert_eq!(enc(b"abcdefghijklmNOP"), "YWJjZGVmZ2hpamtsbU5PUA==");
110        assert_eq!(enc(b"abcdefghijklmNOPQ"), "YWJjZGVmZ2hpamtsbU5PUFE=");
111        assert_eq!(enc(b"abcdefghijklmNOPQR"), "YWJjZGVmZ2hpamtsbU5PUFFS");
112        assert_eq!(enc(b"abcdefghijklmNOPQRS"), "YWJjZGVmZ2hpamtsbU5PUFFSUw==");
113        assert_eq!(enc(b"abcdefghijklmNOPQRST"), "YWJjZGVmZ2hpamtsbU5PUFFSU1Q=");
114        assert_eq!(enc(b"abcdefghijklmNOPQRSTU"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RV");
115        assert_eq!(enc(b"abcdefghijklmNOPQRSTUV"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RVVg==");
116        assert_eq!(enc(b"abcdefghijklmNOPQRSTUVW"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RVVlc=");
117        assert_eq!(enc(b"abcdefghijklmNOPQRSTUVWX"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RVVldY");
118        assert_eq!(enc(b"abcdefghijklmNOPQRSTUVWXY"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RVVldYWQ==");
119        assert_eq!(enc(b"abcdefghijklmNOPQRSTUVWXYZ"), "YWJjZGVmZ2hpamtsbU5PUFFSU1RVVldYWVo=");
120    }
121}