1use super::{
4 contracts::InputError,
5 specifications::{
6 Base64, CodecSettings, DecodePadding, EncodePadding, RuntimeSpec, StrictStandardPadded,
7 StrictStandardUnpadded, StrictUrlSafePadded, StrictUrlSafeUnpadded, TrailingBits,
8 },
9};
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum ConstTransformError {
15 LengthOverflow,
17 Input(InputError),
19 OutputLengthMismatch {
21 required: usize,
23 actual: usize,
25 },
26}
27
28impl core::fmt::Display for ConstTransformError {
29 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30 match self {
31 Self::LengthOverflow => formatter.write_str("base64 output length overflows usize"),
32 Self::Input(error) => error.fmt(formatter),
33 Self::OutputLengthMismatch { required, actual } => write!(
34 formatter,
35 "base64 exact output length mismatch: required {required}, actual {actual}"
36 ),
37 }
38 }
39}
40
41#[cfg(feature = "std")]
42impl std::error::Error for ConstTransformError {}
43
44impl CodecSettings {
45 pub const fn encoded_len(self, input_len: usize) -> Result<usize, ConstTransformError> {
47 let Some(complete) = (input_len / 3).checked_mul(4) else {
48 return Err(ConstTransformError::LengthOverflow);
49 };
50 let remainder = input_len % 3;
51 let tail = match (remainder, self.encode_padding()) {
52 (0, _) => 0,
53 (_, EncodePadding::Padded) => 4,
54 (value, EncodePadding::Unpadded) => value + 1,
55 };
56 match complete.checked_add(tail) {
57 Some(value) => Ok(value),
58 None => Err(ConstTransformError::LengthOverflow),
59 }
60 }
61
62 pub const fn decoded_len(self, input: &[u8]) -> Result<usize, ConstTransformError> {
64 validate_and_measure(self, input)
65 }
66
67 pub const fn encode_array<const INPUT: usize, const OUTPUT: usize>(
69 self,
70 input: &[u8; INPUT],
71 ) -> Result<[u8; OUTPUT], ConstTransformError> {
72 let required = match self.encoded_len(INPUT) {
73 Ok(value) => value,
74 Err(error) => return Err(error),
75 };
76 if required != OUTPUT {
77 return Err(ConstTransformError::OutputLengthMismatch {
78 required,
79 actual: OUTPUT,
80 });
81 }
82
83 let mut output = [0u8; OUTPUT];
84 encode_exact(self, input, &mut output);
85 Ok(output)
86 }
87
88 pub const fn decode_array<const INPUT: usize, const OUTPUT: usize>(
90 self,
91 input: &[u8; INPUT],
92 ) -> Result<[u8; OUTPUT], ConstTransformError> {
93 let required = match self.decoded_len(input) {
94 Ok(value) => value,
95 Err(error) => return Err(error),
96 };
97 if required != OUTPUT {
98 return Err(ConstTransformError::OutputLengthMismatch {
99 required,
100 actual: OUTPUT,
101 });
102 }
103
104 let mut output = [0u8; OUTPUT];
105 decode_exact(self, input, &mut output);
106 Ok(output)
107 }
108}
109
110macro_rules! const_codec_methods {
111 ($specification:ty, $settings:expr) => {
112 impl Base64<$specification> {
113 #[must_use]
115 pub const fn const_settings(&self) -> CodecSettings {
116 $settings
117 }
118
119 pub const fn encode_array<const INPUT: usize, const OUTPUT: usize>(
121 &self,
122 input: &[u8; INPUT],
123 ) -> Result<[u8; OUTPUT], ConstTransformError> {
124 self.const_settings().encode_array(input)
125 }
126
127 pub const fn decode_array<const INPUT: usize, const OUTPUT: usize>(
129 &self,
130 input: &[u8; INPUT],
131 ) -> Result<[u8; OUTPUT], ConstTransformError> {
132 self.const_settings().decode_array(input)
133 }
134 }
135 };
136}
137
138const_codec_methods!(StrictStandardPadded, StrictStandardPadded::const_settings());
139const_codec_methods!(
140 StrictStandardUnpadded,
141 StrictStandardUnpadded::const_settings()
142);
143const_codec_methods!(StrictUrlSafePadded, StrictUrlSafePadded::const_settings());
144const_codec_methods!(
145 StrictUrlSafeUnpadded,
146 StrictUrlSafeUnpadded::const_settings()
147);
148
149impl Base64<RuntimeSpec> {
150 #[must_use]
152 pub const fn const_settings(&self) -> CodecSettings {
153 self.specification().const_settings()
154 }
155
156 pub const fn encode_array<const INPUT: usize, const OUTPUT: usize>(
158 &self,
159 input: &[u8; INPUT],
160 ) -> Result<[u8; OUTPUT], ConstTransformError> {
161 self.const_settings().encode_array(input)
162 }
163
164 pub const fn decode_array<const INPUT: usize, const OUTPUT: usize>(
166 &self,
167 input: &[u8; INPUT],
168 ) -> Result<[u8; OUTPUT], ConstTransformError> {
169 self.const_settings().decode_array(input)
170 }
171}
172
173#[allow(clippy::manual_is_multiple_of)]
174const fn validate_and_measure(
175 settings: CodecSettings,
176 input: &[u8],
177) -> Result<usize, ConstTransformError> {
178 if matches!(settings.decode_padding(), DecodePadding::RequireCanonical) && input.len() % 4 != 0
179 {
180 return Err(ConstTransformError::Input(InputError::TruncatedInput {
181 index: input.len(),
182 }));
183 }
184
185 let mut read = 0;
186 let mut written = 0usize;
187 while input.len() - read >= 4 {
188 let (produced, terminal) = match validate_quantum(settings, input, read) {
189 Ok(value) => value,
190 Err(error) => return Err(ConstTransformError::Input(error)),
191 };
192 written = match written.checked_add(produced) {
193 Some(value) => value,
194 None => return Err(ConstTransformError::LengthOverflow),
195 };
196 read += 4;
197 if terminal && read != input.len() {
198 return Err(ConstTransformError::Input(InputError::TrailingData {
199 index: read,
200 }));
201 }
202 }
203
204 if read == input.len() {
205 return Ok(written);
206 }
207 if matches!(settings.decode_padding(), DecodePadding::RequireCanonical) {
208 return Err(ConstTransformError::Input(InputError::TruncatedInput {
209 index: input.len(),
210 }));
211 }
212
213 match input.len() - read {
214 2 => {
215 if let Err(error) = decode_symbol(settings, input[read], read) {
216 return Err(ConstTransformError::Input(error));
217 }
218 let second = match decode_symbol(settings, input[read + 1], read + 1) {
219 Ok(value) => value,
220 Err(error) => return Err(ConstTransformError::Input(error)),
221 };
222 if second & 0x0f != 0
223 && matches!(settings.trailing_bits(), TrailingBits::RequireCanonical)
224 {
225 return Err(ConstTransformError::Input(
226 InputError::NonCanonicalTrailingBits { index: read + 1 },
227 ));
228 }
229 add_decoded_len(written, 1)
230 }
231 3 => validate_three_symbol_tail(settings, input, read, written),
232 _ => Err(ConstTransformError::Input(InputError::InvalidLength)),
233 }
234}
235
236const fn validate_three_symbol_tail(
237 settings: CodecSettings,
238 input: &[u8],
239 read: usize,
240 written: usize,
241) -> Result<usize, ConstTransformError> {
242 let first = match decode_symbol(settings, input[read], read) {
243 Ok(value) => value,
244 Err(error) => return Err(ConstTransformError::Input(error)),
245 };
246 let second = match decode_symbol(settings, input[read + 1], read + 1) {
247 Ok(value) => value,
248 Err(error) => return Err(ConstTransformError::Input(error)),
249 };
250 let _ = first;
251 if input[read + 2] == b'=' {
252 if matches!(settings.decode_padding(), DecodePadding::Forbid) {
253 return Err(ConstTransformError::Input(InputError::InvalidPadding {
254 index: read + 2,
255 }));
256 }
257 if second & 0x0f != 0 && matches!(settings.trailing_bits(), TrailingBits::RequireCanonical)
258 {
259 return Err(ConstTransformError::Input(
260 InputError::NonCanonicalTrailingBits { index: read + 1 },
261 ));
262 }
263 return add_decoded_len(written, 1);
264 }
265
266 let third = match decode_symbol(settings, input[read + 2], read + 2) {
267 Ok(value) => value,
268 Err(error) => return Err(ConstTransformError::Input(error)),
269 };
270 if third & 0x03 != 0 && matches!(settings.trailing_bits(), TrailingBits::RequireCanonical) {
271 return Err(ConstTransformError::Input(
272 InputError::NonCanonicalTrailingBits { index: read + 2 },
273 ));
274 }
275 add_decoded_len(written, 2)
276}
277
278const fn validate_quantum(
279 settings: CodecSettings,
280 input: &[u8],
281 read: usize,
282) -> Result<(usize, bool), InputError> {
283 let first = match decode_symbol(settings, input[read], read) {
284 Ok(value) => value,
285 Err(error) => return Err(error),
286 };
287 let second = match decode_symbol(settings, input[read + 1], read + 1) {
288 Ok(value) => value,
289 Err(error) => return Err(error),
290 };
291 let _ = first;
292 match (input[read + 2], input[read + 3]) {
293 (b'=', b'=') => {
294 if matches!(settings.decode_padding(), DecodePadding::Forbid) {
295 return Err(InputError::InvalidPadding { index: read + 2 });
296 }
297 if second & 0x0f != 0
298 && matches!(settings.trailing_bits(), TrailingBits::RequireCanonical)
299 {
300 return Err(InputError::NonCanonicalTrailingBits { index: read + 1 });
301 }
302 Ok((1, true))
303 }
304 (b'=', _) => Err(InputError::InvalidPadding { index: read + 2 }),
305 (third, b'=') => {
306 if matches!(settings.decode_padding(), DecodePadding::Forbid) {
307 return Err(InputError::InvalidPadding { index: read + 3 });
308 }
309 let third = match decode_symbol(settings, third, read + 2) {
310 Ok(value) => value,
311 Err(error) => return Err(error),
312 };
313 if third & 0x03 != 0
314 && matches!(settings.trailing_bits(), TrailingBits::RequireCanonical)
315 {
316 return Err(InputError::NonCanonicalTrailingBits { index: read + 2 });
317 }
318 Ok((2, true))
319 }
320 (third, fourth) => {
321 if let Err(error) = decode_symbol(settings, third, read + 2) {
322 return Err(error);
323 }
324 if let Err(error) = decode_symbol(settings, fourth, read + 3) {
325 return Err(error);
326 }
327 Ok((3, false))
328 }
329 }
330}
331
332const fn decode_symbol(settings: CodecSettings, byte: u8, index: usize) -> Result<u8, InputError> {
333 match settings.alphabet().decode_byte(byte) {
334 Some(value) => Ok(value),
335 None if byte == b'=' => Err(InputError::InvalidPadding { index }),
336 None => Err(InputError::InvalidByte { index, byte }),
337 }
338}
339
340const fn add_decoded_len(written: usize, additional: usize) -> Result<usize, ConstTransformError> {
341 match written.checked_add(additional) {
342 Some(value) => Ok(value),
343 None => Err(ConstTransformError::LengthOverflow),
344 }
345}
346
347#[allow(clippy::cast_lossless)]
348const fn encode_exact(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
349 let alphabet = settings.alphabet().as_array();
350 let mut read = 0;
351 let mut write = 0;
352 while input.len() - read >= 3 {
353 output[write] = alphabet[(input[read] >> 2) as usize];
354 output[write + 1] = alphabet[(((input[read] & 3) << 4) | (input[read + 1] >> 4)) as usize];
355 output[write + 2] =
356 alphabet[(((input[read + 1] & 15) << 2) | (input[read + 2] >> 6)) as usize];
357 output[write + 3] = alphabet[(input[read + 2] & 63) as usize];
358 read += 3;
359 write += 4;
360 }
361
362 let remaining = input.len() - read;
363 if remaining != 0 {
364 output[write] = alphabet[(input[read] >> 2) as usize];
365 output[write + 1] = alphabet[((input[read] & 3) << 4) as usize];
366 if remaining == 2 {
367 output[write + 1] =
368 alphabet[(((input[read] & 3) << 4) | (input[read + 1] >> 4)) as usize];
369 output[write + 2] = alphabet[((input[read + 1] & 15) << 2) as usize];
370 if matches!(settings.encode_padding(), EncodePadding::Padded) {
371 output[write + 3] = b'=';
372 }
373 } else if matches!(settings.encode_padding(), EncodePadding::Padded) {
374 output[write + 2] = b'=';
375 output[write + 3] = b'=';
376 }
377 }
378}
379
380const fn decode_exact(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
381 let mut read = 0;
382 let mut write = 0;
383 while input.len() - read >= 4 {
384 let first = decode_value(settings, input[read]);
385 let second = decode_value(settings, input[read + 1]);
386 output[write] = (first << 2) | (second >> 4);
387 write += 1;
388 if input[read + 2] != b'=' {
389 let third = decode_value(settings, input[read + 2]);
390 output[write] = (second << 4) | (third >> 2);
391 write += 1;
392 if input[read + 3] != b'=' {
393 output[write] = (third << 6) | decode_value(settings, input[read + 3]);
394 write += 1;
395 }
396 }
397 read += 4;
398 }
399
400 if read + 2 <= input.len() {
401 let first = decode_value(settings, input[read]);
402 let second = decode_value(settings, input[read + 1]);
403 output[write] = (first << 2) | (second >> 4);
404 if read + 3 == input.len() && input[read + 2] != b'=' {
405 output[write + 1] = (second << 4) | (decode_value(settings, input[read + 2]) >> 2);
406 }
407 }
408}
409
410const fn decode_value(settings: CodecSettings, byte: u8) -> u8 {
411 match settings.alphabet().decode_byte(byte) {
412 Some(value) => value,
413 None => 0,
414 }
415}