1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(any(feature = "alloc", test))]
55extern crate alloc;
56
57#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
58mod serde;
59
60#[cfg(target_arch = "aarch64")]
61mod base32_neon;
62
63#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
64mod base32_avx2;
65
66const PAD: u8 = b'=';
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Alphabet {
70 Crockford,
71 Rfc4648,
72 Rfc4648NoPadding,
73 Rfc4648Lower,
74 Rfc4648LowerNoPadding,
75 Rfc4648Hex,
76 Rfc4648HexNoPadding,
77 Rfc4648HexLower,
78 Rfc4648HexLowerNoPadding,
79}
80
81impl Alphabet {
82 #[inline]
83 const fn is_padded(&self) -> bool {
84 match self {
85 Alphabet::Crockford => false,
86 Alphabet::Rfc4648 => true,
87 Alphabet::Rfc4648NoPadding => false,
88 Alphabet::Rfc4648Lower => true,
89 Alphabet::Rfc4648LowerNoPadding => false,
90 Alphabet::Rfc4648Hex => true,
91 Alphabet::Rfc4648HexNoPadding => false,
92 Alphabet::Rfc4648HexLower => true,
93 Alphabet::Rfc4648HexLowerNoPadding => false,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum EncodeError {
100 InvalidOutputLength,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum DecodeError {
105 InvalidInput,
106 InvalidLength,
107 InvalidPadding,
108}
109
110impl core::fmt::Display for EncodeError {
111 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112 match self {
113 Self::InvalidOutputLength => f.write_str("output buffer size is not valid"),
114 }
115 }
116}
117
118impl core::fmt::Display for DecodeError {
119 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120 match self {
121 Self::InvalidInput => f.write_str("invalid base32 character"),
122 Self::InvalidLength => f.write_str("invalid base32 length"),
123 Self::InvalidPadding => f.write_str("invalid base32 padding"),
124 }
125 }
126}
127
128#[cfg(feature = "std")]
129impl std::error::Error for EncodeError {}
130
131#[cfg(feature = "std")]
132impl std::error::Error for DecodeError {}
133
134pub const fn encoded_length(bytes_len: usize, padding: bool) -> Option<usize> {
146 if bytes_len == 0 {
147 return Some(0);
148 }
149 let complete_chunks = bytes_len / 5;
150 let base = match complete_chunks.checked_mul(8) {
151 Some(v) => v,
152 None => return None,
153 };
154 let rem = bytes_len % 5;
155 if rem == 0 {
156 Some(base)
157 } else if padding {
158 base.checked_add(8)
159 } else {
160 let bits = match bytes_len.checked_mul(8) {
161 Some(v) => v,
162 None => return None,
163 };
164 match bits.checked_add(4) {
165 Some(v) => Some(v / 5),
166 None => None,
167 }
168 }
169}
170
171#[cfg(feature = "alloc")]
184pub fn encode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
185 let data = data.as_ref();
186 let padding = alphabet.is_padded();
187 let len = encoded_length(data.len(), padding).expect("encoded length overflow");
188 let mut output = alloc::vec![0u8; len];
189 encode_into(&mut output, data, alphabet).expect("output buffer sized correctly");
190 unsafe { alloc::string::String::from_utf8_unchecked(output) }
191}
192
193pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
206 match encoded_length(data.len(), alphabet.is_padded()) {
207 Some(len) if len == OUT => {}
208 _ => panic!("encode_array: output array length is invalid"),
209 }
210 let mut result = [0u8; OUT];
211 match encode_into_constant_time(&mut result, data, alphabet) {
212 Ok(()) => result,
213 Err(_) => panic!("encode_array: output array length is invalid"),
214 }
215}
216
217pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
237 let padding = alphabet.is_padded();
238 let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
239 if output.len() < expected {
240 return Err(EncodeError::InvalidOutputLength);
241 }
242
243 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
244 if data.len() >= 40 {
245 return unsafe { base32_neon::encode_into(output, data, alphabet) };
246 }
247
248 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
249 if data.len() >= 40 {
250 return unsafe { base32_avx2::encode_into(output, data, alphabet) };
251 }
252
253 encode_into_constant_time(output, data, alphabet)
254}
255
256pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
271 let padding = alphabet.is_padded();
272 let expected = encoded_length(data.len(), padding).expect("encoded length overflow");
273 if output.len() < expected {
274 return Err(EncodeError::InvalidOutputLength);
275 }
276
277 let len = data.len();
278 let mut i = 0;
279
280 while i + 40 <= len {
281 encode_8blocks(output, alphabet, data, i);
282 i += 40;
283 }
284
285 while i + 5 <= len {
286 let b0 = data[i];
287 let b1 = data[i + 1];
288 let b2 = data[i + 2];
289 let b3 = data[i + 3];
290 let b4 = data[i + 4];
291
292 let q0 = b0 >> 3;
293 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
294 let q2 = (b1 >> 1) & 0x1F;
295 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
296 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
297 let q5 = (b3 >> 2) & 0x1F;
298 let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
299 let q7 = b4 & 0x1F;
300
301 let o = (i / 5) * 8;
302 output[o] = quintet_to_char(q0, alphabet);
303 output[o + 1] = quintet_to_char(q1, alphabet);
304 output[o + 2] = quintet_to_char(q2, alphabet);
305 output[o + 3] = quintet_to_char(q3, alphabet);
306 output[o + 4] = quintet_to_char(q4, alphabet);
307 output[o + 5] = quintet_to_char(q5, alphabet);
308 output[o + 6] = quintet_to_char(q6, alphabet);
309 output[o + 7] = quintet_to_char(q7, alphabet);
310
311 i += 5;
312 }
313
314 let rem = len - i;
315 if rem > 0 {
316 let o = (i / 5) * 8;
317 let b0 = data[i];
318 let q0 = b0 >> 3;
319 let q1 = (b0 & 0x07) << 2;
320 output[o] = quintet_to_char(q0, alphabet);
321 output[o + 1] = quintet_to_char(q1, alphabet);
322
323 if rem >= 2 {
324 let b1 = data[i + 1];
325 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
326 let q2 = (b1 >> 1) & 0x1F;
327 let q3 = (b1 & 0x01) << 4;
328 output[o + 1] = quintet_to_char(q1, alphabet);
329 output[o + 2] = quintet_to_char(q2, alphabet);
330 output[o + 3] = quintet_to_char(q3, alphabet);
331
332 if rem >= 3 {
333 let b2 = data[i + 2];
334 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
335 let q4 = (b2 & 0x0F) << 1;
336 output[o + 3] = quintet_to_char(q3, alphabet);
337 output[o + 4] = quintet_to_char(q4, alphabet);
338
339 if rem == 4 {
340 let b3 = data[i + 3];
341 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
342 let q5 = (b3 >> 2) & 0x1F;
343 let q6 = (b3 & 0x03) << 3;
344 output[o + 4] = quintet_to_char(q4, alphabet);
345 output[o + 5] = quintet_to_char(q5, alphabet);
346 output[o + 6] = quintet_to_char(q6, alphabet);
347 }
348 }
349 }
350
351 if padding {
352 let pad_start = match rem {
353 1 => o + 2,
354 2 => o + 4,
355 3 => o + 5,
356 4 => o + 7,
357 _ => unreachable!(),
358 };
359 let pad_end = o + 8;
360 let mut p = pad_start;
361 while p < pad_end {
362 output[p] = PAD;
363 p += 1;
364 }
365 }
366 }
367 Ok(())
368}
369
370#[cfg(feature = "alloc")]
380pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
381 let encoded_length = encoded_length(data.len(), alphabet.is_padded()).expect("output length overflow");
382 if encoded_length <= 256 {
383 let mut buf = [0u8; 256];
385 let mut buf = &mut buf[..encoded_length];
386 encode_into(&mut buf, data, alphabet).unwrap();
387 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
389 } else {
390 let mut buf = alloc::vec![0u8; encoded_length];
391 encode_into(&mut buf, data, alphabet).unwrap();
392 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
394 }
395}
396
397#[inline]
400const fn not_in_range(v: u8, lo: u8, hi: u8) -> u8 {
401 (((v.wrapping_sub(lo) as i8) | (hi.wrapping_sub(v) as i8)) >> 7) as u8
402}
403
404#[inline]
407const fn check_trailing_bits(value: u8, max_pad: u8) -> u8 {
408 let mask = (1u8 << max_pad).wrapping_sub(1);
409 let pad_bits = value & mask;
410 (!not_in_range(pad_bits, 1, mask)) & 0x20
411}
412
413#[inline]
414const fn encode_8blocks(output: &mut [u8], alphabet: Alphabet, data: &[u8], start: usize) {
415 let mut n = 0;
416 while n < 8 {
417 let i = start + n * 5;
418 let b0 = data[i];
419 let b1 = data[i + 1];
420 let b2 = data[i + 2];
421 let b3 = data[i + 3];
422 let b4 = data[i + 4];
423
424 let q0 = b0 >> 3;
425 let q1 = ((b0 & 0x07) << 2) | (b1 >> 6);
426 let q2 = (b1 >> 1) & 0x1F;
427 let q3 = ((b1 & 0x01) << 4) | (b2 >> 4);
428 let q4 = ((b2 & 0x0F) << 1) | (b3 >> 7);
429 let q5 = (b3 >> 2) & 0x1F;
430 let q6 = ((b3 & 0x03) << 3) | (b4 >> 5);
431 let q7 = b4 & 0x1F;
432
433 let o = (start / 5) * 8 + n * 8;
434 output[o] = quintet_to_char(q0, alphabet);
435 output[o + 1] = quintet_to_char(q1, alphabet);
436 output[o + 2] = quintet_to_char(q2, alphabet);
437 output[o + 3] = quintet_to_char(q3, alphabet);
438 output[o + 4] = quintet_to_char(q4, alphabet);
439 output[o + 5] = quintet_to_char(q5, alphabet);
440 output[o + 6] = quintet_to_char(q6, alphabet);
441 output[o + 7] = quintet_to_char(q7, alphabet);
442
443 n += 1;
444 }
445}
446
447#[inline]
450const fn quintet_to_char(v: u8, alphabet: Alphabet) -> u8 {
451 match alphabet {
452 Alphabet::Crockford => quintet_to_crockford(v),
453 Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
454 let not_upper = not_in_range(v, 0, 25);
455 let not_digit = not_in_range(v, 26, 31);
456 (v + b'A') & !not_upper | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
457 }
458 Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
459 let not_lower = not_in_range(v, 0, 25);
460 let not_digit = not_in_range(v, 26, 31);
461 (v + b'a') & !not_lower | (v.wrapping_sub(26).wrapping_add(b'2')) & !not_digit
462 }
463 Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
464 let not_digit = not_in_range(v, 0, 9);
465 let not_upper = not_in_range(v, 10, 31);
466 (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'A')) & !not_upper
467 }
468 Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
469 let not_digit = not_in_range(v, 0, 9);
470 let not_lower = not_in_range(v, 10, 31);
471 (v + b'0') & !not_digit | (v.wrapping_sub(10).wrapping_add(b'a')) & !not_lower
472 }
473 }
474}
475
476#[inline]
478const fn quintet_to_crockford(v: u8) -> u8 {
479 let not_0_9 = not_in_range(v, 0, 9);
480 let not_10_17 = not_in_range(v, 10, 17);
481 let not_18_19 = not_in_range(v, 18, 19);
482 let not_20_21 = not_in_range(v, 20, 21);
483 let not_22_26 = not_in_range(v, 22, 26);
484 let not_27_31 = not_in_range(v, 27, 31);
485 (v + b'0') & !not_0_9
486 | (v + 55) & !not_10_17
487 | (v + 56) & !not_18_19
488 | (v + 57) & !not_20_21
489 | (v + 58) & !not_22_26
490 | (v + 59) & !not_27_31
491}
492
493#[inline]
498const fn decoded_length(encoded_content_len: usize) -> Result<usize, DecodeError> {
499 let full_blocks = encoded_content_len / 8;
500 let rem = encoded_content_len % 8;
501
502 let base = full_blocks * 5;
503
504 match rem {
505 0 => Ok(base),
506 2 => Ok(base + 1),
507 4 => Ok(base + 2),
508 5 => Ok(base + 3),
509 7 => Ok(base + 4),
510 _ => Err(DecodeError::InvalidLength),
511 }
512}
513
514#[cfg(feature = "alloc")]
528pub fn decode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> Result<alloc::vec::Vec<u8>, DecodeError> {
529 let data = data.as_ref();
530 let padding = alphabet.is_padded();
531 let (content_len, _) = strip_padding_info(data, padding)?;
532 let output_len = decoded_length(content_len)?;
533 let mut output = alloc::vec![0u8; output_len];
534 decode_into(&mut output, data, alphabet)?;
535 Ok(output)
536}
537
538pub const fn decode_array<const OUT: usize>(encoded_data: &[u8], alphabet: Alphabet) -> Result<[u8; OUT], DecodeError> {
551 let mut result = [0u8; OUT];
552 match decode_into_constant_time(&mut result, encoded_data, alphabet) {
553 Ok(()) => Ok(result),
554 Err(err) => Err(err),
555 }
556}
557
558pub fn decode_into(output: &mut [u8], encoded_data: &[u8], alphabet: Alphabet) -> Result<(), DecodeError> {
579 let padding = alphabet.is_padded();
580 let (content_len, _) = strip_padding_info(encoded_data, padding)?;
581 let computed_output = decoded_length(content_len)?;
582 if output.len() < computed_output {
583 return Err(DecodeError::InvalidLength);
584 }
585
586 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
587 if content_len >= 64 {
588 let content = &encoded_data[..content_len];
589 return unsafe { base32_neon::decode_into(output, content, alphabet) };
590 }
591
592 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
593 if content_len >= 32 {
594 let content = &encoded_data[..content_len];
595 return unsafe { base32_avx2::decode_into(output, content, alphabet) };
596 }
597
598 decode_into_constant_time(output, encoded_data, alphabet)
599}
600
601pub const fn decode_into_constant_time(
616 output: &mut [u8],
617 encoded_data: &[u8],
618 alphabet: Alphabet,
619) -> Result<(), DecodeError> {
620 let in_len = encoded_data.len();
621 let padding = alphabet.is_padded();
622
623 if in_len == 0 {
624 return Ok(());
625 }
626
627 let (content_len, _padding_len) = match strip_padding_info(encoded_data, padding) {
628 Ok(info) => info,
629 Err(e) => return Err(e),
630 };
631
632 if content_len == 0 {
633 return Ok(());
634 }
635
636 let computed_output = match decoded_length(content_len) {
637 Ok(len) => len,
638 Err(e) => return Err(e),
639 };
640
641 if output.len() < computed_output {
642 return Err(DecodeError::InvalidLength);
643 }
644
645 let mut err: u8 = 0;
646 let mut i = 0;
647 let mut o = 0;
648
649 while i + 64 <= content_len {
650 decode_8quads(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
651 }
652
653 while i + 8 <= content_len {
654 decode_1quad(output, alphabet, encoded_data, &mut i, &mut o, &mut err);
655 }
656
657 if i < content_len {
658 let remaining = content_len - i;
659 match remaining {
660 2 => {
661 let v0 = char_to_quintet(encoded_data[i], alphabet);
662 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
663 err |= v0 | v1;
664 err |= check_trailing_bits(v1, 2);
665 output[o] = (v0 << 3) | (v1 >> 2);
666 }
667 4 => {
668 let v0 = char_to_quintet(encoded_data[i], alphabet);
669 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
670 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
671 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
672 err |= v0 | v1 | v2 | v3;
673 err |= check_trailing_bits(v3, 4);
674 output[o] = (v0 << 3) | (v1 >> 2);
675 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
676 }
677 5 => {
678 let v0 = char_to_quintet(encoded_data[i], alphabet);
679 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
680 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
681 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
682 let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
683 err |= v0 | v1 | v2 | v3 | v4;
684 err |= check_trailing_bits(v4, 1);
685 output[o] = (v0 << 3) | (v1 >> 2);
686 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
687 output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
688 }
689 7 => {
690 let v0 = char_to_quintet(encoded_data[i], alphabet);
691 let v1 = char_to_quintet(encoded_data[i + 1], alphabet);
692 let v2 = char_to_quintet(encoded_data[i + 2], alphabet);
693 let v3 = char_to_quintet(encoded_data[i + 3], alphabet);
694 let v4 = char_to_quintet(encoded_data[i + 4], alphabet);
695 let v5 = char_to_quintet(encoded_data[i + 5], alphabet);
696 let v6 = char_to_quintet(encoded_data[i + 6], alphabet);
697 err |= v0 | v1 | v2 | v3 | v4 | v5 | v6;
698 err |= check_trailing_bits(v6, 3);
699 output[o] = (v0 << 3) | (v1 >> 2);
700 output[o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
701 output[o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
702 output[o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
703 }
704 _ => return Err(DecodeError::InvalidLength),
705 }
706 }
707
708 if err >= 32 {
709 return Err(DecodeError::InvalidInput);
710 }
711
712 Ok(())
713}
714
715#[inline]
716const fn decode_1quad(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
717 let v0 = char_to_quintet(data[*i], alphabet);
718 let v1 = char_to_quintet(data[*i + 1], alphabet);
719 let v2 = char_to_quintet(data[*i + 2], alphabet);
720 let v3 = char_to_quintet(data[*i + 3], alphabet);
721 let v4 = char_to_quintet(data[*i + 4], alphabet);
722 let v5 = char_to_quintet(data[*i + 5], alphabet);
723 let v6 = char_to_quintet(data[*i + 6], alphabet);
724 let v7 = char_to_quintet(data[*i + 7], alphabet);
725 *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
726 output[*o] = (v0 << 3) | (v1 >> 2);
727 output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
728 output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
729 output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
730 output[*o + 4] = (v6.wrapping_shl(5)) | v7;
731 *i += 8;
732 *o += 5;
733}
734
735#[inline]
736const fn decode_8quads(output: &mut [u8], alphabet: Alphabet, data: &[u8], i: &mut usize, o: &mut usize, err: &mut u8) {
737 let mut n = 0;
738 while n < 8 {
739 let v0 = char_to_quintet(data[*i], alphabet);
740 let v1 = char_to_quintet(data[*i + 1], alphabet);
741 let v2 = char_to_quintet(data[*i + 2], alphabet);
742 let v3 = char_to_quintet(data[*i + 3], alphabet);
743 let v4 = char_to_quintet(data[*i + 4], alphabet);
744 let v5 = char_to_quintet(data[*i + 5], alphabet);
745 let v6 = char_to_quintet(data[*i + 6], alphabet);
746 let v7 = char_to_quintet(data[*i + 7], alphabet);
747 *err |= v0 | v1 | v2 | v3 | v4 | v5 | v6 | v7;
748 output[*o] = (v0 << 3) | (v1 >> 2);
749 output[*o + 1] = (v1.wrapping_shl(6)) | (v2 << 1) | (v3 >> 4);
750 output[*o + 2] = (v3.wrapping_shl(4)) | (v4 >> 1);
751 output[*o + 3] = (v4.wrapping_shl(7)) | (v5 << 2) | (v6 >> 3);
752 output[*o + 4] = (v6.wrapping_shl(5)) | v7;
753 *i += 8;
754 *o += 5;
755 n += 1;
756 }
757}
758
759#[inline]
760const fn strip_padding_info(data: &[u8], expect_padding: bool) -> Result<(usize, usize), DecodeError> {
761 let in_len = data.len();
762
763 if expect_padding {
764 if in_len == 0 {
765 return Ok((0, 0));
766 }
767
768 let count = count_trailing_padding(data);
769 let content_len = in_len - count;
770
771 let err = (count > 0 && in_len % 8 != 0)
772 || count > 6
773 || (count > 0
774 && match count {
775 6 => content_len % 8 != 2,
776 4 => content_len % 8 != 4,
777 3 => content_len % 8 != 5,
778 1 => content_len % 8 != 7,
779 _ => true,
780 });
781
782 if err {
783 return Err(DecodeError::InvalidPadding);
784 }
785
786 Ok((content_len, count))
787 } else {
788 if in_len > 0 && data[in_len - 1] == PAD {
789 return Err(DecodeError::InvalidPadding);
790 }
791 Ok((in_len, 0))
792 }
793}
794
795const fn count_trailing_padding(data: &[u8]) -> usize {
799 let len = data.len();
800 if len == 0 {
801 return 0;
802 }
803 let max_check = if len < 7 { len } else { 7 };
804 let mut count: usize = 0;
805 let mut all_pad: u8 = 0xFF;
806
807 let mut k = 0;
808 while k < max_check {
809 let idx = len - 1 - k;
810 let is_pad = if data[idx] == PAD { 0xFFu8 } else { 0x00u8 };
811 all_pad = all_pad & is_pad;
812 let all_pad_ext = (all_pad as i8 >> 7) as usize;
813 count = ((k + 1) as usize) & all_pad_ext | count & !all_pad_ext;
814 k += 1;
815 }
816
817 count
818}
819
820#[inline]
823const fn char_to_quintet(c: u8, alphabet: Alphabet) -> u8 {
824 match alphabet {
825 Alphabet::Crockford => crockford_to_quintet(c),
826 Alphabet::Rfc4648 | Alphabet::Rfc4648NoPadding => {
827 let not_upper = not_in_range(c, b'A', b'Z');
828 let not_digit = not_in_range(c, b'2', b'7');
829 let value = (c.wrapping_sub(b'A')) & !not_upper | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
830 let invalid = not_upper & not_digit;
831 value | (invalid & 0x20)
832 }
833 Alphabet::Rfc4648Lower | Alphabet::Rfc4648LowerNoPadding => {
834 let not_lower = not_in_range(c, b'a', b'z');
835 let not_digit = not_in_range(c, b'2', b'7');
836 let value = (c.wrapping_sub(b'a')) & !not_lower | (c.wrapping_sub(b'2').wrapping_add(26)) & !not_digit;
837 let invalid = not_lower & not_digit;
838 value | (invalid & 0x20)
839 }
840 Alphabet::Rfc4648Hex | Alphabet::Rfc4648HexNoPadding => {
841 let not_digit = not_in_range(c, b'0', b'9');
842 let not_upper = not_in_range(c, b'A', b'V');
843 let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_upper;
844 let invalid = not_digit & not_upper;
845 value | (invalid & 0x20)
846 }
847 Alphabet::Rfc4648HexLower | Alphabet::Rfc4648HexLowerNoPadding => {
848 let not_digit = not_in_range(c, b'0', b'9');
849 let not_lower = not_in_range(c, b'a', b'v');
850 let value = (c.wrapping_sub(b'0')) & !not_digit | (c.wrapping_sub(b'a').wrapping_add(10)) & !not_lower;
851 let invalid = not_digit & not_lower;
852 value | (invalid & 0x20)
853 }
854 }
855}
856
857#[inline]
859const fn crockford_to_quintet(c: u8) -> u8 {
860 let not_0_9 = not_in_range(c, b'0', b'9');
861 let not_a_h = not_in_range(c, b'A', b'H');
862 let not_j_k = not_in_range(c, b'J', b'K');
863 let not_m_n = not_in_range(c, b'M', b'N');
864 let not_p_t = not_in_range(c, b'P', b'T');
865 let not_v_z = not_in_range(c, b'V', b'Z');
866 let value = (c.wrapping_sub(b'0')) & !not_0_9
867 | (c.wrapping_sub(b'A').wrapping_add(10)) & !not_a_h
868 | (c.wrapping_sub(b'J').wrapping_add(18)) & !not_j_k
869 | (c.wrapping_sub(b'M').wrapping_add(20)) & !not_m_n
870 | (c.wrapping_sub(b'P').wrapping_add(22)) & !not_p_t
871 | (c.wrapping_sub(b'V').wrapping_add(27)) & !not_v_z;
872 let invalid = not_0_9 & not_a_h & not_j_k & not_m_n & not_p_t & not_v_z;
873 value | (invalid & 0x20)
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879
880 const ENCODE_VECTORS: &[(&[u8], Alphabet, &str, &str)] = &[
882 (b"", Alphabet::Rfc4648, "", "RFC4648 padded: empty"),
883 (b"", Alphabet::Rfc4648NoPadding, "", "RFC4648 unpadded: empty"),
884 (b"\x00", Alphabet::Rfc4648, "AA======", "RFC4648 padded: 0x00"),
885 (b"\xFF", Alphabet::Rfc4648, "74======", "RFC4648 padded: 0xFF"),
886 (b"\xAB", Alphabet::Rfc4648, "VM======", "RFC4648 padded: 0xAB"),
887 (b"fo", Alphabet::Rfc4648, "MZXQ====", "RFC4648 padded: 'fo'"),
888 (b"foo", Alphabet::Rfc4648, "MZXW6===", "RFC4648 padded: 'foo'"),
889 (b"foob", Alphabet::Rfc4648, "MZXW6YQ=", "RFC4648 padded: 'foob'"),
890 (b"fooba", Alphabet::Rfc4648, "MZXW6YTB", "RFC4648 padded: 'fooba'"),
891 (b"foobar", Alphabet::Rfc4648, "MZXW6YTBOI======", "RFC4648 padded: 'foobar'"),
892 (b"hello", Alphabet::Rfc4648, "NBSWY3DP", "RFC4648 padded: 'hello'"),
893 (b"hello", Alphabet::Rfc4648NoPadding, "NBSWY3DP", "RFC4648 unpadded: 'hello'"),
894 (b"h", Alphabet::Rfc4648NoPadding, "NA", "RFC4648 unpadded: 'h'"),
895 (b"he", Alphabet::Rfc4648NoPadding, "NBSQ", "RFC4648 unpadded: 'he'"),
896 (b"hel", Alphabet::Rfc4648NoPadding, "NBSWY", "RFC4648 unpadded: 'hel'"),
897 (b"hell", Alphabet::Rfc4648NoPadding, "NBSWY3A", "RFC4648 unpadded: 'hell'"),
898 (b"hello", Alphabet::Rfc4648Lower, "nbswy3dp", "RFC4648 lower: 'hello'"),
899 (b"hello", Alphabet::Rfc4648Hex, "D1IMOR3F", "RFC4648 hex: 'hello'"),
900 (b"hello", Alphabet::Rfc4648HexLower, "d1imor3f", "RFC4648 hex lower: 'hello'"),
901 (b"hello", Alphabet::Crockford, "D1JPRV3F", "Crockford: 'hello'"),
902 (b"f", Alphabet::Rfc4648Hex, "CO======", "RFC4648 hex: 'f'"),
904 (b"fo", Alphabet::Rfc4648Hex, "CPNG====", "RFC4648 hex: 'fo'"),
905 (b"foo", Alphabet::Rfc4648Hex, "CPNMU===", "RFC4648 hex: 'foo'"),
906 (b"foob", Alphabet::Rfc4648Hex, "CPNMUOG=", "RFC4648 hex: 'foob'"),
907 (b"fooba", Alphabet::Rfc4648Hex, "CPNMUOJ1", "RFC4648 hex: 'fooba'"),
908 (b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "RFC4648 hex: 'foobar'"),
909 ];
910
911 const DECODE_VECTORS: &[(&[u8], Alphabet, &[u8], &str)] = &[
913 (b"", Alphabet::Rfc4648, b"", "RFC4648 padded: empty"),
914 (b"AA======", Alphabet::Rfc4648, b"\x00", "RFC4648 padded: 0x00"),
915 (b"AE======", Alphabet::Rfc4648, b"\x01", "RFC4648 padded: 0x01"),
916 (b"MZXQ====", Alphabet::Rfc4648, b"fo", "RFC4648 padded: 'fo'"),
917 (b"MZXW6===", Alphabet::Rfc4648, b"foo", "RFC4648 padded: 'foo'"),
918 (b"MZXW6YQ=", Alphabet::Rfc4648, b"foob", "RFC4648 padded: 'foob'"),
919 (b"MZXW6YTB", Alphabet::Rfc4648, b"fooba", "RFC4648 padded: 'fooba'"),
920 (b"NA", Alphabet::Rfc4648NoPadding, b"h", "RFC4648 unpadded: 'h'"),
921 (b"NBSQ", Alphabet::Rfc4648NoPadding, b"he", "RFC4648 unpadded: 'he'"),
922 (b"NBSWY", Alphabet::Rfc4648NoPadding, b"hel", "RFC4648 unpadded: 'hel'"),
923 (b"NBSWY3A", Alphabet::Rfc4648NoPadding, b"hell", "RFC4648 unpadded: 'hell'"),
924 (b"nbswy3dp", Alphabet::Rfc4648Lower, b"hello", "RFC4648 lower: 'hello'"),
925 (b"D1IMOR3F", Alphabet::Rfc4648Hex, b"hello", "RFC4648 hex: 'hello'"),
926 (b"D1JPRV3F", Alphabet::Crockford, b"hello", "Crockford: 'hello'"),
927 ];
928
929 const DECODE_ERROR_VECTORS: &[(&[u8], Alphabet, DecodeError, &str)] = &[
931 (b"!!!!====", Alphabet::Rfc4648, DecodeError::InvalidInput, "4 invalid chars"),
932 (
933 b"AAA=====",
934 Alphabet::Rfc4648,
935 DecodeError::InvalidPadding,
936 "wrong padding position",
937 ),
938 (
939 b"AA==========",
940 Alphabet::Rfc4648,
941 DecodeError::InvalidPadding,
942 "too many padding chars",
943 ),
944 (
945 b"AA======",
946 Alphabet::Rfc4648NoPadding,
947 DecodeError::InvalidPadding,
948 "no-pad rejects padding",
949 ),
950 (b"A", Alphabet::Rfc4648, DecodeError::InvalidLength, "single char"),
951 (
952 b"D1JPRV!!",
953 Alphabet::Crockford,
954 DecodeError::InvalidInput,
955 "Crockford invalid chars",
956 ),
957 ];
958
959 const ENCODED_LENGTH_VECTORS: &[(usize, bool, Option<usize>, &str)] = &[
961 (0, true, Some(0), "empty padded"),
962 (1, true, Some(8), "1 byte padded"),
963 (5, true, Some(8), "5 bytes padded"),
964 (6, true, Some(16), "6 bytes padded"),
965 (10, true, Some(16), "10 bytes padded"),
966 (0, false, Some(0), "empty unpadded"),
967 (1, false, Some(2), "1 byte unpadded"),
968 (5, false, Some(8), "5 bytes unpadded"),
969 (6, false, Some(10), "6 bytes unpadded"),
970 ];
971
972 const ENCODE_INTO_STRING_VECTORS: &[(&str, &[u8], Alphabet, &str, &str)] = &[
974 ("", b"", Alphabet::Rfc4648, "", "empty"),
975 ("prefix", b"", Alphabet::Rfc4648, "prefix", "empty data with prefix"),
976 (
977 "",
978 b"hello world",
979 Alphabet::Rfc4648,
980 "NBSWY3DPEB3W64TMMQ======",
981 "hello world padded",
982 ),
983 (
984 "",
985 b"hello world",
986 Alphabet::Rfc4648NoPadding,
987 "NBSWY3DPEB3W64TMMQ",
988 "hello world unpadded",
989 ),
990 (
991 "data: ",
992 b"hello world",
993 Alphabet::Rfc4648,
994 "data: NBSWY3DPEB3W64TMMQ======",
995 "append to prefix",
996 ),
997 ("", b"foobar", Alphabet::Rfc4648Hex, "CPNMUOJ1E8======", "hex alphabet"),
998 ];
999
1000 const ALL_ALPHABETS: &[Alphabet] = &[
1001 Alphabet::Rfc4648,
1002 Alphabet::Rfc4648NoPadding,
1003 Alphabet::Rfc4648Lower,
1004 Alphabet::Rfc4648Hex,
1005 Alphabet::Rfc4648HexLower,
1006 Alphabet::Crockford,
1007 ];
1008
1009 const ROUNDTRIP_SIZES: &[usize] = &[
1010 0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129,
1011 ];
1012
1013 const SIMD_BOUNDARY_SIZES: &[usize] = &[38, 39, 40, 41, 42, 45, 50, 62, 63, 64, 65, 66, 84, 85, 100];
1014
1015 #[test]
1016 fn test_encode() {
1017 for &(input, alphabet, expected, desc) in ENCODE_VECTORS {
1018 let result = encode(input, alphabet);
1019 assert_eq!(result, expected, "encode: {desc}");
1020 }
1021 }
1022
1023 #[test]
1024 fn test_decode() {
1025 for &(encoded, alphabet, expected, desc) in DECODE_VECTORS {
1026 let result = decode(encoded, alphabet).unwrap();
1027 assert_eq!(&result, expected, "decode: {desc}");
1028 }
1029 }
1030
1031 #[test]
1032 fn test_decode_error() {
1033 for &(encoded, alphabet, expected_err, desc) in DECODE_ERROR_VECTORS {
1034 let result = decode(encoded, alphabet);
1035 assert_eq!(result, Err(expected_err), "decode error: {desc}");
1036 }
1037
1038 let mut data = alloc::vec![b'A'; 256];
1040 data[255] = b'!';
1041 assert_eq!(decode(&data, Alphabet::Rfc4648), Err(DecodeError::InvalidInput));
1042 }
1043
1044 #[test]
1045 fn test_encoded_length() {
1046 for &(data_len, padding, expected, desc) in ENCODED_LENGTH_VECTORS {
1047 let result = encoded_length(data_len, padding);
1048 assert_eq!(result, expected, "encoded_length: {desc}");
1049 }
1050 }
1051
1052 #[test]
1053 fn test_encode_into_string() {
1054 for &(initial, input, alphabet, expected, desc) in ENCODE_INTO_STRING_VECTORS {
1055 let mut s = alloc::string::String::from(initial);
1056 encode_into_string(&mut s, input, alphabet);
1057 assert_eq!(s, expected, "encode_into_string: {desc}");
1058 }
1059
1060 let mut s = alloc::string::String::from("~~");
1062 encode_into_string(&mut s, b"hello", Alphabet::Rfc4648);
1063 assert_eq!(s, "~~NBSWY3DP");
1064 encode_into_string(&mut s, b"foo", Alphabet::Rfc4648);
1065 assert_eq!(s, "~~NBSWY3DPMZXW6===");
1066
1067 for alphabet in ALL_ALPHABETS {
1069 let expected = encode(b"hello world", *alphabet);
1070 let mut s = alloc::string::String::new();
1071 encode_into_string(&mut s, b"hello world", *alphabet);
1072 assert_eq!(s, expected, "encode_into_string alphabet {alphabet:?}");
1073 }
1074 }
1075
1076 #[test]
1077 fn test_roundtrip() {
1078 for &len in ROUNDTRIP_SIZES {
1079 let data: Vec<u8> = (0..len as u8).collect();
1080 for alphabet in ALL_ALPHABETS {
1081 let encoded = encode(&data, *alphabet);
1082 let decoded = decode(encoded.as_bytes(), *alphabet).unwrap();
1083 assert_eq!(decoded, data, "roundtrip len={len} alphabet={alphabet:?}");
1084 }
1085 }
1086 }
1087
1088 #[test]
1089 fn test_roundtrip_large() {
1090 let size = 4096;
1091
1092 let data = alloc::vec![0x00u8; size];
1093 let elen = encoded_length(size, true).expect("encoded_len overflow");
1094 let mut encoded = alloc::vec![0u8; elen];
1095 encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
1096 let mut decoded = alloc::vec![0u8; size];
1097 decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
1098 assert_eq!(decoded, data, "4096 zeroes constant-time");
1099
1100 let data = alloc::vec![0xFFu8; size];
1101 let mut encoded = alloc::vec![0u8; elen];
1102 encode_into_constant_time(&mut encoded, &data, Alphabet::Rfc4648).unwrap();
1103 decode_into_constant_time(&mut decoded, &encoded, Alphabet::Rfc4648).unwrap();
1104 assert_eq!(decoded, data, "4096 0xFF constant-time");
1105
1106 let data: Vec<u8> = (0..=255).cycle().take(size).collect();
1107 let encoded = encode(&data, Alphabet::Rfc4648);
1108 let decoded = decode(encoded.as_bytes(), Alphabet::Rfc4648).unwrap();
1109 assert_eq!(decoded, data, "4096 cycle dispatch");
1110
1111 let data: Vec<u8> = (0..=255).collect();
1112 let mut s = alloc::string::String::new();
1113 encode_into_string(&mut s, &data, Alphabet::Rfc4648);
1114 let decoded = decode(s.as_bytes(), Alphabet::Rfc4648).unwrap();
1115 assert_eq!(decoded, data, "256-byte encode_into_string roundtrip");
1116
1117 let data: Vec<u8> = (0..255).cycle().take(4096).collect();
1118 let expected = encode(&data, Alphabet::Rfc4648);
1119 let mut s = alloc::string::String::new();
1120 encode_into_string(&mut s, &data, Alphabet::Rfc4648);
1121 assert_eq!(s, expected, "4096-byte encode_into_string");
1122 }
1123
1124 #[test]
1125 fn test_encode_all_single_bytes() {
1126 for byte in 0..=255u8 {
1127 for alphabet in &[
1128 Alphabet::Rfc4648,
1129 Alphabet::Rfc4648Lower,
1130 Alphabet::Rfc4648Hex,
1131 Alphabet::Rfc4648HexLower,
1132 Alphabet::Crockford,
1133 ] {
1134 let padding = alphabet.is_padded();
1135 let elen = encoded_length(1, padding).unwrap();
1136 let mut encoded = alloc::vec![0u8; elen];
1137 encode_into_constant_time(&mut encoded, &[byte], *alphabet).unwrap();
1138 let mut decoded = [0u8; 1];
1139 decode_into_constant_time(&mut decoded, &encoded, *alphabet).unwrap();
1140 assert_eq!(decoded[0], byte, "single byte roundtrip {byte:#04x} alphabet={alphabet:?}");
1141 }
1142 }
1143 }
1144
1145 #[test]
1146 fn test_decode_invalid_char_every_position() {
1147 let mut out = [0u8; 128];
1148
1149 for pos in 0..8 {
1151 let mut input = [b'A'; 8];
1152 input[pos] = b'!';
1153 assert_eq!(
1154 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
1155 Err(DecodeError::InvalidInput),
1156 "invalid char at position {pos} in 8-char input"
1157 );
1158 }
1159
1160 for pos in 0..64 {
1162 let mut input = [b'A'; 64];
1163 input[pos] = b'!';
1164 assert_eq!(
1165 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
1166 Err(DecodeError::InvalidInput),
1167 "invalid char at position {pos} in 64-char input"
1168 );
1169 }
1170
1171 for pos in 0..72 {
1173 let mut input = [b'A'; 72];
1174 input[pos] = b'!';
1175 assert_eq!(
1176 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648),
1177 Err(DecodeError::InvalidInput),
1178 "invalid char at position {pos} in 72-char input"
1179 );
1180 }
1181
1182 for pos in 0..8 {
1184 let mut input = [b'a'; 8];
1185 input[pos] = b'!';
1186 assert_eq!(
1187 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Lower),
1188 Err(DecodeError::InvalidInput)
1189 );
1190 }
1191 for pos in 0..8 {
1192 let mut input = [b'0'; 8];
1193 input[pos] = b'!';
1194 assert_eq!(
1195 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648Hex),
1196 Err(DecodeError::InvalidInput)
1197 );
1198 }
1199 for pos in 0..8 {
1200 let mut input = [b'0'; 8];
1201 input[pos] = b'!';
1202 assert_eq!(
1203 decode_into_constant_time(&mut out, &input, Alphabet::Rfc4648HexLower),
1204 Err(DecodeError::InvalidInput)
1205 );
1206 }
1207 for pos in 0..8 {
1208 let mut input = [b'0'; 8];
1209 input[pos] = b'!';
1210 assert_eq!(
1211 decode_into_constant_time(&mut out, &input, Alphabet::Crockford),
1212 Err(DecodeError::InvalidInput)
1213 );
1214 }
1215 }
1216
1217 #[test]
1218 fn test_decode_non_canonical_trailing_bits() {
1219 let mut out = [0u8; 8];
1220
1221 for &(input, expected) in &[
1223 (b"AA======" as &[u8], Ok(())),
1224 (b"AB======" as &[u8], Err(DecodeError::InvalidInput)),
1225 (b"AC======" as &[u8], Err(DecodeError::InvalidInput)),
1226 (b"AD======" as &[u8], Err(DecodeError::InvalidInput)),
1227 ] {
1228 assert_eq!(
1229 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1230 expected,
1231 "non-canonical (rem=2): {:?}",
1232 core::str::from_utf8(input)
1233 );
1234 }
1235
1236 for &(input, expected) in &[
1238 (b"MZXQ====" as &[u8], Ok(())),
1239 (b"MZXR====" as &[u8], Err(DecodeError::InvalidInput)),
1240 ] {
1241 assert_eq!(
1242 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1243 expected,
1244 "non-canonical (rem=4): {:?}",
1245 core::str::from_utf8(input)
1246 );
1247 }
1248
1249 for &(input, expected) in &[
1251 (b"MZXW6===" as &[u8], Ok(())),
1252 (b"MZXW7===" as &[u8], Err(DecodeError::InvalidInput)),
1253 ] {
1254 assert_eq!(
1255 decode_into_constant_time(&mut out, input, Alphabet::Rfc4648),
1256 expected,
1257 "non-canonical (rem=5): {:?}",
1258 core::str::from_utf8(input)
1259 );
1260 }
1261
1262 assert_eq!(decode_into_constant_time(&mut out, b"NBSWY3DP", Alphabet::Rfc4648), Ok(()));
1264 }
1265
1266 #[test]
1267 fn test_decode_rejects_interior_padding() {
1268 let mut out = [0u8; 8];
1269 assert_eq!(
1270 decode_into_constant_time(&mut out, b"=AAA====", Alphabet::Rfc4648),
1271 Err(DecodeError::InvalidInput)
1272 );
1273 assert_eq!(
1274 decode_into_constant_time(&mut out, b"A=AA====", Alphabet::Rfc4648),
1275 Err(DecodeError::InvalidInput)
1276 );
1277 assert_eq!(
1278 decode_into_constant_time(&mut out, b"AA=A====", Alphabet::Rfc4648),
1279 Err(DecodeError::InvalidInput)
1280 );
1281 assert_eq!(
1282 decode_into_constant_time(&mut out, b"AAA=====", Alphabet::Rfc4648),
1283 Err(DecodeError::InvalidPadding)
1284 );
1285 }
1286
1287 #[test]
1288 fn test_roundtrip_simd_boundary_sizes() {
1289 let mut data_buf = Vec::new();
1290 let mut enc_buf = Vec::new();
1291
1292 for &input_len in SIMD_BOUNDARY_SIZES {
1293 data_buf.clear();
1294 for b in 0..input_len {
1295 data_buf.push(b as u8);
1296 }
1297
1298 for alphabet in &[Alphabet::Rfc4648, Alphabet::Rfc4648NoPadding] {
1299 let padding = alphabet.is_padded();
1300 let elen = encoded_length(input_len, padding).expect("encoded_len overflow");
1301 enc_buf.resize(elen, 0);
1302 encode_into_constant_time(&mut enc_buf, &data_buf, *alphabet).unwrap();
1303
1304 let mut decoded = alloc::vec![0u8; input_len];
1305 assert_eq!(
1306 decode_into_constant_time(&mut decoded, &enc_buf, *alphabet),
1307 Ok(()),
1308 "decode failed len={input_len} alphabet={alphabet:?}"
1309 );
1310 assert_eq!(&decoded, &data_buf, "roundtrip mismatch len={input_len} alphabet={alphabet:?}");
1311 }
1312 }
1313 }
1314
1315 #[test]
1316 fn test_const_encode() {
1317 const RESULT: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Rfc4648);
1318 assert_eq!(&RESULT, b"NBSWY3DP");
1319
1320 const RESULT_EMPTY: [u8; 0] = encode_array::<0>(b"", Alphabet::Rfc4648);
1321 assert_eq!(RESULT_EMPTY.len(), 0);
1322
1323 const RESULT_CROCKFORD: [u8; 8] = encode_array::<8>(b"hello", Alphabet::Crockford);
1324 assert_eq!(&RESULT_CROCKFORD, b"D1JPRV3F");
1325 }
1326
1327 #[test]
1328 fn test_const_decode() {
1329 const RESULT: Result<[u8; 5], DecodeError> = decode_array::<5>(b"NBSWY3DP", Alphabet::Rfc4648);
1330 assert_eq!(RESULT.unwrap(), *b"hello");
1331
1332 const RESULT_EMPTY: Result<[u8; 0], DecodeError> = decode_array::<0>(b"", Alphabet::Rfc4648);
1333 assert_eq!(RESULT_EMPTY.unwrap().len(), 0);
1334 }
1335
1336 #[test]
1337 fn test_const_decode_error() {
1338 const ERR_INVALID: Result<[u8; 5], DecodeError> = decode_array::<5>(b"D1JPRV!!", Alphabet::Crockford);
1339 assert_eq!(ERR_INVALID, Err(DecodeError::InvalidInput));
1340 }
1341
1342 #[test]
1343 fn test_buffer_management() {
1344 let mut out = [0u8; 1];
1345 assert_eq!(
1346 encode_into(&mut out, b"hello", Alphabet::Rfc4648),
1347 Err(EncodeError::InvalidOutputLength)
1348 );
1349
1350 let mut out = [0u8; 5];
1351 decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648).unwrap();
1352 assert_eq!(&out, b"hello");
1353
1354 let mut out = [0u8; 1];
1355 assert_eq!(
1356 decode_into(&mut out, b"NBSWY3DP", Alphabet::Rfc4648),
1357 Err(DecodeError::InvalidLength)
1358 );
1359
1360 let mut remainders = [0u8; 80];
1362 let mut output = [0u8; 80];
1363 for len in 1..=80 {
1364 for i in 0..len {
1365 remainders[i] = (i * 7 + 3) as u8;
1366 }
1367 let encoded = encode(&remainders[..len], Alphabet::Rfc4648);
1368 let expected_output_len = len;
1369 let r = decode_into(&mut output[..expected_output_len], encoded.as_bytes(), Alphabet::Rfc4648);
1370 assert!(r.is_ok(), "decode_into failed at len {}", len);
1371 assert_eq!(&output[..expected_output_len], &remainders[..len], "mismatch at len {}", len);
1372 }
1373 }
1374
1375 #[test]
1376 fn test_display_error() {
1377 assert_eq!(format!("{}", DecodeError::InvalidInput), "invalid base32 character");
1378 assert_eq!(format!("{}", DecodeError::InvalidLength), "invalid base32 length");
1379 assert_eq!(format!("{}", DecodeError::InvalidPadding), "invalid base32 padding");
1380 assert_eq!(
1381 format!("{}", EncodeError::InvalidOutputLength),
1382 "output buffer size is not valid"
1383 );
1384 }
1385
1386 #[cfg(feature = "serde")]
1387 #[test]
1388 fn test_serde() {
1389 #[derive(::serde::Serialize, ::serde::Deserialize)]
1390 struct Data(#[serde(with = "crate::serde")] Vec<u8>);
1391
1392 let data = Data(b"hello world".to_vec());
1393 let json = ::serde_json::to_string(&data).unwrap();
1394 assert_eq!(json, "\"NBSWY3DPEB3W64TMMQ======\"");
1395 let deserialized: Data = ::serde_json::from_str(&json).unwrap();
1396 assert_eq!(deserialized.0, b"hello world");
1397 }
1398}