base64/engine/general_purpose/
mod.rs1use crate::alphabet::Symbol;
5use crate::{
6 alphabet,
7 alphabet::Alphabet,
8 engine::{Config, DecodeMetadata, DecodePaddingMode},
9 DecodeSliceError,
10};
11use core::convert::TryInto;
12
13pub(crate) mod decode;
14pub(crate) mod decode_suffix;
15
16pub use decode::GeneralPurposeEstimate;
17
18pub(crate) const INVALID_VALUE: u8 = 255;
19
20#[derive(Debug, Clone)]
28pub struct GeneralPurpose {
29 encode_table: [u8; 64],
30 decode_table: [u8; 256],
31 pub(crate) padding: Symbol,
32 config: GeneralPurposeConfig,
33}
34
35pub type Scalar = GeneralPurpose;
40
41impl GeneralPurpose {
42 #[must_use]
47 pub const fn new(alphabet: &Alphabet, config: GeneralPurposeConfig) -> Self {
48 Self {
49 encode_table: encode_table(alphabet),
50 decode_table: decode_table(alphabet),
51 padding: alphabet.padding,
52 config,
53 }
54 }
55
56 #[cfg(all(
58 feature = "simd-unsafe",
59 any(
60 target_arch = "x86_64",
61 all(target_arch = "aarch64", target_feature = "neon")
62 )
63 ))]
64 pub(crate) fn encode_table(&self) -> &[u8; 64] {
65 &self.encode_table
66 }
67
68 #[cfg(all(
70 feature = "simd-unsafe",
71 any(
72 target_arch = "x86_64",
73 all(target_arch = "aarch64", target_feature = "neon")
74 )
75 ))]
76 pub(crate) fn decode_table(&self) -> &[u8; 256] {
77 &self.decode_table
78 }
79}
80
81impl super::Engine for GeneralPurpose {
82 type Config = GeneralPurposeConfig;
83 type DecodeEstimate = GeneralPurposeEstimate;
84
85 fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
86 encode_helper(&self.encode_table, input, output, |_, _| (0, 0))
87 }
88
89 fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
90 GeneralPurposeEstimate::new(input_len)
91 }
92
93 fn internal_decode(
94 &self,
95 input: &[u8],
96 output: &mut [u8],
97 estimate: Self::DecodeEstimate,
98 ) -> Result<DecodeMetadata, DecodeSliceError> {
99 decode::decode_helper(
100 input,
101 &estimate,
102 output,
103 &self.decode_table,
104 self.config.decode_allow_trailing_bits,
105 self.padding,
106 self.config.decode_padding_mode,
107 |_, _, _| (0, 0),
108 )
109 }
110
111 fn config(&self) -> &Self::Config {
112 &self.config
113 }
114
115 fn padding(&self) -> Symbol {
116 self.padding
117 }
118}
119
120#[inline]
127pub(crate) fn encode_helper(
128 encode_table: &[u8; 64],
129 input: &[u8],
130 output: &mut [u8],
131 simd_prefix: impl FnOnce(&[u8], &mut [u8]) -> (usize, usize),
132) -> usize {
133 let (input_index, output_index) = simd_prefix(input, output);
134
135 debug_assert!(
136 input_index % 3 == 0,
137 "prefix must consume whole 3-byte groups"
138 );
139 debug_assert!(
140 output_index == input_index / 3 * 4,
141 "prefix output must match consumed input"
142 );
143 debug_assert!(input_index <= input.len());
144 debug_assert!(output_index <= output.len());
145
146 encode_scalar_tail(encode_table, input, output, input_index, output_index)
147}
148
149fn encode_scalar_tail(
152 encode_table: &[u8; 64],
153 input: &[u8],
154 output: &mut [u8],
155 mut input_index: usize,
156 mut output_index: usize,
157) -> usize {
158 const BLOCKS_PER_FAST_LOOP: usize = 4;
159 const LOW_SIX_BITS: u64 = 0x3F;
160
161 let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
164
165 if last_fast_index > 0 {
166 while input_index <= last_fast_index {
167 let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
170 let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
171
172 let input_u64 = read_u64(&input_chunk[0..]);
181
182 output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
183 output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
184 output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
185 output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
186 output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
187 output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
188 output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
189 output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
190
191 let input_u64 = read_u64(&input_chunk[6..]);
192
193 output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
194 output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
195 output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
196 output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
197 output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
198 output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
199 output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
200 output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
201
202 let input_u64 = read_u64(&input_chunk[12..]);
203
204 output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
205 output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
206 output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
207 output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
208 output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
209 output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
210 output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
211 output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
212
213 let input_u64 = read_u64(&input_chunk[18..]);
214
215 output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
216 output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
217 output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
218 output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
219 output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
220 output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
221 output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
222 output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
223
224 output_index += BLOCKS_PER_FAST_LOOP * 8;
225 input_index += BLOCKS_PER_FAST_LOOP * 6;
226 }
227 }
228
229 const LOW_SIX_BITS_U8: u8 = 0x3F;
232
233 let rem = input.len() % 3;
234 let start_of_rem = input.len() - rem;
235
236 while input_index < start_of_rem {
239 let input_chunk = &input[input_index..(input_index + 3)];
240 let output_chunk = &mut output[output_index..(output_index + 4)];
241
242 output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize];
243 output_chunk[1] =
244 encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
245 output_chunk[2] =
246 encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
247 output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
248
249 input_index += 3;
250 output_index += 4;
251 }
252
253 if rem == 2 {
254 output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
255 output[output_index + 1] = encode_table[((input[start_of_rem] << 4
256 | input[start_of_rem + 1] >> 4)
257 & LOW_SIX_BITS_U8) as usize];
258 output[output_index + 2] =
259 encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
260 output_index += 3;
261 } else if rem == 1 {
262 output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
263 output[output_index + 1] =
264 encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
265 output_index += 2;
266 }
267
268 output_index
269}
270
271pub(crate) const fn encode_table(alphabet: &Alphabet) -> [u8; 64] {
273 let mut encode_table = [0_u8; 64];
276 {
277 let mut index = 0;
278 while index < 64 {
279 encode_table[index] = alphabet.symbols[index];
280 index += 1;
281 }
282 }
283
284 encode_table
285}
286
287pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] {
291 let mut decode_table = [INVALID_VALUE; 256];
292
293 let mut index = 0_usize;
296 while index < 64 {
297 decode_table[alphabet.symbols[index] as usize] = index as u8;
300 index += 1;
301 }
302
303 decode_table
304}
305
306#[inline]
307fn read_u64(s: &[u8]) -> u64 {
308 u64::from_be_bytes(s[..8].try_into().unwrap())
309}
310
311#[derive(Clone, Copy, Debug)]
324pub struct GeneralPurposeConfig {
325 encode_padding: bool,
326 decode_allow_trailing_bits: bool,
327 decode_padding_mode: DecodePaddingMode,
328}
329
330impl GeneralPurposeConfig {
331 #[must_use]
337 pub const fn new() -> Self {
338 Self {
339 encode_padding: true,
341 decode_allow_trailing_bits: false,
342 decode_padding_mode: DecodePaddingMode::RequireCanonical,
343 }
344 }
345
346 #[must_use]
357 pub const fn with_encode_padding(self, padding: bool) -> Self {
358 Self {
359 encode_padding: padding,
360 ..self
361 }
362 }
363
364 #[must_use]
372 pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> Self {
373 Self {
374 decode_allow_trailing_bits: allow,
375 ..self
376 }
377 }
378
379 #[must_use]
393 pub const fn with_decode_padding_mode(self, mode: DecodePaddingMode) -> Self {
394 Self {
395 decode_padding_mode: mode,
396 ..self
397 }
398 }
399}
400
401impl Default for GeneralPurposeConfig {
402 fn default() -> Self {
404 Self::new()
405 }
406}
407
408impl Config for GeneralPurposeConfig {
409 fn encode_padding(&self) -> bool {
410 self.encode_padding
411 }
412}
413
414#[cfg(all(
415 feature = "simd-unsafe",
416 any(
417 target_arch = "x86_64",
418 all(target_arch = "aarch64", target_feature = "neon")
419 )
420))]
421impl GeneralPurposeConfig {
422 pub(crate) fn decode_allow_trailing_bits(&self) -> bool {
424 self.decode_allow_trailing_bits
425 }
426
427 pub(crate) fn decode_padding_mode(&self) -> DecodePaddingMode {
429 self.decode_padding_mode
430 }
431}
432
433pub const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
437
438pub const STANDARD_PAD_INDIFFERENT: GeneralPurpose =
443 GeneralPurpose::new(&alphabet::STANDARD, PAD_INDIFFERENT);
444
445pub const STANDARD_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
449
450pub const STANDARD_NO_PAD_INDIFFERENT: GeneralPurpose =
455 GeneralPurpose::new(&alphabet::STANDARD, NO_PAD_INDIFFERENT);
456
457pub const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, PAD);
461
462pub const URL_SAFE_PAD_INDIFFERENT: GeneralPurpose =
467 GeneralPurpose::new(&alphabet::URL_SAFE, PAD_INDIFFERENT);
468
469pub const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
473
474pub const URL_SAFE_NO_PAD_INDIFFERENT: GeneralPurpose =
479 GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD_INDIFFERENT);
480
481pub const PAD: GeneralPurposeConfig = GeneralPurposeConfig::new();
488
489pub const PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
493 .with_encode_padding(true)
494 .with_decode_padding_mode(DecodePaddingMode::Indifferent);
495
496pub const NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
500 .with_encode_padding(false)
501 .with_decode_padding_mode(DecodePaddingMode::RequireNone);
502
503pub const NO_PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
507 .with_encode_padding(false)
508 .with_decode_padding_mode(DecodePaddingMode::Indifferent);