base64/engine/general_purpose/
mod.rs1use crate::{
3    alphabet,
4    alphabet::Alphabet,
5    engine::{Config, DecodeMetadata, DecodePaddingMode},
6    DecodeSliceError,
7};
8use core::convert::TryInto;
9
10pub(crate) mod decode;
11pub(crate) mod decode_suffix;
12
13pub use decode::GeneralPurposeEstimate;
14
15pub(crate) const INVALID_VALUE: u8 = 255;
16
17#[derive(Debug, Clone)]
24pub struct GeneralPurpose {
25    encode_table: [u8; 64],
26    decode_table: [u8; 256],
27    config: GeneralPurposeConfig,
28}
29
30impl GeneralPurpose {
31    pub const fn new(alphabet: &Alphabet, config: GeneralPurposeConfig) -> Self {
36        Self {
37            encode_table: encode_table(alphabet),
38            decode_table: decode_table(alphabet),
39            config,
40        }
41    }
42}
43
44impl super::Engine for GeneralPurpose {
45    type Config = GeneralPurposeConfig;
46    type DecodeEstimate = GeneralPurposeEstimate;
47
48    fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
49        let mut input_index: usize = 0;
50
51        const BLOCKS_PER_FAST_LOOP: usize = 4;
52        const LOW_SIX_BITS: u64 = 0x3F;
53
54        let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
57        let mut output_index = 0;
58
59        if last_fast_index > 0 {
60            while input_index <= last_fast_index {
61                let input_chunk =
64                    &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
65                let output_chunk =
66                    &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
67
68                let input_u64 = read_u64(&input_chunk[0..]);
77
78                output_chunk[0] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
79                output_chunk[1] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
80                output_chunk[2] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
81                output_chunk[3] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
82                output_chunk[4] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
83                output_chunk[5] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
84                output_chunk[6] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
85                output_chunk[7] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
86
87                let input_u64 = read_u64(&input_chunk[6..]);
88
89                output_chunk[8] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
90                output_chunk[9] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
91                output_chunk[10] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
92                output_chunk[11] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
93                output_chunk[12] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
94                output_chunk[13] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
95                output_chunk[14] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
96                output_chunk[15] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
97
98                let input_u64 = read_u64(&input_chunk[12..]);
99
100                output_chunk[16] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
101                output_chunk[17] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
102                output_chunk[18] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
103                output_chunk[19] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
104                output_chunk[20] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
105                output_chunk[21] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
106                output_chunk[22] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
107                output_chunk[23] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
108
109                let input_u64 = read_u64(&input_chunk[18..]);
110
111                output_chunk[24] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
112                output_chunk[25] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
113                output_chunk[26] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
114                output_chunk[27] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
115                output_chunk[28] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
116                output_chunk[29] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
117                output_chunk[30] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
118                output_chunk[31] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
119
120                output_index += BLOCKS_PER_FAST_LOOP * 8;
121                input_index += BLOCKS_PER_FAST_LOOP * 6;
122            }
123        }
124
125        const LOW_SIX_BITS_U8: u8 = 0x3F;
128
129        let rem = input.len() % 3;
130        let start_of_rem = input.len() - rem;
131
132        while input_index < start_of_rem {
135            let input_chunk = &input[input_index..(input_index + 3)];
136            let output_chunk = &mut output[output_index..(output_index + 4)];
137
138            output_chunk[0] = self.encode_table[(input_chunk[0] >> 2) as usize];
139            output_chunk[1] = self.encode_table
140                [((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
141            output_chunk[2] = self.encode_table
142                [((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
143            output_chunk[3] = self.encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
144
145            input_index += 3;
146            output_index += 4;
147        }
148
149        if rem == 2 {
150            output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
151            output[output_index + 1] =
152                self.encode_table[((input[start_of_rem] << 4 | input[start_of_rem + 1] >> 4)
153                    & LOW_SIX_BITS_U8) as usize];
154            output[output_index + 2] =
155                self.encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
156            output_index += 3;
157        } else if rem == 1 {
158            output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
159            output[output_index + 1] =
160                self.encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
161            output_index += 2;
162        }
163
164        output_index
165    }
166
167    fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
168        GeneralPurposeEstimate::new(input_len)
169    }
170
171    fn internal_decode(
172        &self,
173        input: &[u8],
174        output: &mut [u8],
175        estimate: Self::DecodeEstimate,
176    ) -> Result<DecodeMetadata, DecodeSliceError> {
177        decode::decode_helper(
178            input,
179            estimate,
180            output,
181            &self.decode_table,
182            self.config.decode_allow_trailing_bits,
183            self.config.decode_padding_mode,
184        )
185    }
186
187    fn config(&self) -> &Self::Config {
188        &self.config
189    }
190}
191
192pub(crate) const fn encode_table(alphabet: &Alphabet) -> [u8; 64] {
194    let mut encode_table = [0_u8; 64];
197    {
198        let mut index = 0;
199        while index < 64 {
200            encode_table[index] = alphabet.symbols[index];
201            index += 1;
202        }
203    }
204
205    encode_table
206}
207
208pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] {
212    let mut decode_table = [INVALID_VALUE; 256];
213
214    let mut index = 0;
217    while index < 64 {
218        decode_table[alphabet.symbols[index] as usize] = index as u8;
221        index += 1;
222    }
223
224    decode_table
225}
226
227#[inline]
228fn read_u64(s: &[u8]) -> u64 {
229    u64::from_be_bytes(s[..8].try_into().unwrap())
230}
231
232#[derive(Clone, Copy, Debug)]
245pub struct GeneralPurposeConfig {
246    encode_padding: bool,
247    decode_allow_trailing_bits: bool,
248    decode_padding_mode: DecodePaddingMode,
249}
250
251impl GeneralPurposeConfig {
252    pub const fn new() -> Self {
258        Self {
259            encode_padding: true,
261            decode_allow_trailing_bits: false,
262            decode_padding_mode: DecodePaddingMode::RequireCanonical,
263        }
264    }
265
266    pub const fn with_encode_padding(self, padding: bool) -> Self {
277        Self {
278            encode_padding: padding,
279            ..self
280        }
281    }
282
283    pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> Self {
291        Self {
292            decode_allow_trailing_bits: allow,
293            ..self
294        }
295    }
296
297    pub const fn with_decode_padding_mode(self, mode: DecodePaddingMode) -> Self {
311        Self {
312            decode_padding_mode: mode,
313            ..self
314        }
315    }
316}
317
318impl Default for GeneralPurposeConfig {
319    fn default() -> Self {
321        Self::new()
322    }
323}
324
325impl Config for GeneralPurposeConfig {
326    fn encode_padding(&self) -> bool {
327        self.encode_padding
328    }
329}
330
331pub const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
333
334pub const STANDARD_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
336
337pub const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, PAD);
339
340pub const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
342
343pub const PAD: GeneralPurposeConfig = GeneralPurposeConfig::new();
348
349pub const NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
351    .with_encode_padding(false)
352    .with_decode_padding_mode(DecodePaddingMode::RequireNone);