1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(any(feature = "alloc", test))]
44extern crate alloc;
45
46#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
47mod serde;
48
49#[cfg(target_arch = "aarch64")]
50mod hex_neon;
51
52#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
53mod hex_avx2;
54
55const ALPHABET_LOWER: [u8; 16] = *b"0123456789abcdef";
56const ALPHABET_UPPER: [u8; 16] = *b"0123456789ABCDEF";
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum Alphabet {
78 Lower,
79 Upper,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum DecodeError {
85 InvalidInput,
88 InvalidInputLength,
90 InvalidOutputLength,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum EncodeError {
97 InvalidOutputLength,
99}
100
101impl core::fmt::Display for DecodeError {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103 match self {
104 Self::InvalidInput => f.write_str("invalid hex character"),
105 Self::InvalidInputLength => f.write_str("odd number of hex characters"),
106 Self::InvalidOutputLength => f.write_str("output buffer size must be equal to input.len() / 2"),
107 }
108 }
109}
110
111impl core::fmt::Display for EncodeError {
112 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113 match self {
114 Self::InvalidOutputLength => f.write_str("output buffer size must be equal to input.len() * 2"),
115 }
116 }
117}
118
119#[cfg(feature = "std")]
120impl std::error::Error for DecodeError {}
121
122#[cfg(feature = "std")]
123impl std::error::Error for EncodeError {}
124
125#[cfg(any(feature = "alloc", test))]
136#[inline]
137pub fn encode(data: impl AsRef<[u8]>) -> alloc::string::String {
138 encode_with_alphabet(data.as_ref(), Alphabet::Lower)
139}
140
141#[cfg(any(feature = "alloc", test))]
149#[inline]
150pub fn encode_with_alphabet(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
151 let data = data.as_ref();
152 let mut output = alloc::vec![0u8; data.len() * 2];
153 encode_into(&mut output, data, alphabet).unwrap();
154 unsafe { alloc::string::String::from_utf8_unchecked(output) }
155}
156
157pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
170 let mut result = [0u8; OUT];
171 match encode_into_constant_time(&mut result, data, alphabet) {
172 Ok(_) => {}
173 Err(_) => panic!("output buffer size is not valid"),
174 };
175 result
176}
177
178#[inline]
197pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
198 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
199 if data.len() >= 16 {
200 check_encode_output_length(data.len(), output.len())?;
201 return unsafe { hex_neon::encode_into(output, data, alphabet) };
202 }
203
204 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
205 if data.len() >= 32 {
206 check_encode_output_length(data.len(), output.len())?;
207 return unsafe { hex_avx2::encode_into(output, data, alphabet) };
208 }
209
210 return encode_into_constant_time(output, data, alphabet);
211}
212
213pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
220 match check_encode_output_length(data.len(), output.len()) {
221 Ok(_) => {}
222 Err(err) => return Err(err),
223 };
224
225 let mut i = 0;
226 let len = data.len();
227
228 while i + 16 <= len {
229 let b0 = data[i];
230 let b1 = data[i + 1];
231 let b2 = data[i + 2];
232 let b3 = data[i + 3];
233 let b4 = data[i + 4];
234 let b5 = data[i + 5];
235 let b6 = data[i + 6];
236 let b7 = data[i + 7];
237 let b8 = data[i + 8];
238 let b9 = data[i + 9];
239 let b10 = data[i + 10];
240 let b11 = data[i + 11];
241 let b12 = data[i + 12];
242 let b13 = data[i + 13];
243 let b14 = data[i + 14];
244 let b15 = data[i + 15];
245
246 let o = i * 2;
247 output[o] = nibble_to_hex(b0 >> 4, alphabet);
248 output[o + 1] = nibble_to_hex(b0 & 0x0F, alphabet);
249 output[o + 2] = nibble_to_hex(b1 >> 4, alphabet);
250 output[o + 3] = nibble_to_hex(b1 & 0x0F, alphabet);
251 output[o + 4] = nibble_to_hex(b2 >> 4, alphabet);
252 output[o + 5] = nibble_to_hex(b2 & 0x0F, alphabet);
253 output[o + 6] = nibble_to_hex(b3 >> 4, alphabet);
254 output[o + 7] = nibble_to_hex(b3 & 0x0F, alphabet);
255 output[o + 8] = nibble_to_hex(b4 >> 4, alphabet);
256 output[o + 9] = nibble_to_hex(b4 & 0x0F, alphabet);
257 output[o + 10] = nibble_to_hex(b5 >> 4, alphabet);
258 output[o + 11] = nibble_to_hex(b5 & 0x0F, alphabet);
259 output[o + 12] = nibble_to_hex(b6 >> 4, alphabet);
260 output[o + 13] = nibble_to_hex(b6 & 0x0F, alphabet);
261 output[o + 14] = nibble_to_hex(b7 >> 4, alphabet);
262 output[o + 15] = nibble_to_hex(b7 & 0x0F, alphabet);
263 output[o + 16] = nibble_to_hex(b8 >> 4, alphabet);
264 output[o + 17] = nibble_to_hex(b8 & 0x0F, alphabet);
265 output[o + 18] = nibble_to_hex(b9 >> 4, alphabet);
266 output[o + 19] = nibble_to_hex(b9 & 0x0F, alphabet);
267 output[o + 20] = nibble_to_hex(b10 >> 4, alphabet);
268 output[o + 21] = nibble_to_hex(b10 & 0x0F, alphabet);
269 output[o + 22] = nibble_to_hex(b11 >> 4, alphabet);
270 output[o + 23] = nibble_to_hex(b11 & 0x0F, alphabet);
271 output[o + 24] = nibble_to_hex(b12 >> 4, alphabet);
272 output[o + 25] = nibble_to_hex(b12 & 0x0F, alphabet);
273 output[o + 26] = nibble_to_hex(b13 >> 4, alphabet);
274 output[o + 27] = nibble_to_hex(b13 & 0x0F, alphabet);
275 output[o + 28] = nibble_to_hex(b14 >> 4, alphabet);
276 output[o + 29] = nibble_to_hex(b14 & 0x0F, alphabet);
277 output[o + 30] = nibble_to_hex(b15 >> 4, alphabet);
278 output[o + 31] = nibble_to_hex(b15 & 0x0F, alphabet);
279
280 i += 16;
281 }
282
283 while i < len {
284 let b = data[i];
285 let o = i * 2;
286 output[o] = nibble_to_hex(b >> 4, alphabet);
287 output[o + 1] = nibble_to_hex(b & 0x0F, alphabet);
288 i += 1;
289 }
290
291 Ok(())
292}
293
294#[inline]
295const fn nibble_to_hex(nibble: u8, alphabet: Alphabet) -> u8 {
296 let nibble = nibble & 0x0F;
297 let digit_mask = (((nibble as i16) - 10) >> 8) as u8;
298
299 let digit_val = b'0' + nibble;
300 let letter_val = b'a' + nibble - 10;
301 let upper_val = b'A' + nibble - 10;
302
303 let lower_result = (digit_val & digit_mask) | (letter_val & !digit_mask);
304 let upper_result = (digit_val & digit_mask) | (upper_val & !digit_mask);
305
306 match alphabet {
307 Alphabet::Lower => lower_result,
308 Alphabet::Upper => upper_result,
309 }
310}
311
312#[inline]
313const fn check_encode_output_length(data_length: usize, output_length: usize) -> Result<(), EncodeError> {
314 if data_length * 2 != output_length {
315 return Err(EncodeError::InvalidOutputLength);
316 }
317 Ok(())
318}
319
320#[cfg(feature = "alloc")]
330pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
331 let encoded_length = data.len() * 2;
332 if encoded_length <= 256 {
333 let mut buf = [0u8; 256];
335 let mut buf = &mut buf[..encoded_length];
336 encode_into(&mut buf, data, alphabet).unwrap();
337 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
339 } else {
340 let mut buf = alloc::vec![0u8; encoded_length];
341 encode_into(&mut buf, data, alphabet).unwrap();
342 output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
344 }
345}
346
347#[cfg(any(feature = "alloc", test))]
367pub fn decode(data: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
368 let data = data.as_ref();
369 let mut output = alloc::vec![0u8; data.len() / 2];
370 decode_into(&mut output, data)?;
371 Ok(output)
372}
373
374pub const fn decode_array<const OUT: usize>(encoded_data: &[u8]) -> Result<[u8; OUT], DecodeError> {
387 let mut result = [0u8; OUT];
388 match decode_into_constant_time(&mut result, encoded_data) {
389 Ok(_) => {}
390 Err(err) => return Err(err),
391 }
392 Ok(result)
393}
394
395pub fn decode_into(output: &mut [u8], encoded_data: &[u8]) -> Result<(), DecodeError> {
417 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
418 if encoded_data.len() >= 32 {
419 check_decode_input_and_output_length(encoded_data.len(), output.len())?;
420 return unsafe { hex_neon::decode_into(output, encoded_data) };
421 }
422
423 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
424 if encoded_data.len() >= 32 {
425 check_decode_input_and_output_length(encoded_data.len(), output.len())?;
426 return unsafe { hex_avx2::decode_into(output, encoded_data) };
427 }
428
429 decode_into_constant_time(output, encoded_data)
430}
431
432pub const fn decode_into_constant_time(output: &mut [u8], encoded_data: &[u8]) -> Result<(), DecodeError> {
443 match check_decode_input_and_output_length(encoded_data.len(), output.len()) {
444 Ok(_) => {}
445 Err(err) => return Err(err),
446 };
447
448 let in_len = encoded_data.len();
449 let mut i = 0;
450 let mut err: u8 = 0;
451
452 while i + 32 <= in_len {
453 let h0 = nibble_from_hex(encoded_data[i]);
454 let l0 = nibble_from_hex(encoded_data[i + 1]);
455 err |= h0 | l0;
456 output[i / 2] = (h0 << 4) | l0;
457
458 let h1 = nibble_from_hex(encoded_data[i + 2]);
459 let l1 = nibble_from_hex(encoded_data[i + 3]);
460 err |= h1 | l1;
461 output[i / 2 + 1] = (h1 << 4) | l1;
462
463 let h2 = nibble_from_hex(encoded_data[i + 4]);
464 let l2 = nibble_from_hex(encoded_data[i + 5]);
465 err |= h2 | l2;
466 output[i / 2 + 2] = (h2 << 4) | l2;
467
468 let h3 = nibble_from_hex(encoded_data[i + 6]);
469 let l3 = nibble_from_hex(encoded_data[i + 7]);
470 err |= h3 | l3;
471 output[i / 2 + 3] = (h3 << 4) | l3;
472
473 let h4 = nibble_from_hex(encoded_data[i + 8]);
474 let l4 = nibble_from_hex(encoded_data[i + 9]);
475 err |= h4 | l4;
476 output[i / 2 + 4] = (h4 << 4) | l4;
477
478 let h5 = nibble_from_hex(encoded_data[i + 10]);
479 let l5 = nibble_from_hex(encoded_data[i + 11]);
480 err |= h5 | l5;
481 output[i / 2 + 5] = (h5 << 4) | l5;
482
483 let h6 = nibble_from_hex(encoded_data[i + 12]);
484 let l6 = nibble_from_hex(encoded_data[i + 13]);
485 err |= h6 | l6;
486 output[i / 2 + 6] = (h6 << 4) | l6;
487
488 let h7 = nibble_from_hex(encoded_data[i + 14]);
489 let l7 = nibble_from_hex(encoded_data[i + 15]);
490 err |= h7 | l7;
491 output[i / 2 + 7] = (h7 << 4) | l7;
492
493 let h8 = nibble_from_hex(encoded_data[i + 16]);
494 let l8 = nibble_from_hex(encoded_data[i + 17]);
495 err |= h8 | l8;
496 output[i / 2 + 8] = (h8 << 4) | l8;
497
498 let h9 = nibble_from_hex(encoded_data[i + 18]);
499 let l9 = nibble_from_hex(encoded_data[i + 19]);
500 err |= h9 | l9;
501 output[i / 2 + 9] = (h9 << 4) | l9;
502
503 let h10 = nibble_from_hex(encoded_data[i + 20]);
504 let l10 = nibble_from_hex(encoded_data[i + 21]);
505 err |= h10 | l10;
506 output[i / 2 + 10] = (h10 << 4) | l10;
507
508 let h11 = nibble_from_hex(encoded_data[i + 22]);
509 let l11 = nibble_from_hex(encoded_data[i + 23]);
510 err |= h11 | l11;
511 output[i / 2 + 11] = (h11 << 4) | l11;
512
513 let h12 = nibble_from_hex(encoded_data[i + 24]);
514 let l12 = nibble_from_hex(encoded_data[i + 25]);
515 err |= h12 | l12;
516 output[i / 2 + 12] = (h12 << 4) | l12;
517
518 let h13 = nibble_from_hex(encoded_data[i + 26]);
519 let l13 = nibble_from_hex(encoded_data[i + 27]);
520 err |= h13 | l13;
521 output[i / 2 + 13] = (h13 << 4) | l13;
522
523 let h14 = nibble_from_hex(encoded_data[i + 28]);
524 let l14 = nibble_from_hex(encoded_data[i + 29]);
525 err |= h14 | l14;
526 output[i / 2 + 14] = (h14 << 4) | l14;
527
528 let h15 = nibble_from_hex(encoded_data[i + 30]);
529 let l15 = nibble_from_hex(encoded_data[i + 31]);
530 err |= h15 | l15;
531 output[i / 2 + 15] = (h15 << 4) | l15;
532
533 i += 32;
534 }
535
536 while i < in_len {
537 let h = nibble_from_hex(encoded_data[i]);
538 let l = nibble_from_hex(encoded_data[i + 1]);
539 err |= h | l;
540 output[i / 2] = (h << 4) | l;
541 i += 2;
542 }
543
544 if err & 0xF0 != 0 {
545 return Err(DecodeError::InvalidInput);
546 }
547
548 Ok(())
549}
550
551#[inline]
552const fn nibble_from_hex(c: u8) -> u8 {
553 let is_digit = ((((c as i16) - (b'0' as i16)) | ((b'9' as i16) - (c as i16))) >> 8) as u8;
554 let is_lower = ((((c as i16) - (b'a' as i16)) | ((b'f' as i16) - (c as i16))) >> 8) as u8;
555 let is_upper = ((((c as i16) - (b'A' as i16)) | ((b'F' as i16) - (c as i16))) >> 8) as u8;
556
557 let digit_val = c.wrapping_sub(b'0');
558 let lower_val = c.wrapping_sub(b'a').wrapping_add(10);
559 let upper_val = c.wrapping_sub(b'A').wrapping_add(10);
560
561 let value = (digit_val & !is_digit) | (lower_val & !is_lower) | (upper_val & !is_upper);
562 let invalid = is_digit & is_lower & is_upper;
563
564 value | (invalid & 0xF0)
565}
566
567#[inline]
568const fn check_decode_input_and_output_length(encoded_length: usize, output_length: usize) -> Result<(), DecodeError> {
569 if encoded_length % 2 != 0 {
570 return Err(DecodeError::InvalidInputLength);
571 }
572 if output_length != encoded_length / 2 {
573 return Err(DecodeError::InvalidOutputLength);
574 }
575
576 Ok(())
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582
583 #[test]
584 fn encode_empty() {
585 assert_eq!(encode(b""), "");
586 let mut out = [0u8; 0];
587 encode_into(&mut out, b"", Alphabet::Lower).unwrap();
588 }
589
590 #[test]
591 fn encode_single_byte() {
592 assert_eq!(encode(b"\x00"), "00");
593 assert_eq!(encode(b"\xFF"), "ff");
594 assert_eq!(encode(b"\xAB"), "ab");
595 assert_eq!(encode_with_alphabet(b"\xAB", Alphabet::Upper), "AB");
596 }
597
598 #[test]
599 fn encode_multiple_bytes() {
600 assert_eq!(encode(b"hello"), "68656c6c6f");
601 assert_eq!(encode_with_alphabet(b"hello", Alphabet::Upper), "68656C6C6F");
602 assert_eq!(
603 encode(b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"),
604 "00112233445566778899aabbccddeeff"
605 );
606 }
607
608 #[test]
609 fn encode_all_bytes() {
610 let data: Vec<u8> = (0..=255).collect();
611 let hex = encode(&data);
612 assert_eq!(hex.len(), 512);
613 for (i, &b) in data.iter().enumerate() {
614 let hi = ALPHABET_LOWER[(b >> 4) as usize];
615 let lo = ALPHABET_LOWER[(b & 0x0F) as usize];
616 assert_eq!(hex.as_bytes()[i * 2], hi);
617 assert_eq!(hex.as_bytes()[i * 2 + 1], lo);
618 }
619 }
620
621 #[test]
622 fn encode_into_exact_buffer() {
623 let mut out = [0u8; 4];
624 encode_into(&mut out, b"\xDE\xAD", Alphabet::Upper).unwrap();
625 assert_eq!(&out, b"DEAD");
626 }
627
628 #[test]
629 fn decode_empty() {
630 assert_eq!(decode(b"").unwrap(), b"");
631 }
632
633 #[test]
634 fn decode_single_byte() {
635 assert_eq!(decode(b"00").unwrap(), b"\x00");
636 assert_eq!(decode(b"ff").unwrap(), b"\xFF");
637 assert_eq!(decode(b"FF").unwrap(), b"\xFF");
638 assert_eq!(decode(b"ab").unwrap(), b"\xAB");
639 assert_eq!(decode(b"AB").unwrap(), b"\xAB");
640 }
641
642 #[test]
643 fn decode_multiple_bytes() {
644 assert_eq!(decode(b"68656c6c6f").unwrap(), b"hello");
645 assert_eq!(
646 decode(b"00112233445566778899AABBCCDDEEFF").unwrap(),
647 b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"
648 );
649 }
650
651 #[test]
652 fn decode_into_exact_buffer() {
653 let mut out = [0u8; 2];
654 decode_into(&mut out, b"DEAD").unwrap();
655 assert_eq!(&out, b"\xDE\xAD");
656 }
657
658 #[test]
659 fn decode_invalid_character() {
660 assert_eq!(decode(b"0g"), Err(DecodeError::InvalidInput));
661 assert_eq!(decode(b"GG"), Err(DecodeError::InvalidInput));
662 assert_eq!(decode(b" "), Err(DecodeError::InvalidInput));
663 }
664
665 #[test]
666 fn decode_odd_length() {
667 assert_eq!(decode(b"0"), Err(DecodeError::InvalidInputLength));
668 assert_eq!(decode(b"abc"), Err(DecodeError::InvalidInputLength));
669 }
670
671 #[test]
672 fn decode_trailing_invalid_in_large_buffer() {
673 let mut input = alloc::vec![b'0'; 64];
674 input[63] = b'g';
675 assert_eq!(decode(&input), Err(DecodeError::InvalidInput));
676 }
677
678 #[test]
679 fn roundtrip() {
680 let data: Vec<u8> = (0..=255).cycle().take(1024).collect();
681 let hex = encode(&data);
682 let decoded = decode(hex.as_bytes()).unwrap();
683 assert_eq!(decoded, data);
684 }
685
686 #[test]
687 fn roundtrip_upper() {
688 let data: Vec<u8> = (0..=255).cycle().take(1024).collect();
689 let hex = encode_with_alphabet(&data, Alphabet::Upper);
690 let decoded = decode(&hex).unwrap();
691 assert_eq!(decoded, data);
692 }
693
694 #[test]
695 fn roundtrip_various_sizes() {
696 for len in [0, 1, 2, 3, 4, 5, 15, 16, 17, 31, 32, 33, 63, 64, 65, 95, 96] {
697 let data: Vec<u8> = (0..len as u8).collect();
698 let hex = encode(&data);
699 let decoded = decode(hex.as_bytes()).unwrap();
700 assert_eq!(decoded, data, "roundtrip failed for len={}", len);
701 }
702 }
703
704 #[test]
705 fn decode_case_insensitivity() {
706 assert_eq!(decode(b"abcdef"), decode(b"ABCDEF"));
707 assert_eq!(decode(b"AbCdEf"), decode(b"aBcDeF"));
708 }
709
710 #[test]
711 fn rfc4648_test_vectors_encode() {
712 let vectors = [
713 (b"" as &[u8], ""),
714 (b"f", "66"),
715 (b"fo", "666F"),
716 (b"foo", "666F6F"),
717 (b"foob", "666F6F62"),
718 (b"fooba", "666F6F6261"),
719 (b"foobar", "666F6F626172"),
720 ];
721 for (input, expected) in &vectors {
722 assert_eq!(encode_with_alphabet(input, Alphabet::Upper), *expected);
723 }
724 }
725
726 #[test]
727 fn rfc4648_test_vectors_decode() {
728 let vectors = [
729 ("", b"" as &[u8]),
730 ("66", b"f"),
731 ("666F", b"fo"),
732 ("666F6F", b"foo"),
733 ("666F6F62", b"foob"),
734 ("666F6F6261", b"fooba"),
735 ("666F6F626172", b"foobar"),
736 ];
737 for (hex_str, expected) in &vectors {
738 assert_eq!(decode(hex_str.as_bytes()).unwrap(), *expected);
739 }
740 }
741
742 #[test]
743 fn rfc4648_test_vectors_lowercase() {
744 let vectors = [
745 ("66", b"f" as &[u8]),
746 ("666f", b"fo" as &[u8]),
747 ("666f6f", b"foo" as &[u8]),
748 ("666f6f62", b"foob" as &[u8]),
749 ("666f6f6261", b"fooba" as &[u8]),
750 ("666f6f626172", b"foobar" as &[u8]),
751 ];
752 for (hex_str, expected) in &vectors {
753 assert_eq!(decode(hex_str.as_bytes()).unwrap(), *expected);
754 }
755 }
756
757 #[test]
758 fn simd_boundary_nonuniform() {
759 let sizes = [
760 0, 1, 2, 3, 15, 16, 17, 31, 32, 33, 63, 64, 65, 95, 96, 127, 128, 129, 255, 256, 257,
761 ];
762 for &len in &sizes {
763 let data: Vec<u8> = (0..len)
764 .map(|i: usize| (i.wrapping_mul(17).wrapping_add(0xAB)) as u8)
765 .collect();
766 let hex = encode(&data);
767 let decoded = decode(hex.as_bytes()).unwrap();
768 assert_eq!(decoded, data, "non-uniform roundtrip failed for len={}", len);
769 }
770 }
771
772 #[test]
773 fn decode_into_too_small() {
774 let mut out = [0u8; 1];
775 assert_eq!(decode_into(&mut out, b"0000"), Err(DecodeError::InvalidOutputLength));
776 }
777
778 #[test]
779 fn encode_into_panics_on_too_small() {
780 use std::panic::{AssertUnwindSafe, catch_unwind};
781 let mut out = [0u8; 1];
782 let result = catch_unwind(AssertUnwindSafe(|| {
783 encode_into(&mut out, b"hello", Alphabet::Lower).unwrap();
784 }));
785 assert!(result.is_err());
786 }
787
788 #[cfg(feature = "serde")]
789 #[test]
790 fn serde_roundtrip() {
791 #[derive(::serde::Serialize, ::serde::Deserialize)]
792 struct Data(#[serde(with = "crate::serde")] Vec<u8>);
793
794 let data = Data(b"hello world".to_vec());
795 let json = ::serde_json::to_string(&data).unwrap();
796 assert_eq!(json, "\"68656c6c6f20776f726c64\"");
797 let deserialized: Data = ::serde_json::from_str(&json).unwrap();
798 assert_eq!(deserialized.0, b"hello world");
799 }
800
801 #[test]
802 fn const_encode() {
803 const DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
804 const HEX: [u8; 8] = encode_array::<8>(&DATA, Alphabet::Lower);
805 const HEX_UPPER: [u8; 8] = encode_array::<8>(&DATA, Alphabet::Upper);
806 assert_eq!(&HEX, b"deadbeef");
807 assert_eq!(&HEX_UPPER, b"DEADBEEF");
808 }
809
810 #[test]
811 fn const_encode_empty() {
812 const HEX: [u8; 0] = encode_array::<0>(b"", Alphabet::Lower);
813 assert_eq!(HEX.len(), 0);
814 }
815
816 #[test]
817 fn const_decode() {
818 const RESULT: Result<[u8; 4], DecodeError> = decode_array::<4>(b"deadbeef");
819 assert_eq!(RESULT.unwrap(), [0xDE, 0xAD, 0xBE, 0xEF]);
820 }
821
822 #[test]
823 fn const_decode_empty() {
824 const RESULT: Result<[u8; 0], DecodeError> = decode_array::<0>(b"");
825 assert_eq!(RESULT.unwrap().len(), 0);
826 }
827
828 #[test]
829 fn const_decode_upper() {
830 const RESULT: Result<[u8; 4], DecodeError> = decode_array::<4>(b"DEADBEEF");
831 assert_eq!(RESULT.unwrap(), [0xDE, 0xAD, 0xBE, 0xEF]);
832 }
833
834 #[test]
835 fn const_decode_invalid_character() {
836 const ERR: Result<[u8; 1], DecodeError> = decode_array::<1>(b"0g");
837 assert_eq!(ERR, Err(DecodeError::InvalidInput));
838 }
839
840 #[test]
841 fn const_decode_odd_length() {
842 const ERR: Result<[u8; 0], DecodeError> = decode_array::<0>(b"0");
843 assert_eq!(ERR, Err(DecodeError::InvalidInputLength));
844 }
845
846 #[test]
847 fn const_decode_wrong_output_size() {
848 const ERR: Result<[u8; 2], DecodeError> = decode_array::<2>(b"00");
849 assert_eq!(ERR, Err(DecodeError::InvalidOutputLength));
850 }
851
852 #[test]
853 fn encode_into_string_empty() {
854 let mut s = alloc::string::String::new();
855 encode_into_string(&mut s, b"", Alphabet::Lower);
856 assert_eq!(s, "");
857 }
858
859 #[test]
860 fn encode_into_string_empty_data_nonempty_output() {
861 let mut s = alloc::string::String::from("prefix");
862 encode_into_string(&mut s, b"", Alphabet::Lower);
863 assert_eq!(s, "prefix");
864 }
865
866 #[test]
867 fn encode_into_string_single_byte() {
868 let mut s = alloc::string::String::new();
869 encode_into_string(&mut s, b"\x00", Alphabet::Lower);
870 assert_eq!(s, "00");
871 let mut s = alloc::string::String::new();
872 encode_into_string(&mut s, b"\xFF", Alphabet::Upper);
873 assert_eq!(s, "FF");
874 }
875
876 #[test]
877 fn encode_into_string_multiple_bytes() {
878 let mut s = alloc::string::String::new();
879 encode_into_string(&mut s, b"hello", Alphabet::Lower);
880 assert_eq!(s, "68656c6c6f");
881 let mut s = alloc::string::String::new();
882 encode_into_string(&mut s, b"hello", Alphabet::Upper);
883 assert_eq!(s, "68656C6C6F");
884 }
885
886 #[test]
887 fn encode_into_string_append() {
888 let mut s = alloc::string::String::from("~~");
889 encode_into_string(&mut s, b"\xDE\xAD", Alphabet::Lower);
890 assert_eq!(s, "~~dead");
891 encode_into_string(&mut s, b"\xBE\xEF", Alphabet::Lower);
892 assert_eq!(s, "~~deadbeef");
893 }
894
895 #[test]
896 fn encode_into_string_large() {
897 let data: Vec<u8> = (0..255).cycle().take(4096).collect();
898 let expected = encode_with_alphabet(&data, Alphabet::Lower);
899 let mut s = alloc::string::String::new();
900 encode_into_string(&mut s, &data, Alphabet::Lower);
901 assert_eq!(s, expected);
902 }
903
904 #[test]
905 fn encode_into_string_roundtrip() {
906 let data: Vec<u8> = (0..=255).collect();
907 let mut s = alloc::string::String::new();
908 encode_into_string(&mut s, &data, Alphabet::Lower);
909 let decoded = decode(s.as_bytes()).unwrap();
910 assert_eq!(decoded, data);
911 }
912
913 #[test]
914 fn encode_into_string_small_boundary() {
915 let mut s = alloc::string::String::new();
916 encode_into_string(
917 &mut s,
918 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
919 Alphabet::Lower,
920 );
921 assert_eq!(s, "000102030405060708090a0b0c0d0e0f");
922 }
923
924 #[test]
925 fn encode_into_string_all_alphabets() {
926 let data = b"hello world";
927 for alphabet in &[Alphabet::Lower, Alphabet::Upper] {
928 let expected = encode_with_alphabet(data, *alphabet);
929 let mut s = alloc::string::String::new();
930 encode_into_string(&mut s, data, *alphabet);
931 assert_eq!(s, expected, "mismatch for alphabet {alphabet:?}");
932 }
933 }
934
935 #[test]
936 fn encode_into_string_rfc4648_vectors() {
937 let vectors = [
938 (b"" as &[u8], "", ""),
939 (b"f", "66", "66"),
940 (b"fo", "666f", "666F"),
941 (b"foo", "666f6f", "666F6F"),
942 (b"foob", "666f6f62", "666F6F62"),
943 (b"fooba", "666f6f6261", "666F6F6261"),
944 (b"foobar", "666f6f626172", "666F6F626172"),
945 ];
946 for (input, expected_lower, expected_upper) in &vectors {
947 let mut s = alloc::string::String::new();
948 encode_into_string(&mut s, input, Alphabet::Lower);
949 assert_eq!(s, *expected_lower);
950 let mut s = alloc::string::String::new();
951 encode_into_string(&mut s, input, Alphabet::Upper);
952 assert_eq!(s, *expected_upper);
953 }
954 }
955
956 #[test]
957 fn encode_into_string_exact_stack_capacity() {
958 let data: Vec<u8> = (0..128).collect();
959 let expected = encode(&data);
960 let mut s = alloc::string::String::new();
961 encode_into_string(&mut s, &data, Alphabet::Lower);
962 assert_eq!(s, expected);
963 }
964
965 #[test]
966 fn encode_into_string_exceeds_stack_capacity() {
967 let data: Vec<u8> = (0..129).collect();
968 let expected = encode(&data);
969 let mut s = alloc::string::String::new();
970 encode_into_string(&mut s, &data, Alphabet::Lower);
971 assert_eq!(s, expected);
972 }
973}