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