Skip to main content

base64_const/
lib.rs

1use std::str;
2pub mod url_safe_util;
3pub mod macros;
4
5const BITS_DIFF: usize = 8 - 6;
6const PADDING_B4: u8 = 0x4d;
7
8
9pub fn encode(bytes: &[u8]) -> String {
10    let bytes: &[u8] = &encode_bytes(bytes);
11    str::from_utf8(bytes).unwrap().to_string()
12}
13
14pub fn encode_bytes(bytes: &[u8]) -> Box<[u8]> {
15    let result_length = encoded_length_for(bytes);
16
17    let mut result = Vec::with_capacity(result_length);
18    for index in 0..result_length {
19        let start_bit = index * 6;
20        let start_byte = start_bit >> 3;
21
22        let start_bit = (start_bit % 8) as u8;
23        let b64 = if start_byte >= bytes.len() {
24            PADDING_B4
25        } else if start_bit <= BITS_DIFF as u8 {
26            bytes[start_byte] >> (BITS_DIFF as u8 - start_bit) & 0b0011_1111
27        } else {
28            let mut result = (bytes[start_byte] & (0xff >> start_bit)) << start_bit - BITS_DIFF as u8;
29            if start_byte + 1 < bytes.len() {
30                result |= bytes[start_byte + 1] >> 10 - start_bit;
31            }
32            result
33        };
34
35        result.push(b64_to_ascii(b64));
36    }
37
38    result.into_boxed_slice()
39}
40
41pub fn decode(bytes: &[u8]) -> Box<[u8]> {
42    let potential_length = (bytes.len() * 6) >> 3;
43    let mut start_bit: u8 = 0;
44    let mut byte: u8 = 0;
45    let mut result: Vec<u8> = Vec::with_capacity(potential_length);
46
47    for index in 0..bytes.len() {
48        let ascii = bytes[index];
49
50        if ascii == b'=' {
51            break;
52        }
53
54        let b64 = ascii_to_b64(ascii);
55
56        if start_bit <= BITS_DIFF as u8 {
57            byte |= b64 << (BITS_DIFF as u8 - start_bit);
58            if start_bit == BITS_DIFF as u8 {
59                result.push(byte);
60                byte = 0;
61            }
62        } else {
63            byte |= b64 >> start_bit - BITS_DIFF as u8;
64            result.push(byte);
65            byte = b64 << (8 - start_bit + BITS_DIFF as u8);
66        }
67        start_bit = (start_bit + 6) % 8;
68    }
69
70    result.into_boxed_slice()
71}
72
73const fn b64_to_ascii(b64: u8) -> u8 {
74    if b64 < 26 {
75        b'A' + b64
76    } else if b64 < 52 {
77        b'a' + b64 - 26
78    } else if b64 < 62 {
79        b'0' + b64 - 52
80    } else if b64 == 62 {
81        b'+'
82    } else if b64 == 63 {
83        b'/'
84    } else {
85        b'='
86    }
87}
88
89const fn ascii_to_b64(ascii: u8) -> u8 {
90    if ascii >= b'a' {
91        26 + ascii - b'a'
92    } else if ascii >= b'A' {
93        ascii - b'A'
94    } else if ascii >= b'0' {
95        52 + ascii - b'0'
96    } else if ascii == b'+' {
97        62
98    } else if ascii == b'/' {
99        63
100    } else {
101        0xff
102    }
103}
104
105pub fn encoded_length_for(bytes: &[u8]) -> usize {
106    let bits_length = bytes.len() << 3;
107
108    let mut result_length = bits_length / 6;
109    if bits_length % 6 > 0 {
110        result_length += (8 - (bits_length % 6)) / BITS_DIFF;
111    }
112    result_length
113}
114
115
116#[cfg(test)]
117mod tests {
118    use std::str::from_utf8;
119
120    use super::*;
121
122    #[test]
123    fn test_result_length() {
124        assert_eq!(encoded_length_for(b"a"), 4);
125        assert_eq!(encoded_length_for(b"ab"), 4);
126        assert_eq!(encoded_length_for(b"abc"), 4);
127        assert_eq!(encoded_length_for(b"abcd"), 8);
128        assert_eq!(encoded_length_for(b"abcde"), 8);
129        assert_eq!(encoded_length_for(b"abcdef"), 8);
130    }
131
132    #[test]
133    fn test_encode() {
134        let base64 = encode("Ma".as_bytes());
135        assert_eq!(base64, "TWE=");
136
137        let base64 = encode("Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.".as_bytes());
138        assert_eq!(base64, "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
139
140        assert_eq!(
141            "XC5WGfrtRruAKSncMk+InwNcbkIX/v+jyz3YAqKMwiCtv9O5BbqsvlSDErnoEoyJCljGAWvqOHLLgP1BxxuZsg==",
142            encode([
143                0x5c, 0x2e, 0x56, 0x19, 0xfa, 0xed, 0x46, 0xbb, 0x80, 0x29, 0x29, 0xdc, 0x32, 0x4f, 0x88, 0x9f, 0x03, 0x5c, 
144                0x6e, 0x42, 0x17, 0xfe, 0xff, 0xa3, 0xcb, 0x3d, 0xd8, 0x02, 0xa2, 0x8c, 0xc2, 0x20, 0xad, 0xbf, 0xd3, 0xb9, 
145                0x05, 0xba, 0xac, 0xbe, 0x54, 0x83, 0x12, 0xb9, 0xe8, 0x12, 0x8c, 0x89, 0x0a, 0x58, 0xc6, 0x01, 0x6b, 0xea, 
146                0x38, 0x72, 0xcb, 0x80, 0xfd, 0x41, 0xc7, 0x1b, 0x99, 0xb2
147            ].as_slice())
148        )
149    }
150
151    #[test]
152    fn test_b64_to_ascii() {
153        assert_eq!(b64_to_ascii(0), b'A');
154        assert_eq!(b64_to_ascii(9), b'J');
155        assert_eq!(b64_to_ascii(40), b'o');
156        assert_eq!(b64_to_ascii(51), b'z');
157        assert_eq!(b64_to_ascii(52), b'0');
158        assert_eq!(b64_to_ascii(61), b'9');
159        assert_eq!(b64_to_ascii(62), b'+');
160        assert_eq!(b64_to_ascii(63), b'/');
161        assert_eq!(b64_to_ascii(PADDING_B4), b'=');
162    }
163
164    #[test]
165    fn test_ascii_to_b64() {
166        assert_eq!(ascii_to_b64(b'A'), 0);
167        assert_eq!(ascii_to_b64(b'J'), 9);
168        assert_eq!(ascii_to_b64(b'o'), 40);
169        assert_eq!(ascii_to_b64(b'z'), 51);
170        assert_eq!(ascii_to_b64(b'0'), 52);
171        assert_eq!(ascii_to_b64(b'9'), 61);
172        assert_eq!(ascii_to_b64(b'+'), 62);
173        assert_eq!(ascii_to_b64(b'/'), 63);
174        //assert_eq!(ascii_to_b64(b'='), PADDING_B4);
175    }
176
177    #[test]
178    fn test_decode() {
179        let message = decode("TWE=".as_bytes());
180        assert_eq!(message.as_ref(), b"Ma");
181
182        let message = base64_decode!("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
183        assert_eq!(from_utf8(&message).unwrap(), "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.");
184
185        const DECODED: &[u8] = &base64_decode!("TWE=");
186        assert_eq!(DECODED, b"Ma");
187    
188        const LONG_DECODED: &[u8] = &base64_decode!("TWFu");
189        assert_eq!(LONG_DECODED, b"Man");
190        assert_eq!(
191            decode(b"XC5WGfrtRruAKSncMk+InwNcbkIX/v+jyz3YAqKMwiCtv9O5BbqsvlSDErnoEoyJCljGAWvqOHLLgP1BxxuZsg==").as_ref(),
192            [
193                0x5c, 0x2e, 0x56, 0x19, 0xfa, 0xed, 0x46, 0xbb, 0x80, 0x29, 0x29, 0xdc, 0x32, 0x4f, 0x88, 0x9f, 0x03, 0x5c, 
194                0x6e, 0x42, 0x17, 0xfe, 0xff, 0xa3, 0xcb, 0x3d, 0xd8, 0x02, 0xa2, 0x8c, 0xc2, 0x20, 0xad, 0xbf, 0xd3, 0xb9, 
195                0x05, 0xba, 0xac, 0xbe, 0x54, 0x83, 0x12, 0xb9, 0xe8, 0x12, 0x8c, 0x89, 0x0a, 0x58, 0xc6, 0x01, 0x6b, 0xea, 
196                0x38, 0x72, 0xcb, 0x80, 0xfd, 0x41, 0xc7, 0x1b, 0x99, 0xb2
197            ].as_slice()
198        )
199    }
200}