Skip to main content

base32/
base32.rs

1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(any(feature = "alloc", test))]
5extern crate alloc;
6
7#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
8mod serde;
9
10#[cfg(target_arch = "aarch64")]
11mod base32_neon;
12
13#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
14mod base32_avx2;
15
16const PAD: u8 = b'=';
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Alphabet {
20    Crockford,
21    Rfc4648,
22    Rfc4648NoPadding,
23    Rfc4648Lower,
24    Rfc4648LowerNoPadding,
25    Rfc4648Hex,
26    Rfc4648HexNoPadding,
27    Rfc4648HexLower,
28    Rfc4648HexLowerNoPadding,
29}
30
31impl Alphabet {
32    #[inline]
33    const fn is_padded(&self) -> bool {
34        match self {
35            Alphabet::Crockford => false,
36            Alphabet::Rfc4648 => true,
37            Alphabet::Rfc4648NoPadding => false,
38            Alphabet::Rfc4648Lower => true,
39            Alphabet::Rfc4648LowerNoPadding => false,
40            Alphabet::Rfc4648Hex => true,
41            Alphabet::Rfc4648HexNoPadding => false,
42            Alphabet::Rfc4648HexLower => true,
43            Alphabet::Rfc4648HexLowerNoPadding => false,
44        }
45    }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum EncodeError {
50    InvalidOutputLength,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum DecodeError {
55    InvalidInput,
56    InvalidLength,
57    InvalidPadding,
58}
59
60impl core::fmt::Display for EncodeError {
61    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62        match self {
63            Self::InvalidOutputLength => f.write_str("output buffer size is not valid"),
64        }
65    }
66}
67
68impl core::fmt::Display for DecodeError {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        match self {
71            Self::InvalidInput => f.write_str("invalid base32 character"),
72            Self::InvalidLength => f.write_str("invalid base32 length"),
73            Self::InvalidPadding => f.write_str("invalid base32 padding"),
74        }
75    }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for EncodeError {}
80
81#[cfg(feature = "std")]
82impl std::error::Error for DecodeError {}
83
84pub const fn encoded_length(bytes_len: usize, padding: bool) -> Option<usize> {
85    if bytes_len == 0 {
86        return Some(0);
87    }
88    let complete_chunks = bytes_len / 5;
89    let base = match complete_chunks.checked_mul(8) {
90        Some(v) => v,
91        None => return None,
92    };
93    let rem = bytes_len % 5;
94    if rem == 0 {
95        Some(base)
96    } else if padding {
97        base.checked_add(8)
98    } else {
99        let bits = match bytes_len.checked_mul(8) {
100            Some(v) => v,
101            None => return None,
102        };
103        match bits.checked_add(4) {
104            Some(v) => Some(v / 5),
105            None => None,
106        }
107    }
108}
109
110////////////////////////////////////////////////////////////////////////////////////////////////////
111/// Encode
112////////////////////////////////////////////////////////////////////////////////////////////////////
113
114#[cfg(feature = "alloc")]
115pub fn encode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
116    let data = data.as_ref();
117    let padding = alphabet.is_padded();
118    let len = encoded_length(data.len(), padding).expect("encoded length overflow");
119    let mut output = alloc::vec![0u8; len];
120    encode_into(&mut output, data, alphabet).expect("output buffer sized correctly");
121    unsafe { alloc::string::String::from_utf8_unchecked(output) }
122}
123
124pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
125    match encoded_length(data.len(), alphabet.is_padded()) {
126        Some(len) if len == OUT => {}
127        _ => panic!("encode_array: output array length is invalid"),
128    }
129    let mut result = [0u8; OUT];
130    match encode_into_constant_time(&mut result, data, alphabet) {
131        Ok(()) => result,
132        Err(_) => panic!("encode_array: output array length is invalid"),
133    }
134}
135
136pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
137    let padding = alphabet.is_padded();
138    let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
139    if output.len() < expected {
140        return Err(EncodeError::InvalidOutputLength);
141    }
142
143    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
144    if data.len() >= 40 {
145        return unsafe { base32_neon::encode_into(output, data, alphabet) };
146    }
147
148    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
149    if data.len() >= 40 {
150        return unsafe { base32_avx2::encode_into(output, data, alphabet) };
151    }
152
153    encode_into_constant_time(output, data, alphabet)
154}
155
156pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
157    let padding = alphabet.is_padded();
158    let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
159    if output.len() < expected {
160        return Err(EncodeError::InvalidOutputLength);
161    }
162
163    let len = data.len();
164    let mut i = 0;
165
166    while i + 40 <= len {
167        encode_8blocks(output, alphabet, data, i);
168        i += 40;
169    }
170
171    while i + 5 <= len {
172        let b0 = data[i];
173        let b1 = data[i + 1];
174        let b2 = data[i + 2];
175        let b3 = data[i + 3];
176        let b4 = data[i + 4];
177
178        let q0 = b0 >> 3;
179        let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
180        let q2 = (b1 >> 1) & 0x1F;
181        let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
182        let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
183        let q5 = (b3 >> 2) & 0x1F;
184        let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
185        let q7 = b4 & 0x1F;
186
187        let o = (i / 5) * 8;
188        output[o] = quintet_to_char(q0, alphabet);
189        output[o + 1] = quintet_to_char(q1, alphabet);
190        output[o + 2] = quintet_to_char(q2, alphabet);
191        output[o + 3] = quintet_to_char(q3, alphabet);
192        output[o + 4] = quintet_to_char(q4, alphabet);
193        output[o + 5] = quintet_to_char(q5, alphabet);
194        output[o + 6] = quintet_to_char(q6, alphabet);
195        output[o + 7] = quintet_to_char(q7, alphabet);
196
197        i += 5;
198    }
199
200    let rem = len - i;
201    if rem > 0 {
202        let o = (i / 5) * 8;
203        let b0 = data[i];
204        let q0 = b0 >> 3;
205        let q1 = (b0 & 0x07) << 2;
206        output[o] = quintet_to_char(q0, alphabet);
207        output[o + 1] = quintet_to_char(q1, alphabet);
208
209        if rem >= 2 {
210            let b1 = data[i + 1];
211            let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
212            let q2 = (b1 >> 1) & 0x1F;
213            let q3 = (b1 & 0x01) << 4;
214            output[o + 1] = quintet_to_char(q1, alphabet);
215            output[o + 2] = quintet_to_char(q2, alphabet);
216            output[o + 3] = quintet_to_char(q3, alphabet);
217
218            if rem >= 3 {
219                let b2 = data[i + 2];
220                let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
221                let q4 = (b2 & 0x0F) << 1;
222                output[o + 3] = quintet_to_char(q3, alphabet);
223                output[o + 4] = quintet_to_char(q4, alphabet);
224
225                if rem == 4 {
226                    let b3 = data[i + 3];
227                    let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
228                    let q5 = (b3 >> 2) & 0x1F;
229                    let q6 = (b3 & 0x03) << 3;
230                    output[o + 4] = quintet_to_char(q4, alphabet);
231                    output[o + 5] = quintet_to_char(q5, alphabet);
232                    output[o + 6] = quintet_to_char(q6, alphabet);
233                }
234            }
235        }
236
237        if padding {
238            let pad_start = match rem {
239                1 => o + 2,
240                2 => o + 4,
241                3 => o + 5,
242                4 => o + 7,
243                _ => unreachable!(),
244            };
245            let pad_end = o + 8;
246            let mut p = pad_start;
247            while p < pad_end {
248                output[p] = PAD;
249                p += 1;
250            }
251        }
252    }
253    Ok(())
254}
255
256/// Helper function that appends the encoded data to a `String`.
257#[cfg(feature = "alloc")]
258pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
259    let encoded_length = encoded_length(data.len(), alphabet.is_padded()).expect("output length overflow");
260    if encoded_length <= 256 {
261        // zero-alloc version for small data
262        let mut buf = [0u8; 256];
263        let mut buf = &mut buf[..encoded_length];
264        encode_into(&mut buf, data, alphabet).unwrap();
265        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
266        output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
267    } else {
268        let mut buf = alloc::vec![0u8; encoded_length];
269        encode_into(&mut buf, data, alphabet).unwrap();
270        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
271        output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
272    }
273}
274
275/// Returns 0x00 if lo <= v <= hi, 0xFF otherwise.
276/// Uses sign-bit propagation for branchless range checking.
277#[inline]
278const fn not_in_range(v: u8, lo: u8, hi: u8) -> u8 {
279    (((v.wrapping_sub(lo) as i8) | (hi.wrapping_sub(v) as i8)) >> 7) as u8
280}
281
282/// Returns 0x20 if the lower `max_pad` bits of `value` are non-zero (invalid trailing bits),
283/// 0x00 otherwise. Used for branchless non-canonical encoding rejection.
284#[inline]
285const fn check_trailing_bits(value: u8, max_pad: u8) -> u8 {
286    let mask = (1u8 << max_pad).wrapping_sub(1);
287    let pad_bits = value & mask;
288    (!not_in_range(pad_bits, 1, mask)) & 0x20
289}
290
291#[inline]
292const fn encode_8blocks(output: &mut [u8], alphabet: Alphabet, data: &[u8], start: usize) {
293    let mut n = 0;
294    while n < 8 {
295        let i = start + n * 5;
296        let b0 = data[i];
297        let b1 = data[i + 1];
298        let b2 = data[i + 2];
299        let b3 = data[i + 3];
300        let b4 = data[i + 4];
301
302        let q0 = b0 >> 3;
303        let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
304        let q2 = (b1 >> 1) & 0x1F;
305        let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
306        let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
307        let q5 = (b3 >> 2) & 0x1F;
308        let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
309        let q7 = b4 & 0x1F;
310
311        let o = (start / 5) * 8 + n * 8;
312        output[o] = quintet_to_char(q0, alphabet);
313        output[o + 1] = quintet_to_char(q1, alphabet);
314        output[o + 2] = quintet_to_char(q2, alphabet);
315        output[o + 3] = quintet_to_char(q3, alphabet);
316        output[o + 4] = quintet_to_char(q4, alphabet);
317        output[o + 5] = quintet_to_char(q5, alphabet);
318        output[o + 6] = quintet_to_char(q6, alphabet);
319        output[o + 7] = quintet_to_char(q7, alphabet);
320
321        n += 1;
322    }
323}
324
325/// Constant-time mapping: 5-bit value (0-31) to base32 character.
326/// No secret-dependent branches or memory accesses.
327#[inline]
328const fn quintet_to_char(v: u8, alphabet: Alphabet) -> u8 {
329    match alphabet {
330        Alphabet::Crockford => quintet_to_crockford(v),
331        Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
332            let not_upper = not_in_range(v, 0, 25);
333            let not_digit = not_in_range(v, 26, 31);
334            (v + b'A') & !not_upper | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
335        }
336        Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
337            let not_lower = not_in_range(v, 0, 25);
338            let not_digit = not_in_range(v, 26, 31);
339            (v + b'a') & !not_lower | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
340        }
341        Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
342            let not_digit = not_in_range(v, 0, 9);
343            let not_upper = not_in_range(v, 10, 31);
344            (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'A')) & !not_upper
345        }
346        Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
347            let not_digit = not_in_range(v, 0, 9);
348            let not_lower = not_in_range(v, 10, 31);
349            (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'a')) & !not_lower
350        }
351    }
352}
353
354/// Crockford quintet-to-character: 6 non-contiguous ranges.
355#[inline]
356const fn quintet_to_crockford(v: u8) -> u8 {
357    let not_0_9 = not_in_range(v, 0, 9);
358    let not_10_17 = not_in_range(v, 10, 17);
359    let not_18_19 = not_in_range(v, 18, 19);
360    let not_20_21 = not_in_range(v, 20, 21);
361    let not_22_26 = not_in_range(v, 22, 26);
362    let not_27_31 = not_in_range(v, 27, 31);
363    (v + b'0') & !not_0_9
364        | (v + 55) & !not_10_17
365        | (v + 56) & !not_18_19
366        | (v + 57) & !not_20_21
367        | (v + 58) & !not_22_26
368        | (v + 59) & !not_27_31
369}
370
371////////////////////////////////////////////////////////////////////////////////////////////////////
372/// Decode
373////////////////////////////////////////////////////////////////////////////////////////////////////
374
375#[inline]
376const fn decoded_length(encoded_content_len: usize) -> Result<usize, DecodeError> {
377    let full_blocks = encoded_content_len / 8;
378    let rem = encoded_content_len % 8;
379
380    let base = full_blocks * 5;
381
382    match rem {
383        0 => Ok(base),
384        2 => Ok(base + 1),
385        4 => Ok(base + 2),
386        5 => Ok(base + 3),
387        7 => Ok(base + 4),
388        _ => Err(DecodeError::InvalidLength),
389    }
390}
391
392#[cfg(feature = "alloc")]
393pub fn decode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> Result<alloc::vec::Vec<u8>, DecodeError> {
394    let data = data.as_ref();
395    let padding = alphabet.is_padded();
396    let (content_len, _) = strip_padding_info(data, padding)?;
397    let output_len = decoded_length(content_len)?;
398    let mut output = alloc::vec![0u8; output_len];
399    decode_into(&mut output, data, alphabet)?;
400    Ok(output)
401}
402
403pub const fn decode_array<const OUT: usize>(encoded_data: &[u8], alphabet: Alphabet) -> Result<[u8; OUT], DecodeError> {
404    let mut result = [0u8; OUT];
405    match decode_into_constant_time(&mut result, encoded_data, alphabet) {
406        Ok(()) => Ok(result),
407        Err(err) => Err(err),
408    }
409}
410
411pub fn decode_into(output: &mut [u8], encoded_data: &[u8], alphabet: Alphabet) -> Result<(), DecodeError> {
412    let padding = alphabet.is_padded();
413    let (content_len, _) = strip_padding_info(encoded_data, padding)?;
414    let computed_output = decoded_length(content_len)?;
415    if output.len() < computed_output {
416        return Err(DecodeError::InvalidLength);
417    }
418
419    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
420    if content_len >= 64 {
421        let content = &encoded_data[..content_len];
422        return unsafe { base32_neon::decode_into(output, content, alphabet) };
423    }
424
425    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
426    if content_len >= 32 {
427        let content = &encoded_data[..content_len];
428        return unsafe { base32_avx2::decode_into(output, content, alphabet) };
429    }
430
431    decode_into_constant_time(output, encoded_data, alphabet)
432}
433
434pub const fn decode_into_constant_time(
435    output: &mut [u8],
436    encoded_data: &[u8],
437    alphabet: Alphabet,
438) -> Result<(), DecodeError> {
439    let in_len = encoded_data.len();
440    let padding = alphabet.is_padded();
441
442    if in_len == 0 {
443        return Ok(());
444    }
445
446    let (content_len, _padding_len) = match strip_padding_info(encoded_data, padding) {
447        Ok(info) => info,
448        Err(e) => return Err(e),
449    };
450
451    if content_len == 0 {
452        return Ok(());
453    }
454
455    let computed_output = match decoded_length(content_len) {
456        Ok(len) => len,
457        Err(e) => return Err(e),
458    };
459
460    if output.len() < computed_output {
461        return Err(DecodeError::InvalidLength);
462    }
463
464    let mut err: u8 = 0;
465    let mut i = 0;
466    let mut o = 0;
467
468    while i + 64 <= content_len {
469        decode_8quads(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
470    }
471
472    while i + 8 <= content_len {
473        decode_1quad(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
474    }
475
476    if i < content_len {
477        let remaining = content_len - i;
478        match remaining {
479            2 => {
480                let v0 = char_to_quintet(encoded_data[i], alphabet);
481                let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
482                err |= v0 | v1;
483                err |= check_trailing_bits(v1, 2);
484                output[o] = (v0 << 3) | (v1 >> 2);
485            }
486            4 => {
487                let v0 = char_to_quintet(encoded_data[i], alphabet);
488                let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
489                let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
490                let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
491                err |= v0 | v1 | v2 | v3;
492                err |= check_trailing_bits(v3, 4);
493                output[o] = (v0 << 3) | (v1 >> 2);
494                output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
495            }
496            5 => {
497                let v0 = char_to_quintet(encoded_data[i], alphabet);
498                let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
499                let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
500                let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
501                let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
502                err |= v0 | v1 | v2 | v3 | v4;
503                err |= check_trailing_bits(v4, 1);
504                output[o] = (v0 << 3) | (v1 >> 2);
505                output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
506                output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
507            }
508            7 => {
509                let v0 = char_to_quintet(encoded_data[i], alphabet);
510                let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
511                let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
512                let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
513                let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
514                let v5 = char_to_quintet(encoded_data[i + 5], alphabet);
515                let v6 = char_to_quintet(encoded_data[i + 6], alphabet);
516                err |= v0 | v1 | v2 | v3 | v4 | v5 | v6;
517                err |= check_trailing_bits(v6, 3);
518                output[o] = (v0 << 3) | (v1 >> 2);
519                output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
520                output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
521                output[o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
522            }
523            _ => return Err(DecodeError::InvalidLength),
524        }
525    }
526
527    if err >= 32 {
528        return Err(DecodeError::InvalidInput);
529    }
530
531    Ok(())
532}
533
534#[inline]
535const fn decode_1quad(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
536    let v0 = char_to_quintet(data[*i], alphabet);
537    let v1 = char_to_quintet(data[*i + 1], alphabet);
538    let v2 = char_to_quintet(data[*i + 2], alphabet);
539    let v3 = char_to_quintet(data[*i + 3], alphabet);
540    let v4 = char_to_quintet(data[*i + 4], alphabet);
541    let v5 = char_to_quintet(data[*i + 5], alphabet);
542    let v6 = char_to_quintet(data[*i + 6], alphabet);
543    let v7 = char_to_quintet(data[*i + 7], alphabet);
544    *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
545    output[*o] = (v0 << 3) | (v1 >> 2);
546    output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
547    output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
548    output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
549    output[*o + 4] = (v6.wrapping_shl(5)) | v7;
550    *i += 8;
551    *o += 5;
552}
553
554#[inline]
555const fn decode_8quads(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
556    let mut n = 0;
557    while n < 8 {
558        let v0 = char_to_quintet(data[*i], alphabet);
559        let v1 = char_to_quintet(data[*i + 1], alphabet);
560        let v2 = char_to_quintet(data[*i + 2], alphabet);
561        let v3 = char_to_quintet(data[*i + 3], alphabet);
562        let v4 = char_to_quintet(data[*i + 4], alphabet);
563        let v5 = char_to_quintet(data[*i + 5], alphabet);
564        let v6 = char_to_quintet(data[*i + 6], alphabet);
565        let v7 = char_to_quintet(data[*i + 7], alphabet);
566        *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
567        output[*o] = (v0 << 3) | (v1 >> 2);
568        output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
569        output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
570        output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
571        output[*o + 4] = (v6.wrapping_shl(5)) | v7;
572        *i += 8;
573        *o += 5;
574        n += 1;
575    }
576}
577
578#[inline]
579const fn strip_padding_info(data: &[u8], expect_padding: bool) -> Result<(usize, usize), DecodeError> {
580    let in_len = data.len();
581
582    if expect_padding {
583        if in_len == 0 {
584            return Ok((0, 0));
585        }
586
587        let count = count_trailing_padding(data);
588        let content_len = in_len - count;
589
590        let err = (count > 0 && in_len % 8 != 0)
591            || count > 6
592            || (count > 0
593                && match count {
594                    6 => content_len % 8 != 2,
595                    4 => content_len % 8 != 4,
596                    3 => content_len % 8 != 5,
597                    1 => content_len % 8 != 7,
598                    _ => true,
599                });
600
601        if err {
602            return Err(DecodeError::InvalidPadding);
603        }
604
605        Ok((content_len, count))
606    } else {
607        if in_len > 0 && data[in_len - 1] == PAD {
608            return Err(DecodeError::InvalidPadding);
609        }
610        Ok((in_len, 0))
611    }
612}
613
614/// Count trailing `=` padding characters in constant time.
615/// Scans at most 7 bytes from the end (max valid padding is 6).
616/// The loop always runs exactly `min(len, 7)` iterations.
617const fn count_trailing_padding(data: &[u8]) -> usize {
618    let len = data.len();
619    if len == 0 {
620        return 0;
621    }
622    let max_check = if len < 7 { len } else { 7 };
623    let mut count: usize = 0;
624    let mut all_pad: u8 = 0xFF;
625
626    let mut k = 0;
627    while k < max_check {
628        let idx = len - 1 - k;
629        let is_pad = if data[idx] == PAD { 0xFFu8 } else { 0x00u8 };
630        all_pad = all_pad & is_pad;
631        let all_pad_ext = (all_pad as i8 >> 7) as usize;
632        count = ((k + 1) as usize) & all_pad_ext | count & !all_pad_ext;
633        k += 1;
634    }
635
636    count
637}
638
639/// Constant-time mapping: base32 character to 5-bit value.
640/// Valid characters return 0-31. Invalid characters return a value with bit 5 set (>= 32).
641#[inline]
642const fn char_to_quintet(c: u8, alphabet: Alphabet) -> u8 {
643    match alphabet {
644        Alphabet::Crockford => crockford_to_quintet(c),
645        Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
646            let not_upper = not_in_range(c, b'A', b'Z');
647            let not_digit = not_in_range(c, b'2', b'7');
648            let value = (c.wrapping_sub(b'A')) & !not_upper | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
649            let invalid = not_upper & not_digit;
650            value | (invalid & 0x20)
651        }
652        Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
653            let not_lower = not_in_range(c, b'a', b'z');
654            let not_digit = not_in_range(c, b'2', b'7');
655            let value = (c.wrapping_sub(b'a')) & !not_lower | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
656            let invalid = not_lower & not_digit;
657            value | (invalid & 0x20)
658        }
659        Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
660            let not_digit = not_in_range(c, b'0', b'9');
661            let not_upper = not_in_range(c, b'A', b'V');
662            let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_upper;
663            let invalid = not_digit & not_upper;
664            value | (invalid & 0x20)
665        }
666        Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
667            let not_digit = not_in_range(c, b'0', b'9');
668            let not_lower = not_in_range(c, b'a', b'v');
669            let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'a').wrapping_add(10)) & !not_lower;
670            let invalid = not_digit & not_lower;
671            value | (invalid & 0x20)
672        }
673    }
674}
675
676/// Crockford character-to-quintet: 6 non-contiguous ranges.
677#[inline]
678const fn crockford_to_quintet(c: u8) -> u8 {
679    let not_0_9 = not_in_range(c, b'0', b'9');
680    let not_a_h = not_in_range(c, b'A', b'H');
681    let not_j_k = not_in_range(c, b'J', b'K');
682    let not_m_n = not_in_range(c, b'M', b'N');
683    let not_p_t = not_in_range(c, b'P', b'T');
684    let not_v_z = not_in_range(c, b'V', b'Z');
685    let value = (c.wrapping_sub(b'0')) & !not_0_9
686        | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_a_h
687        | (c.wrapping_sub(b'J').wrapping_add(18)) & !not_j_k
688        | (c.wrapping_sub(b'M').wrapping_add(20)) & !not_m_n
689        | (c.wrapping_sub(b'P').wrapping_add(22)) & !not_p_t
690        | (c.wrapping_sub(b'V').wrapping_add(27)) & !not_v_z;
691    let invalid = not_0_9 & not_a_h & not_j_k & not_m_n & not_p_t & not_v_z;
692    value | (invalid & 0x20)
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698
699    // (input_bytes, alphabet, expected_encoded_str, description)
700    const ENCODE_VECTORS: &[(&[u8], Alphabet, &str, &str)] = &[
701        (b"", Alphabet::Rfc4648, "", "RFC4648 padded: empty"),
702        (b"", Alphabet::Rfc4648NoPadding, "", "RFC4648 unpadded: empty"),
703        (b"\x00", Alphabet::Rfc4648, "AA======", "RFC4648 padded: 0x00"),
704        (b"\xFF", Alphabet::Rfc4648, "74======", "RFC4648 padded: 0xFF"),
705        (b"\xAB", Alphabet::Rfc4648, "VM======", "RFC4648 padded: 0xAB"),
706        (b"fo", Alphabet::Rfc4648, "MZXQ====", "RFC4648 padded: 'fo'"),
707        (b"foo", Alphabet::Rfc4648, "MZXW6===", "RFC4648 padded: 'foo'"),
708        (b"foob", Alphabet::Rfc4648, "MZXW6YQ=", "RFC4648 padded: 'foob'"),
709        (b"fooba", Alphabet::Rfc4648, "MZXW6YTB", "RFC4648 padded: 'fooba'"),
710        (b"foobar", Alphabet::Rfc4648, "MZXW6YTBOI======", "RFC4648 padded: 'foobar'"),
711        (b"hello", Alphabet::Rfc4648, "NBSWY3DP", "RFC4648 padded: 'hello'"),
712        (b"hello", Alphabet::Rfc4648NoPadding, "NBSWY3DP", "RFC4648 unpadded: 'hello'"),
713        (b"h", Alphabet::Rfc4648NoPadding, "NA", "RFC4648 unpadded: 'h'"),
714        (b"he", Alphabet::Rfc4648NoPadding, "NBSQ", "RFC4648 unpadded: 'he'"),
715        (b"hel", Alphabet::Rfc4648NoPadding, "NBSWY", "RFC4648 unpadded: 'hel'"),
716        (b"hell", Alphabet::Rfc4648NoPadding, "NBSWY3A", "RFC4648 unpadded: 'hell'"),
717        (b"hello", Alphabet::Rfc4648Lower, "nbswy3dp", "RFC4648 lower: 'hello'"),
718        (b"hello", Alphabet::Rfc4648Hex, "D1IMOR3F", "RFC4648 hex: 'hello'"),
719        (b"hello", Alphabet::Rfc4648HexLower, "d1imor3f", "RFC4648 hex lower: 'hello'"),
720        (b"hello", Alphabet::Crockford, "D1JPRV3F", "Crockford: 'hello'"),
721        // RFC 4648 Section 10 hex test vectors
722        (b"f", Alphabet::Rfc4648Hex, "CO======", "RFC4648 hex: 'f'"),
723        (b"fo", Alphabet::Rfc4648Hex, "CPNG====", "RFC4648 hex: 'fo'"),
724        (b"foo", Alphabet::Rfc4648Hex, "CPNMU===", "RFC4648 hex: 'foo'"),
725        (b"foob", Alphabet::Rfc4648Hex, "CPNMUOG=", "RFC4648 hex: 'foob'"),
726        (b"fooba", Alphabet::Rfc4648Hex, "CPNMUOJ1", "RFC4648 hex: 'fooba'"),
727        (b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "RFC4648 hex: 'foobar'"),
728    ];
729
730    // (encoded_str, alphabet, expected_bytes, description)
731    const DECODE_VECTORS: &[(&[u8], Alphabet, &[u8], &str)] = &[
732        (b"", Alphabet::Rfc4648, b"", "RFC4648 padded: empty"),
733        (b"AA======", Alphabet::Rfc4648, b"\x00", "RFC4648 padded: 0x00"),
734        (b"AE======", Alphabet::Rfc4648, b"\x01", "RFC4648 padded: 0x01"),
735        (b"MZXQ====", Alphabet::Rfc4648, b"fo", "RFC4648 padded: 'fo'"),
736        (b"MZXW6===", Alphabet::Rfc4648, b"foo", "RFC4648 padded: 'foo'"),
737        (b"MZXW6YQ=", Alphabet::Rfc4648, b"foob", "RFC4648 padded: 'foob'"),
738        (b"MZXW6YTB", Alphabet::Rfc4648, b"fooba", "RFC4648 padded: 'fooba'"),
739        (b"NA", Alphabet::Rfc4648NoPadding, b"h", "RFC4648 unpadded: 'h'"),
740        (b"NBSQ", Alphabet::Rfc4648NoPadding, b"he", "RFC4648 unpadded: 'he'"),
741        (b"NBSWY", Alphabet::Rfc4648NoPadding, b"hel", "RFC4648 unpadded: 'hel'"),
742        (b"NBSWY3A", Alphabet::Rfc4648NoPadding, b"hell", "RFC4648 unpadded: 'hell'"),
743        (b"nbswy3dp", Alphabet::Rfc4648Lower, b"hello", "RFC4648 lower: 'hello'"),
744        (b"D1IMOR3F", Alphabet::Rfc4648Hex, b"hello", "RFC4648 hex: 'hello'"),
745        (b"D1JPRV3F", Alphabet::Crockford, b"hello", "Crockford: 'hello'"),
746    ];
747
748    // (encoded_str, alphabet, expected_error, description)
749    const DECODE_ERROR_VECTORS: &[(&[u8], Alphabet, DecodeError, &str)] = &[
750        (b"!!!!====", Alphabet::Rfc4648, DecodeError::InvalidInput, "4 invalid chars"),
751        (
752            b"AAA=====",
753            Alphabet::Rfc4648,
754            DecodeError::InvalidPadding,
755            "wrong padding position",
756        ),
757        (
758            b"AA==========",
759            Alphabet::Rfc4648,
760            DecodeError::InvalidPadding,
761            "too many padding chars",
762        ),
763        (
764            b"AA======",
765            Alphabet::Rfc4648NoPadding,
766            DecodeError::InvalidPadding,
767            "no-pad rejects padding",
768        ),
769        (b"A", Alphabet::Rfc4648, DecodeError::InvalidLength, "single char"),
770        (
771            b"D1JPRV!!",
772            Alphabet::Crockford,
773            DecodeError::InvalidInput,
774            "Crockford invalid chars",
775        ),
776    ];
777
778    // (data_length, padding, expected_result, description)
779    const ENCODED_LENGTH_VECTORS: &[(usize, bool, Option<usize>, &str)] = &[
780        (0, true, Some(0), "empty padded"),
781        (1, true, Some(8), "1 byte padded"),
782        (5, true, Some(8), "5 bytes padded"),
783        (6, true, Some(16), "6 bytes padded"),
784        (10, true, Some(16), "10 bytes padded"),
785        (0, false, Some(0), "empty unpadded"),
786        (1, false, Some(2), "1 byte unpadded"),
787        (5, false, Some(8), "5 bytes unpadded"),
788        (6, false, Some(10), "6 bytes unpadded"),
789    ];
790
791    // (initial_string, input_bytes, alphabet, expected_output, description)
792    const ENCODE_INTO_STRING_VECTORS: &[(&str, &[u8], Alphabet, &str, &str)] = &[
793        ("", b"", Alphabet::Rfc4648, "", "empty"),
794        ("prefix", b"", Alphabet::Rfc4648, "prefix", "empty data with prefix"),
795        (
796            "",
797            b"hello world",
798            Alphabet::Rfc4648,
799            "NBSWY3DPEB3W64TMMQ======",
800            "hello world padded",
801        ),
802        (
803            "",
804            b"hello world",
805            Alphabet::Rfc4648NoPadding,
806            "NBSWY3DPEB3W64TMMQ",
807            "hello world unpadded",
808        ),
809        (
810            "data: ",
811            b"hello world",
812            Alphabet::Rfc4648,
813            "data: NBSWY3DPEB3W64TMMQ======",
814            "append to prefix",
815        ),
816        ("", b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "hex alphabet"),
817    ];
818
819    const ALL_ALPHABETS: &[Alphabet] = &[
820        Alphabet::Rfc4648,
821        Alphabet::Rfc4648NoPadding,
822        Alphabet::Rfc4648Lower,
823        Alphabet::Rfc4648Hex,
824        Alphabet::Rfc4648HexLower,
825        Alphabet::Crockford,
826    ];
827
828    const ROUNDTRIP_SIZES: &[usize] = &[
829        0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129,
830    ];
831
832    const SIMD_BOUNDARY_SIZES: &[usize] = &[38, 39, 40, 41, 42, 45, 50, 62, 63, 64, 65, 66, 84, 85, 100];
833
834    #[test]
835    fn test_encode() {
836        for &(input, alphabet, expected, desc) in ENCODE_VECTORS {
837            let result = encode(input, alphabet);
838            assert_eq!(result, expected, "encode: {desc}");
839        }
840    }
841
842    #[test]
843    fn test_decode() {
844        for &(encoded, alphabet, expected, desc) in DECODE_VECTORS {
845            let result = decode(encoded, alphabet).unwrap();
846            assert_eq!(&result, expected, "decode: {desc}");
847        }
848    }
849
850    #[test]
851    fn test_decode_error() {
852        for &(encoded, alphabet, expected_err, desc) in DECODE_ERROR_VECTORS {
853            let result = decode(encoded, alphabet);
854            assert_eq!(result, Err(expected_err), "decode error: {desc}");
855        }
856
857        // Large buffer with trailing invalid char
858        let mut data = alloc::vec![b'A'; 256];
859        data[255] = b'!';
860        assert_eq!(decode(&data, Alphabet::Rfc4648), Err(DecodeError::InvalidInput));
861    }
862
863    #[test]
864    fn test_encoded_length() {
865        for &(data_len, padding, expected, desc) in ENCODED_LENGTH_VECTORS {
866            let result = encoded_length(data_len, padding);
867            assert_eq!(result, expected, "encoded_length: {desc}");
868        }
869    }
870
871    #[test]
872    fn test_encode_into_string() {
873        for &(initial, input, alphabet, expected, desc) in ENCODE_INTO_STRING_VECTORS {
874            let mut s = alloc::string::String::from(initial);
875            encode_into_string(&mut s, input, alphabet);
876            assert_eq!(s, expected, "encode_into_string: {desc}");
877        }
878
879        // Multi-append
880        let mut s = alloc::string::String::from("~~");
881        encode_into_string(&mut s, b"hello", Alphabet::Rfc4648);
882        assert_eq!(s, "~~NBSWY3DP");
883        encode_into_string(&mut s, b"foo", Alphabet::Rfc4648);
884        assert_eq!(s, "~~NBSWY3DPMZXW6===");
885
886        // All alphabets
887        for alphabet in ALL_ALPHABETS {
888            let expected = encode(b"hello world", *alphabet);
889            let mut s = alloc::string::String::new();
890            encode_into_string(&mut s, b"hello world", *alphabet);
891            assert_eq!(s, expected, "encode_into_string alphabet {alphabet:?}");
892        }
893    }
894
895    #[test]
896    fn test_roundtrip() {
897        for &len in ROUNDTRIP_SIZES {
898            let data: Vec<u8> = (0..len as u8).collect();
899            for alphabet in ALL_ALPHABETS {
900                let encoded = encode(&data, *alphabet);
901                let decoded = decode(encoded.as_bytes(), *alphabet).unwrap();
902                assert_eq!(decoded, data, "roundtrip len={len} alphabet={alphabet:?}");
903            }
904        }
905    }
906
907    #[test]
908    fn test_roundtrip_large() {
909        let size = 4096;
910
911        let data = alloc::vec![0x00u8; size];
912        let elen = encoded_length(size, true).expect("encoded_len overflow");
913        let mut encoded = alloc::vec![0u8; elen];
914        encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
915        let mut decoded = alloc::vec![0u8; size];
916        decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
917        assert_eq!(decoded, data, "4096 zeroes constant-time");
918
919        let data = alloc::vec![0xFFu8; size];
920        let mut encoded = alloc::vec![0u8; elen];
921        encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
922        decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
923        assert_eq!(decoded, data, "4096 0xFF constant-time");
924
925        let data: Vec<u8> = (0..=255).cycle().take(size).collect();
926        let encoded = encode(&data, Alphabet::Rfc4648);
927        let decoded = decode(encoded.as_bytes(), Alphabet::Rfc4648).unwrap();
928        assert_eq!(decoded, data, "4096 cycle dispatch");
929
930        let data: Vec<u8> = (0..=255).collect();
931        let mut s = alloc::string::String::new();
932        encode_into_string(&mut s, &data, Alphabet::Rfc4648);
933        let decoded = decode(s.as_bytes(), Alphabet::Rfc4648).unwrap();
934        assert_eq!(decoded, data, "256-byte encode_into_string roundtrip");
935
936        let data: Vec<u8> = (0..255).cycle().take(4096).collect();
937        let expected = encode(&data, Alphabet::Rfc4648);
938        let mut s = alloc::string::String::new();
939        encode_into_string(&mut s, &data, Alphabet::Rfc4648);
940        assert_eq!(s, expected, "4096-byte encode_into_string");
941    }
942
943    #[test]
944    fn test_encode_all_single_bytes() {
945        for byte in 0..=255u8 {
946            for alphabet in &[
947                Alphabet::Rfc4648,
948                Alphabet::Rfc4648Lower,
949                Alphabet::Rfc4648Hex,
950                Alphabet::Rfc4648HexLower,
951                Alphabet::Crockford,
952            ] {
953                let padding = alphabet.is_padded();
954                let elen = encoded_length(1, padding).unwrap();
955                let mut encoded = alloc::vec![0u8; elen];
956                encode_into_constant_time(&mut encoded, &[byte], *alphabet).unwrap();
957                let mut decoded = [0u8; 1];
958                decode_into_constant_time(&mut decoded, &encoded, *alphabet).unwrap();
959                assert_eq!(decoded[0], byte, "single byte roundtrip {byte:#04x} alphabet={alphabet:?}");
960            }
961        }
962    }
963
964    #[test]
965    fn test_decode_invalid_char_every_position() {
966        let mut out = [0u8; 128];
967
968        // 8 chars (1 full quad), invalid at positions 0..7
969        for pos in 0..8 {
970            let mut input = [b'A'; 8];
971            input[pos] = b'!';
972            assert_eq!(
973                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
974                Err(DecodeError::InvalidInput),
975                "invalid char at position {pos} in 8-char input"
976            );
977        }
978
979        // 64 chars (8 quads), invalid at positions 0..63
980        for pos in 0..64 {
981            let mut input = [b'A'; 64];
982            input[pos] = b'!';
983            assert_eq!(
984                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
985                Err(DecodeError::InvalidInput),
986                "invalid char at position {pos} in 64-char input"
987            );
988        }
989
990        // 72 chars (8 quads + 1 more quad), invalid at positions 0..71
991        for pos in 0..72 {
992            let mut input = [b'A'; 72];
993            input[pos] = b'!';
994            assert_eq!(
995                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
996                Err(DecodeError::InvalidInput),
997                "invalid char at position {pos} in 72-char input"
998            );
999        }
1000
1001        // Lower, hex, hex_lower, crockford alphabets
1002        for pos in 0..8 {
1003            let mut input = [b'a'; 8];
1004            input[pos] = b'!';
1005            assert_eq!(
1006                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Lower),
1007                Err(DecodeError::InvalidInput)
1008            );
1009        }
1010        for pos in 0..8 {
1011            let mut input = [b'0'; 8];
1012            input[pos] = b'!';
1013            assert_eq!(
1014                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Hex),
1015                Err(DecodeError::InvalidInput)
1016            );
1017        }
1018        for pos in 0..8 {
1019            let mut input = [b'0'; 8];
1020            input[pos] = b'!';
1021            assert_eq!(
1022                decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648HexLower),
1023                Err(DecodeError::InvalidInput)
1024            );
1025        }
1026        for pos in 0..8 {
1027            let mut input = [b'0'; 8];
1028            input[pos] = b'!';
1029            assert_eq!(
1030                decode_into_constant_time(&mut out, &input, Alphabet::Crockford),
1031                Err(DecodeError::InvalidInput)
1032            );
1033        }
1034    }
1035
1036    #[test]
1037    fn test_decode_non_canonical_trailing_bits() {
1038        let mut out = [0u8; 8];
1039
1040        // remaining == 2: bottom 2 bits of v1 must be zero
1041        for &(input, expected) in &[
1042            (b"AA======" as &[u8], Ok(())),
1043            (b"AB======" as &[u8], Err(DecodeError::InvalidInput)),
1044            (b"AC======" as &[u8], Err(DecodeError::InvalidInput)),
1045            (b"AD======" as &[u8], Err(DecodeError::InvalidInput)),
1046        ] {
1047            assert_eq!(
1048                decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1049                expected,
1050                "non-canonical (rem=2): {:?}",
1051                core::str::from_utf8(input)
1052            );
1053        }
1054
1055        // remaining == 4: bottom 4 bits of v3 must be zero
1056        for &(input, expected) in &[
1057            (b"MZXQ====" as &[u8], Ok(())),
1058            (b"MZXR====" as &[u8], Err(DecodeError::InvalidInput)),
1059        ] {
1060            assert_eq!(
1061                decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1062                expected,
1063                "non-canonical (rem=4): {:?}",
1064                core::str::from_utf8(input)
1065            );
1066        }
1067
1068        // remaining == 5: bottom 1 bit of v4 must be zero
1069        for &(input, expected) in &[
1070            (b"MZXW6===" as &[u8], Ok(())),
1071            (b"MZXW7===" as &[u8], Err(DecodeError::InvalidInput)),
1072        ] {
1073            assert_eq!(
1074                decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1075                expected,
1076                "non-canonical (rem=5): {:?}",
1077                core::str::from_utf8(input)
1078            );
1079        }
1080
1081        // remaining == 7: bottom 3 bits of v6 must be zero
1082        assert_eq!(decode_into_constant_time(&mut out, b"NBSWY3DP", Alphabet::Rfc4648), Ok(()));
1083    }
1084
1085    #[test]
1086    fn test_decode_rejects_interior_padding() {
1087        let mut out = [0u8; 8];
1088        assert_eq!(
1089            decode_into_constant_time(&mut out, b"=AAA====", Alphabet::Rfc4648),
1090            Err(DecodeError::InvalidInput)
1091        );
1092        assert_eq!(
1093            decode_into_constant_time(&mut out, b"A=AA====", Alphabet::Rfc4648),
1094            Err(DecodeError::InvalidInput)
1095        );
1096        assert_eq!(
1097            decode_into_constant_time(&mut out, b"AA=A====", Alphabet::Rfc4648),
1098            Err(DecodeError::InvalidInput)
1099        );
1100        assert_eq!(
1101            decode_into_constant_time(&mut out, b"AAA=====", Alphabet::Rfc4648),
1102            Err(DecodeError::InvalidPadding)
1103        );
1104    }
1105
1106    #[test]
1107    fn test_roundtrip_simd_boundary_sizes() {
1108        let mut data_buf = Vec::new();
1109        let mut enc_buf = Vec::new();
1110
1111        for &input_len in SIMD_BOUNDARY_SIZES {
1112            data_buf.clear();
1113            for b in 0..input_len {
1114                data_buf.push(b as u8);
1115            }
1116
1117            for alphabet in &[Alphabet::Rfc4648, Alphabet::Rfc4648NoPadding] {
1118                let padding = alphabet.is_padded();
1119                let elen = encoded_length(input_len, padding).expect("encoded_len overflow");
1120                enc_buf.resize(elen, 0);
1121                encode_into_constant_time(&mut enc_buf, &data_buf, *alphabet).unwrap();
1122
1123                let mut decoded = alloc::vec![0u8; input_len];
1124                assert_eq!(
1125                    decode_into_constant_time(&mut decoded, &enc_buf, *alphabet),
1126                    Ok(()),
1127                    "decode failed len={input_len} alphabet={alphabet:?}"
1128                );
1129                assert_eq!(&decoded, &data_buf, "roundtrip mismatch len={input_len} alphabet={alphabet:?}");
1130            }
1131        }
1132    }
1133
1134    #[test]
1135    fn test_const_encode() {
1136        const RESULT: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Rfc4648);
1137        assert_eq!(&RESULT, b"NBSWY3DP");
1138
1139        const RESULT_EMPTY: [u8; 0] = encode_array::<0>(b"", Alphabet::Rfc4648);
1140        assert_eq!(RESULT_EMPTY.len(), 0);
1141
1142        const RESULT_CROCKFORD: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Crockford);
1143        assert_eq!(&RESULT_CROCKFORD, b"D1JPRV3F");
1144    }
1145
1146    #[test]
1147    fn test_const_decode() {
1148        const RESULT: Result<[u8; 5], DecodeError> = decode_array::<5>(b"NBSWY3DP", Alphabet::Rfc4648);
1149        assert_eq!(RESULT.unwrap(), *b"hello");
1150
1151        const RESULT_EMPTY: Result<[u8; 0], DecodeError> = decode_array::<0>(b"", Alphabet::Rfc4648);
1152        assert_eq!(RESULT_EMPTY.unwrap().len(), 0);
1153    }
1154
1155    #[test]
1156    fn test_const_decode_error() {
1157        const ERR_INVALID: Result<[u8; 5], DecodeError> = decode_array::<5>(b"D1JPRV!!", Alphabet::Crockford);
1158        assert_eq!(ERR_INVALID, Err(DecodeError::InvalidInput));
1159    }
1160
1161    #[test]
1162    fn test_buffer_management() {
1163        let mut out = [0u8; 1];
1164        assert_eq!(
1165            encode_into(&mut out, b"hello", Alphabet::Rfc4648),
1166            Err(EncodeError::InvalidOutputLength)
1167        );
1168
1169        let mut out = [0u8; 5];
1170        decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648).unwrap();
1171        assert_eq!(&out, b"hello");
1172
1173        let mut out = [0u8; 1];
1174        assert_eq!(
1175            decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648),
1176            Err(DecodeError::InvalidLength)
1177        );
1178
1179        // Exact output size decode
1180        let mut remainders = [0u8; 80];
1181        let mut output = [0u8; 80];
1182        for len in 1..=80 {
1183            for i in 0..len {
1184                remainders[i] = (i * 7 + 3) as u8;
1185            }
1186            let encoded = encode(&remainders[..len], Alphabet::Rfc4648);
1187            let expected_output_len = len;
1188            let r = decode_into(&mut output[..expected_output_len], encoded.as_bytes(), Alphabet::Rfc4648);
1189            assert!(r.is_ok(), "decode_into failed at len {}", len);
1190            assert_eq!(&output[..expected_output_len], &remainders[..len], "mismatch at len {}", len);
1191        }
1192    }
1193
1194    #[test]
1195    fn test_display_error() {
1196        assert_eq!(format!("{}", DecodeError::InvalidInput), "invalid base32 character");
1197        assert_eq!(format!("{}", DecodeError::InvalidLength), "invalid base32 length");
1198        assert_eq!(format!("{}", DecodeError::InvalidPadding), "invalid base32 padding");
1199        assert_eq!(
1200            format!("{}", EncodeError::InvalidOutputLength),
1201            "output buffer size is not valid"
1202        );
1203    }
1204
1205    #[cfg(feature = "serde")]
1206    #[test]
1207    fn test_serde() {
1208        #[derive(::serde::Serialize, ::serde::Deserialize)]
1209        struct Data(#[serde(with = "crate::serde")] Vec<u8>);
1210
1211        let data = Data(b"hello world".to_vec());
1212        let json = ::serde_json::to_string(&data).unwrap();
1213        assert_eq!(json, "\"NBSWY3DPEB3W64TMMQ======\"");
1214        let deserialized: Data = ::serde_json::from_str(&json).unwrap();
1215        assert_eq!(deserialized.0, b"hello world");
1216    }
1217}