• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::{
2     alphabet::Alphabet,
3     engine::{
4         general_purpose::{self, decode_table, encode_table},
5         Config, DecodeEstimate, DecodePaddingMode, Engine,
6     },
7     DecodeError, PAD_BYTE,
8 };
9 use alloc::ops::BitOr;
10 use std::ops::{BitAnd, Shl, Shr};
11 
12 /// Comparatively simple implementation that can be used as something to compare against in tests
13 pub struct Naive {
14     encode_table: [u8; 64],
15     decode_table: [u8; 256],
16     config: NaiveConfig,
17 }
18 
19 impl Naive {
20     const ENCODE_INPUT_CHUNK_SIZE: usize = 3;
21     const DECODE_INPUT_CHUNK_SIZE: usize = 4;
22 
new(alphabet: &Alphabet, config: NaiveConfig) -> Self23     pub const fn new(alphabet: &Alphabet, config: NaiveConfig) -> Self {
24         Self {
25             encode_table: encode_table(alphabet),
26             decode_table: decode_table(alphabet),
27             config,
28         }
29     }
30 
decode_byte_into_u32(&self, offset: usize, byte: u8) -> Result<u32, DecodeError>31     fn decode_byte_into_u32(&self, offset: usize, byte: u8) -> Result<u32, DecodeError> {
32         let decoded = self.decode_table[byte as usize];
33 
34         if decoded == general_purpose::INVALID_VALUE {
35             return Err(DecodeError::InvalidByte(offset, byte));
36         }
37 
38         Ok(decoded as u32)
39     }
40 }
41 
42 impl Engine for Naive {
43     type Config = NaiveConfig;
44     type DecodeEstimate = NaiveEstimate;
45 
internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize46     fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
47         // complete chunks first
48 
49         const LOW_SIX_BITS: u32 = 0x3F;
50 
51         let rem = input.len() % Self::ENCODE_INPUT_CHUNK_SIZE;
52         // will never underflow
53         let complete_chunk_len = input.len() - rem;
54 
55         let mut input_index = 0_usize;
56         let mut output_index = 0_usize;
57         if let Some(last_complete_chunk_index) =
58             complete_chunk_len.checked_sub(Self::ENCODE_INPUT_CHUNK_SIZE)
59         {
60             while input_index <= last_complete_chunk_index {
61                 let chunk = &input[input_index..input_index + Self::ENCODE_INPUT_CHUNK_SIZE];
62 
63                 // populate low 24 bits from 3 bytes
64                 let chunk_int: u32 =
65                     (chunk[0] as u32).shl(16) | (chunk[1] as u32).shl(8) | (chunk[2] as u32);
66                 // encode 4x 6-bit output bytes
67                 output[output_index] = self.encode_table[chunk_int.shr(18) as usize];
68                 output[output_index + 1] =
69                     self.encode_table[chunk_int.shr(12_u8).bitand(LOW_SIX_BITS) as usize];
70                 output[output_index + 2] =
71                     self.encode_table[chunk_int.shr(6_u8).bitand(LOW_SIX_BITS) as usize];
72                 output[output_index + 3] =
73                     self.encode_table[chunk_int.bitand(LOW_SIX_BITS) as usize];
74 
75                 input_index += Self::ENCODE_INPUT_CHUNK_SIZE;
76                 output_index += 4;
77             }
78         }
79 
80         // then leftovers
81         if rem == 2 {
82             let chunk = &input[input_index..input_index + 2];
83 
84             // high six bits of chunk[0]
85             output[output_index] = self.encode_table[chunk[0].shr(2) as usize];
86             // bottom 2 bits of [0], high 4 bits of [1]
87             output[output_index + 1] =
88                 self.encode_table[(chunk[0].shl(4_u8).bitor(chunk[1].shr(4_u8)) as u32)
89                     .bitand(LOW_SIX_BITS) as usize];
90             // bottom 4 bits of [1], with the 2 bottom bits as zero
91             output[output_index + 2] =
92                 self.encode_table[(chunk[1].shl(2_u8) as u32).bitand(LOW_SIX_BITS) as usize];
93 
94             output_index += 3;
95         } else if rem == 1 {
96             let byte = input[input_index];
97             output[output_index] = self.encode_table[byte.shr(2) as usize];
98             output[output_index + 1] =
99                 self.encode_table[(byte.shl(4_u8) as u32).bitand(LOW_SIX_BITS) as usize];
100             output_index += 2;
101         }
102 
103         output_index
104     }
105 
internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate106     fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
107         NaiveEstimate::new(input_len)
108     }
109 
internal_decode( &self, input: &[u8], output: &mut [u8], estimate: Self::DecodeEstimate, ) -> Result<usize, DecodeError>110     fn internal_decode(
111         &self,
112         input: &[u8],
113         output: &mut [u8],
114         estimate: Self::DecodeEstimate,
115     ) -> Result<usize, DecodeError> {
116         if estimate.rem == 1 {
117             // trailing whitespace is so common that it's worth it to check the last byte to
118             // possibly return a better error message
119             if let Some(b) = input.last() {
120                 if *b != PAD_BYTE
121                     && self.decode_table[*b as usize] == general_purpose::INVALID_VALUE
122                 {
123                     return Err(DecodeError::InvalidByte(input.len() - 1, *b));
124                 }
125             }
126 
127             return Err(DecodeError::InvalidLength);
128         }
129 
130         let mut input_index = 0_usize;
131         let mut output_index = 0_usize;
132         const BOTTOM_BYTE: u32 = 0xFF;
133 
134         // can only use the main loop on non-trailing chunks
135         if input.len() > Self::DECODE_INPUT_CHUNK_SIZE {
136             // skip the last chunk, whether it's partial or full, since it might
137             // have padding, and start at the beginning of the chunk before that
138             let last_complete_chunk_start_index = estimate.complete_chunk_len
139                 - if estimate.rem == 0 {
140                     // Trailing chunk is also full chunk, so there must be at least 2 chunks, and
141                     // this won't underflow
142                     Self::DECODE_INPUT_CHUNK_SIZE * 2
143                 } else {
144                     // Trailing chunk is partial, so it's already excluded in
145                     // complete_chunk_len
146                     Self::DECODE_INPUT_CHUNK_SIZE
147                 };
148 
149             while input_index <= last_complete_chunk_start_index {
150                 let chunk = &input[input_index..input_index + Self::DECODE_INPUT_CHUNK_SIZE];
151                 let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18)
152                     | self
153                         .decode_byte_into_u32(input_index + 1, chunk[1])?
154                         .shl(12)
155                     | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6)
156                     | self.decode_byte_into_u32(input_index + 3, chunk[3])?;
157 
158                 output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8;
159                 output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8;
160                 output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8;
161 
162                 input_index += Self::DECODE_INPUT_CHUNK_SIZE;
163                 output_index += 3;
164             }
165         }
166 
167         general_purpose::decode_suffix::decode_suffix(
168             input,
169             input_index,
170             output,
171             output_index,
172             &self.decode_table,
173             self.config.decode_allow_trailing_bits,
174             self.config.decode_padding_mode,
175         )
176     }
177 
config(&self) -> &Self::Config178     fn config(&self) -> &Self::Config {
179         &self.config
180     }
181 }
182 
183 pub struct NaiveEstimate {
184     /// remainder from dividing input by `Naive::DECODE_CHUNK_SIZE`
185     rem: usize,
186     /// Length of input that is in complete `Naive::DECODE_CHUNK_SIZE`-length chunks
187     complete_chunk_len: usize,
188 }
189 
190 impl NaiveEstimate {
new(input_len: usize) -> Self191     fn new(input_len: usize) -> Self {
192         let rem = input_len % Naive::DECODE_INPUT_CHUNK_SIZE;
193         let complete_chunk_len = input_len - rem;
194 
195         Self {
196             rem,
197             complete_chunk_len,
198         }
199     }
200 }
201 
202 impl DecodeEstimate for NaiveEstimate {
decoded_len_estimate(&self) -> usize203     fn decoded_len_estimate(&self) -> usize {
204         ((self.complete_chunk_len / 4) + ((self.rem > 0) as usize)) * 3
205     }
206 }
207 
208 #[derive(Clone, Copy, Debug)]
209 pub struct NaiveConfig {
210     pub encode_padding: bool,
211     pub decode_allow_trailing_bits: bool,
212     pub decode_padding_mode: DecodePaddingMode,
213 }
214 
215 impl Config for NaiveConfig {
encode_padding(&self) -> bool216     fn encode_padding(&self) -> bool {
217         self.encode_padding
218     }
219 }
220