const_hex/lib.rs
1//! [![github]](https://github.com/danipopes/const-hex) [![crates-io]](https://crates.io/crates/const-hex) [![docs-rs]](https://docs.rs/const-hex)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! This crate provides a fast conversion of byte arrays to hexadecimal strings,
8//! both at compile time, and at run time.
9//!
10//! It aims to be a drop-in replacement for the [`hex`] crate, as well as
11//! extending the API with [const-eval](const_encode), a
12//! [const-generics formatting buffer](Buffer), similar to [`itoa`]'s, and more.
13//!
14//! _Version requirement: rustc 1.64+_
15//!
16//! [`itoa`]: https://docs.rs/itoa/latest/itoa/struct.Buffer.html
17//! [`hex`]: https://docs.rs/hex
18
19#![cfg_attr(not(feature = "std"), no_std)]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21#![cfg_attr(
22 feature = "nightly",
23 feature(core_intrinsics, inline_const),
24 allow(internal_features, stable_features)
25)]
26#![cfg_attr(
27 feature = "portable-simd",
28 feature(portable_simd),
29 allow(unused_features)
30)]
31#![warn(
32 missing_copy_implementations,
33 missing_debug_implementations,
34 missing_docs,
35 unreachable_pub,
36 unsafe_op_in_unsafe_fn,
37 clippy::missing_const_for_fn,
38 clippy::missing_inline_in_public_items,
39 clippy::all,
40 rustdoc::all
41)]
42#![cfg_attr(not(any(test, feature = "__fuzzing")), warn(unused_crate_dependencies))]
43#![deny(unused_must_use, rust_2018_idioms)]
44#![allow(
45 clippy::cast_lossless,
46 clippy::inline_always,
47 clippy::let_unit_value,
48 clippy::must_use_candidate,
49 clippy::wildcard_imports,
50 unsafe_op_in_unsafe_fn,
51 unused_unsafe
52)]
53
54#[cfg(feature = "alloc")]
55#[allow(unused_imports)]
56#[macro_use]
57extern crate alloc;
58
59use cfg_if::cfg_if;
60use core::mem::MaybeUninit;
61
62#[cfg(feature = "alloc")]
63#[allow(unused_imports)]
64use alloc::{string::String, vec::Vec};
65
66// `cpufeatures` may be unused when `force-generic` is enabled.
67#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68use cpufeatures as _;
69
70mod arch;
71use arch::{generic, imp};
72
73mod impl_core;
74
75pub mod traits;
76#[cfg(feature = "alloc")]
77pub use traits::ToHexExt;
78
79mod display;
80pub use display::display;
81
82mod error;
83pub use error::FromHexError;
84
85#[allow(deprecated)]
86pub use traits::{FromHex, ToHex};
87
88// Support for nightly features.
89cfg_if! {
90 if #[cfg(feature = "nightly")] {
91 // Branch prediction hints.
92 #[allow(unused_imports)]
93 use core::intrinsics::{likely, unlikely};
94
95 // `inline_const`: [#76001](https://github.com/rust-lang/rust/issues/76001)
96 macro_rules! maybe_const_assert {
97 ($($tt:tt)*) => {
98 const { assert!($($tt)*) }
99 };
100 }
101 } else {
102 #[allow(unused_imports)]
103 use core::convert::{identity as likely, identity as unlikely};
104
105 macro_rules! maybe_const_assert {
106 ($($tt:tt)*) => {
107 assert!($($tt)*)
108 };
109 }
110 }
111}
112
113// Serde support.
114cfg_if! {
115 if #[cfg(feature = "serde")] {
116 pub mod serde;
117
118 #[doc(no_inline)]
119 pub use self::serde::{deserialize, serialize, serialize_upper};
120 }
121}
122
123mod buffer;
124pub use buffer::Buffer;
125
126mod output;
127use output::Output;
128
129/// The table of lowercase characters used for hex encoding.
130pub const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
131
132/// The table of uppercase characters used for hex encoding.
133pub const HEX_CHARS_UPPER: &[u8; 16] = b"0123456789ABCDEF";
134
135/// The lookup table of hex byte to value, used for hex decoding.
136///
137/// [`NIL`] is used for invalid values.
138pub const HEX_DECODE_LUT: &[u8; 256] = &make_decode_lut();
139
140/// Represents an invalid value in the [`HEX_DECODE_LUT`] table.
141pub const NIL: u8 = u8::MAX;
142
143/// Encodes `input` as a hex string into a [`Buffer`].
144///
145/// # Examples
146///
147/// ```
148/// const BUFFER: const_hex::Buffer<4> = const_hex::const_encode(b"kiwi");
149/// assert_eq!(BUFFER.as_str(), "6b697769");
150/// ```
151#[inline]
152pub const fn const_encode<const N: usize, const PREFIX: bool>(
153 input: &[u8; N],
154) -> Buffer<N, PREFIX> {
155 Buffer::new().const_format(input)
156}
157
158/// Encodes `input` as a hex string using lowercase characters into a mutable
159/// slice of bytes `output`.
160///
161/// # Errors
162///
163/// If the output buffer is not exactly `input.len() * 2` bytes long.
164///
165/// # Examples
166///
167/// ```
168/// let mut bytes = [0u8; 4 * 2];
169/// const_hex::encode_to_slice(b"kiwi", &mut bytes)?;
170/// assert_eq!(&bytes, b"6b697769");
171/// # Ok::<_, const_hex::FromHexError>(())
172/// ```
173#[inline]
174pub fn encode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
175 encode_to_slice_inner::<false>(input.as_ref(), output)
176}
177
178/// Encodes `input` as a hex string using uppercase characters into a mutable
179/// slice of bytes `output`.
180///
181/// # Errors
182///
183/// If the output buffer is not exactly `input.len() * 2` bytes long.
184///
185/// # Examples
186///
187/// ```
188/// let mut bytes = [0u8; 4 * 2];
189/// const_hex::encode_to_slice_upper(b"kiwi", &mut bytes)?;
190/// assert_eq!(&bytes, b"6B697769");
191/// # Ok::<_, const_hex::FromHexError>(())
192/// ```
193#[inline]
194pub fn encode_to_slice_upper<T: AsRef<[u8]>>(
195 input: T,
196 output: &mut [u8],
197) -> Result<(), FromHexError> {
198 encode_to_slice_inner::<true>(input.as_ref(), output)
199}
200
201/// Encodes `input` as a hex string using lowercase characters into a mutable
202/// slice of bytes `output`. Returns the slice to `output` reinterpreted as
203/// `str`.
204///
205/// # Errors
206///
207/// If the output buffer is not exactly `input.len() * 2` bytes long.
208///
209/// # Examples
210///
211/// ```
212/// let mut bytes = [0u8; 4 * 2];
213/// let s = const_hex::encode_to_str(b"kiwi", &mut bytes)?;
214/// assert_eq!(s, "6b697769");
215/// # Ok::<_, const_hex::FromHexError>(())
216/// ```
217#[inline]
218pub fn encode_to_str<T: AsRef<[u8]>>(
219 input: T,
220 output: &mut [u8],
221) -> Result<&mut str, FromHexError> {
222 encode_to_str_inner::<false>(input.as_ref(), output)
223}
224
225/// Encodes `input` as a hex string using uppercase characters into a mutable
226/// slice of bytes `output`. Returns the slice to `output` reinterpreted as
227/// `str`.
228///
229/// # Errors
230///
231/// If the output buffer is not exactly `input.len() * 2` bytes long.
232///
233/// # Examples
234///
235/// ```
236/// let mut bytes = [0u8; 4 * 2];
237/// let s = const_hex::encode_to_str_upper(b"kiwi", &mut bytes)?;
238/// assert_eq!(s, "6B697769");
239/// # Ok::<_, const_hex::FromHexError>(())
240/// ```
241#[inline]
242pub fn encode_to_str_upper<T: AsRef<[u8]>>(
243 input: T,
244 output: &mut [u8],
245) -> Result<&mut str, FromHexError> {
246 encode_to_str_inner::<true>(input.as_ref(), output)
247}
248
249/// Encodes `data` as a hex string using lowercase characters.
250///
251/// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's
252/// length is always even, each byte in `data` is always encoded using two hex
253/// digits. Thus, the resulting string contains exactly twice as many bytes as
254/// the input data.
255///
256/// # Examples
257///
258/// ```
259/// assert_eq!(const_hex::encode("Hello world!"), "48656c6c6f20776f726c6421");
260/// assert_eq!(const_hex::encode([1, 2, 3, 15, 16]), "0102030f10");
261/// ```
262#[cfg(feature = "alloc")]
263#[inline]
264pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
265 encode_inner::<false, false>(data.as_ref())
266}
267
268/// Encodes `data` as a hex string using uppercase characters.
269///
270/// Apart from the characters' casing, this works exactly like `encode()`.
271///
272/// # Examples
273///
274/// ```
275/// assert_eq!(const_hex::encode_upper("Hello world!"), "48656C6C6F20776F726C6421");
276/// assert_eq!(const_hex::encode_upper([1, 2, 3, 15, 16]), "0102030F10");
277/// ```
278#[cfg(feature = "alloc")]
279#[inline]
280pub fn encode_upper<T: AsRef<[u8]>>(data: T) -> String {
281 encode_inner::<true, false>(data.as_ref())
282}
283
284/// Encodes `data` as a prefixed hex string using lowercase characters.
285///
286/// See [`encode()`] for more details.
287///
288/// # Examples
289///
290/// ```
291/// assert_eq!(const_hex::encode_prefixed("Hello world!"), "0x48656c6c6f20776f726c6421");
292/// assert_eq!(const_hex::encode_prefixed([1, 2, 3, 15, 16]), "0x0102030f10");
293/// ```
294#[cfg(feature = "alloc")]
295#[inline]
296pub fn encode_prefixed<T: AsRef<[u8]>>(data: T) -> String {
297 encode_inner::<false, true>(data.as_ref())
298}
299
300/// Encodes `data` as a prefixed hex string using uppercase characters.
301///
302/// See [`encode_upper()`] for more details.
303///
304/// # Examples
305///
306/// ```
307/// assert_eq!(const_hex::encode_upper_prefixed("Hello world!"), "0x48656C6C6F20776F726C6421");
308/// assert_eq!(const_hex::encode_upper_prefixed([1, 2, 3, 15, 16]), "0x0102030F10");
309/// ```
310#[cfg(feature = "alloc")]
311#[inline]
312pub fn encode_upper_prefixed<T: AsRef<[u8]>>(data: T) -> String {
313 encode_inner::<true, true>(data.as_ref())
314}
315
316/// Returns `true` if the input is a valid hex string and can be decoded successfully.
317///
318/// Prefer using [`check`] instead when possible (at runtime), as it is likely to be faster.
319///
320/// # Examples
321///
322/// ```
323/// const _: () = {
324/// assert!(const_hex::const_check(b"48656c6c6f20776f726c6421").is_ok());
325/// assert!(const_hex::const_check(b"0x48656c6c6f20776f726c6421").is_ok());
326/// assert!(const_hex::const_check(b"0X48656C6C6F20776F726C6421").is_ok());
327///
328/// assert!(const_hex::const_check(b"48656c6c6f20776f726c642").is_err());
329/// assert!(const_hex::const_check(b"Hello world!").is_err());
330/// };
331/// ```
332#[inline]
333pub const fn const_check(input: &[u8]) -> Result<(), FromHexError> {
334 if input.len() % 2 != 0 {
335 return Err(FromHexError::OddLength);
336 }
337 let input = strip_prefix(input);
338 if const_check_raw(input) {
339 Ok(())
340 } else {
341 Err(unsafe { invalid_hex_error(input) })
342 }
343}
344
345/// Returns `true` if the input is a valid hex string.
346///
347/// Note that this does not check prefixes or length, but just the contents of the string.
348///
349/// Prefer using [`check_raw`] instead when possible (at runtime), as it is likely to be faster.
350///
351/// # Examples
352///
353/// ```
354/// const _: () = {
355/// assert!(const_hex::const_check_raw(b"48656c6c6f20776f726c6421"));
356///
357/// // Odd length, but valid hex
358/// assert!(const_hex::const_check_raw(b"48656c6c6f20776f726c642"));
359///
360/// // Valid hex string, but the prefix is not valid
361/// assert!(!const_hex::const_check_raw(b"0x48656c6c6f20776f726c6421"));
362///
363/// assert!(!const_hex::const_check_raw(b"Hello world!"));
364/// };
365/// ```
366#[inline]
367pub const fn const_check_raw(input: &[u8]) -> bool {
368 generic::check(input)
369}
370
371/// Returns `true` if the input is a valid hex string and can be decoded successfully.
372///
373/// # Examples
374///
375/// ```
376/// assert!(const_hex::check("48656c6c6f20776f726c6421").is_ok());
377/// assert!(const_hex::check("0x48656c6c6f20776f726c6421").is_ok());
378/// assert!(const_hex::check("0X48656C6C6F20776F726C6421").is_ok());
379///
380/// assert!(const_hex::check("48656c6c6f20776f726c642").is_err());
381/// assert!(const_hex::check("Hello world!").is_err());
382/// ```
383#[inline]
384pub fn check<T: AsRef<[u8]>>(input: T) -> Result<(), FromHexError> {
385 #[allow(clippy::missing_const_for_fn)]
386 fn check_inner(input: &[u8]) -> Result<(), FromHexError> {
387 if input.len() % 2 != 0 {
388 return Err(FromHexError::OddLength);
389 }
390 let stripped = strip_prefix(input);
391 if imp::check(stripped) {
392 Ok(())
393 } else {
394 let mut e = unsafe { invalid_hex_error(stripped) };
395 if let FromHexError::InvalidHexCharacter { ref mut index, .. } = e {
396 *index += input.len() - stripped.len();
397 }
398 Err(e)
399 }
400 }
401
402 check_inner(input.as_ref())
403}
404
405/// Returns `true` if the input is a valid hex string.
406///
407/// Note that this does not check prefixes or length, but just the contents of the string.
408///
409/// # Examples
410///
411/// ```
412/// assert!(const_hex::check_raw("48656c6c6f20776f726c6421"));
413///
414/// // Odd length, but valid hex
415/// assert!(const_hex::check_raw("48656c6c6f20776f726c642"));
416///
417/// // Valid hex string, but the prefix is not valid
418/// assert!(!const_hex::check_raw("0x48656c6c6f20776f726c6421"));
419///
420/// assert!(!const_hex::check_raw("Hello world!"));
421/// ```
422#[inline]
423pub fn check_raw<T: AsRef<[u8]>>(input: T) -> bool {
424 imp::check(input.as_ref())
425}
426
427/// Decode a hex string into a fixed-length byte-array.
428///
429/// Both, upper and lower case characters are valid in the input string and can
430/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
431///
432/// Strips the `0x` prefix if present.
433///
434/// Prefer using [`decode_to_array`] instead when possible (at runtime), as it is likely to be faster.
435///
436/// # Errors
437///
438/// This function returns an error if the input is not an even number of
439/// characters long or contains invalid hex characters, or if the input is not
440/// exactly `N * 2` bytes long.
441///
442/// # Example
443///
444/// ```
445/// const _: () = {
446/// let bytes = const_hex::const_decode_to_array(b"6b697769");
447/// assert!(matches!(bytes.as_ref(), Ok(b"kiwi")));
448///
449/// let bytes = const_hex::const_decode_to_array(b"0x6b697769");
450/// assert!(matches!(bytes.as_ref(), Ok(b"kiwi")));
451///
452/// let bytes = const_hex::const_decode_to_array(b"0X6B697769");
453/// assert!(matches!(bytes.as_ref(), Ok(b"kiwi")));
454/// };
455/// ```
456#[inline]
457pub const fn const_decode_to_array<const N: usize>(input: &[u8]) -> Result<[u8; N], FromHexError> {
458 if input.len() % 2 != 0 {
459 return Err(FromHexError::OddLength);
460 }
461 let input = strip_prefix(input);
462 if input.len() != N * 2 {
463 return Err(FromHexError::InvalidStringLength);
464 }
465 match const_decode_to_array_impl(input) {
466 Some(output) => Ok(output),
467 None => Err(unsafe { invalid_hex_error(input) }),
468 }
469}
470
471const fn const_decode_to_array_impl<const N: usize>(input: &[u8]) -> Option<[u8; N]> {
472 macro_rules! next {
473 ($var:ident, $i:expr) => {
474 let hex = unsafe { *input.as_ptr().add($i) };
475 let $var = HEX_DECODE_LUT[hex as usize];
476 if $var == NIL {
477 return None;
478 }
479 };
480 }
481
482 let mut output = [0; N];
483 debug_assert!(input.len() == N * 2);
484 let mut i = 0;
485 while i < output.len() {
486 next!(high, i * 2);
487 next!(low, i * 2 + 1);
488 output[i] = high << 4 | low;
489 i += 1;
490 }
491 Some(output)
492}
493
494/// Decodes a hex string into raw bytes.
495///
496/// Both, upper and lower case characters are valid in the input string and can
497/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
498///
499/// Strips the `0x` prefix if present.
500///
501/// # Errors
502///
503/// This function returns an error if the input is not an even number of
504/// characters long or contains invalid hex characters.
505///
506/// # Example
507///
508/// ```
509/// assert_eq!(
510/// const_hex::decode("48656c6c6f20776f726c6421"),
511/// Ok("Hello world!".to_owned().into_bytes())
512/// );
513/// assert_eq!(
514/// const_hex::decode("0x48656c6c6f20776f726c6421"),
515/// Ok("Hello world!".to_owned().into_bytes())
516/// );
517///
518/// assert_eq!(const_hex::decode("123"), Err(const_hex::FromHexError::OddLength));
519/// assert!(const_hex::decode("foo").is_err());
520/// ```
521#[cfg(feature = "alloc")]
522#[inline]
523pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, FromHexError> {
524 fn decode_inner(input: &[u8]) -> Result<Vec<u8>, FromHexError> {
525 if unlikely(input.len() % 2 != 0) {
526 return Err(FromHexError::OddLength);
527 }
528 let input = strip_prefix(input);
529
530 // Do not initialize memory since it will be entirely overwritten.
531 let len = input.len() / 2;
532 let mut output = Vec::with_capacity(len);
533
534 // SAFETY: `decode_checked` fully writes `len` bytes on success,
535 // then `set_len` is called only after successful decode.
536 unsafe {
537 decode_checked(input, &mut output.spare_capacity_mut()[..len])?;
538 output.set_len(len);
539 }
540 Ok(output)
541 }
542
543 decode_inner(input.as_ref())
544}
545
546/// Decode a hex string into a mutable bytes slice.
547///
548/// Both, upper and lower case characters are valid in the input string and can
549/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
550///
551/// Strips the `0x` prefix if present.
552///
553/// # Errors
554///
555/// This function returns an error if the input is not an even number of
556/// characters long or contains invalid hex characters, or if the output slice
557/// is not exactly half the length of the input.
558///
559/// # Example
560///
561/// ```
562/// let mut bytes = [0u8; 4];
563/// const_hex::decode_to_slice("6b697769", &mut bytes).unwrap();
564/// assert_eq!(&bytes, b"kiwi");
565///
566/// const_hex::decode_to_slice("0x6b697769", &mut bytes).unwrap();
567/// assert_eq!(&bytes, b"kiwi");
568/// ```
569#[inline]
570pub fn decode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
571 decode_to_slice_inner(input.as_ref(), impl_core::slice_as_uninit_mut(output))
572}
573
574/// Decode a hex string into a fixed-length byte-array.
575///
576/// Both, upper and lower case characters are valid in the input string and can
577/// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
578///
579/// Strips the `0x` prefix if present.
580///
581/// # Errors
582///
583/// This function returns an error if the input is not an even number of
584/// characters long or contains invalid hex characters, or if the input is not
585/// exactly `N / 2` bytes long.
586///
587/// # Example
588///
589/// ```
590/// let bytes = const_hex::decode_to_array(b"6b697769").unwrap();
591/// assert_eq!(&bytes, b"kiwi");
592///
593/// let bytes = const_hex::decode_to_array(b"0x6b697769").unwrap();
594/// assert_eq!(&bytes, b"kiwi");
595/// ```
596#[inline]
597pub fn decode_to_array<T: AsRef<[u8]>, const N: usize>(input: T) -> Result<[u8; N], FromHexError> {
598 fn decode_to_array_inner<const N: usize>(input: &[u8]) -> Result<[u8; N], FromHexError> {
599 let mut output = impl_core::uninit_array();
600 decode_to_slice_inner(input, &mut output)?;
601 // SAFETY: All elements are initialized by successful decode.
602 Ok(unsafe { impl_core::array_assume_init(output) })
603 }
604
605 decode_to_array_inner(input.as_ref())
606}
607
608#[cfg(feature = "alloc")]
609fn encode_inner<const UPPER: bool, const PREFIX: bool>(data: &[u8]) -> String {
610 let capacity = PREFIX as usize * 2 + data.len() * 2;
611 let mut buf = Vec::<u8>::with_capacity(capacity);
612
613 // SAFETY: `spare_capacity_mut` returns uninitialized memory which is fully
614 // written to by the prefix write and `imp::encode`, then `set_len` commits.
615 let mut output = &mut buf.spare_capacity_mut()[..capacity];
616 if PREFIX {
617 // SAFETY: `output` is long enough.
618 unsafe {
619 output.get_unchecked_mut(0).write(b'0');
620 output.get_unchecked_mut(1).write(b'x');
621 output = output.get_unchecked_mut(2..);
622 }
623 }
624 // SAFETY: `output` is long enough (data.len() * 2), `encode` fully writes all bytes,
625 // then `set_len` is called only after successful encode.
626 // We only write ASCII bytes, which are valid UTF-8.
627 unsafe {
628 imp::encode::<UPPER>(data, output);
629 buf.set_len(capacity);
630 String::from_utf8_unchecked(buf)
631 }
632}
633
634fn encode_to_slice_inner<const UPPER: bool>(
635 input: &[u8],
636 output: &mut [u8],
637) -> Result<(), FromHexError> {
638 if unlikely(output.len() != 2 * input.len()) {
639 return Err(FromHexError::InvalidStringLength);
640 }
641 // SAFETY: Lengths are checked above.
642 unsafe { imp::encode::<UPPER>(input, output) };
643 Ok(())
644}
645
646fn encode_to_str_inner<'o, const UPPER: bool>(
647 input: &[u8],
648 output: &'o mut [u8],
649) -> Result<&'o mut str, FromHexError> {
650 encode_to_slice_inner::<UPPER>(input, output)?;
651 // SAFETY: encode_to_slice_inner checks the length of the output slice and
652 // overwrites it completely with only ascii characters, which are valid
653 // unicode
654 let s = unsafe { core::str::from_utf8_unchecked_mut(output) };
655 Ok(s)
656}
657
658fn decode_to_slice_inner(input: &[u8], output: &mut [MaybeUninit<u8>]) -> Result<(), FromHexError> {
659 if unlikely(input.len() % 2 != 0) {
660 return Err(FromHexError::OddLength);
661 }
662 let input = strip_prefix(input);
663 if unlikely(output.len() != input.len() / 2) {
664 return Err(FromHexError::InvalidStringLength);
665 }
666 // SAFETY: Lengths are checked above.
667 unsafe { decode_checked(input, output) }
668}
669
670/// # Safety
671///
672/// Assumes `output.len() == input.len() / 2`.
673#[inline]
674unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> Result<(), FromHexError> {
675 debug_assert_eq!(output.len(), input.len() / 2);
676
677 if imp::USE_CHECK_FN {
678 // Check then decode.
679 if imp::check(input) {
680 unsafe { imp::decode_unchecked(input, output) };
681 return Ok(());
682 }
683 } else {
684 // Check and decode at the same time.
685 if unsafe { imp::decode_checked(input, output) } {
686 return Ok(());
687 }
688 }
689
690 Err(unsafe { invalid_hex_error(input) })
691}
692
693#[inline]
694const fn byte2hex<const UPPER: bool>(byte: u8) -> (u8, u8) {
695 let table = get_chars_table::<UPPER>();
696 let high = table[(byte >> 4) as usize];
697 let low = table[(byte & 0x0f) as usize];
698 (high, low)
699}
700
701#[inline]
702const fn strip_prefix(bytes: &[u8]) -> &[u8] {
703 match bytes {
704 [b'0', b'x' | b'X', rest @ ..] => rest,
705 _ => bytes,
706 }
707}
708
709/// Creates an invalid hex error from the input.
710///
711/// # Safety
712///
713/// Assumes `input` contains at least one invalid character.
714#[cold]
715#[cfg_attr(debug_assertions, track_caller)]
716const unsafe fn invalid_hex_error(input: &[u8]) -> FromHexError {
717 // Find the first invalid character.
718 let mut index = None;
719 let mut iter = input;
720 while let [byte, rest @ ..] = iter {
721 if HEX_DECODE_LUT[*byte as usize] == NIL {
722 index = Some(input.len() - rest.len() - 1);
723 break;
724 }
725 iter = rest;
726 }
727
728 let index = match index {
729 Some(index) => index,
730 None => {
731 if cfg!(debug_assertions) {
732 panic!("input was valid but `check` failed")
733 } else {
734 unsafe { core::hint::unreachable_unchecked() }
735 }
736 }
737 };
738
739 FromHexError::InvalidHexCharacter {
740 c: input[index] as char,
741 index,
742 }
743}
744
745#[inline(always)]
746const fn get_chars_table<const UPPER: bool>() -> &'static [u8; 16] {
747 if UPPER {
748 HEX_CHARS_UPPER
749 } else {
750 HEX_CHARS_LOWER
751 }
752}
753
754const fn make_decode_lut() -> [u8; 256] {
755 let mut lut = [0; 256];
756 let mut i = 0u8;
757 loop {
758 lut[i as usize] = match i {
759 b'0'..=b'9' => i - b'0',
760 b'A'..=b'F' => i - b'A' + 10,
761 b'a'..=b'f' => i - b'a' + 10,
762 // use max value for invalid characters
763 _ => NIL,
764 };
765 if i == NIL {
766 break;
767 }
768 i += 1;
769 }
770 lut
771}
772
773#[allow(
774 missing_docs,
775 unused,
776 clippy::all,
777 clippy::missing_inline_in_public_items
778)]
779#[cfg(all(feature = "__fuzzing", not(miri)))]
780#[doc(hidden)]
781pub mod fuzzing {
782 use super::*;
783 use proptest::test_runner::TestCaseResult;
784 use proptest::{prop_assert, prop_assert_eq};
785 use std::fmt::Write;
786
787 pub fn fuzz(data: &[u8]) -> TestCaseResult {
788 self::encode(&data)?;
789 self::decode(&data)?;
790 Ok(())
791 }
792
793 pub fn encode(input: &[u8]) -> TestCaseResult {
794 test_buffer::<8, 16>(input)?;
795 test_buffer::<20, 40>(input)?;
796 test_buffer::<32, 64>(input)?;
797 test_buffer::<64, 128>(input)?;
798 test_buffer::<128, 256>(input)?;
799
800 let encoded = crate::encode(input);
801 let expected = mk_expected(input);
802 prop_assert_eq!(&encoded, &expected);
803
804 let decoded = crate::decode(&encoded).unwrap();
805 prop_assert_eq!(decoded, input);
806
807 Ok(())
808 }
809
810 pub fn decode(input: &[u8]) -> TestCaseResult {
811 if let Ok(decoded) = crate::decode(input) {
812 let input_len = strip_prefix(input).len() / 2;
813 prop_assert_eq!(decoded.len(), input_len);
814 }
815
816 Ok(())
817 }
818
819 fn mk_expected(bytes: &[u8]) -> String {
820 let mut s = String::with_capacity(bytes.len() * 2);
821 for i in bytes {
822 write!(s, "{i:02x}").unwrap();
823 }
824 s
825 }
826
827 fn test_buffer<const N: usize, const LEN: usize>(bytes: &[u8]) -> TestCaseResult {
828 if let Ok(bytes) = <&[u8; N]>::try_from(bytes) {
829 let mut buffer = Buffer::<N, false>::new();
830 let string = buffer.format(bytes).to_string();
831 prop_assert_eq!(string.len(), bytes.len() * 2);
832 prop_assert_eq!(string.as_bytes(), buffer.as_byte_array::<LEN>());
833 prop_assert_eq!(string.as_str(), buffer.as_str());
834 prop_assert_eq!(string.as_str(), mk_expected(bytes));
835
836 let mut buffer = Buffer::<N, true>::new();
837 let prefixed = buffer.format(bytes).to_string();
838 prop_assert_eq!(prefixed.len(), 2 + bytes.len() * 2);
839 prop_assert_eq!(prefixed.as_str(), buffer.as_str());
840 prop_assert_eq!(prefixed.as_str(), format!("0x{string}"));
841
842 prop_assert_eq!(decode_to_array(&string), Ok(*bytes));
843 prop_assert_eq!(decode_to_array(&prefixed), Ok(*bytes));
844 prop_assert_eq!(const_decode_to_array(string.as_bytes()), Ok(*bytes));
845 prop_assert_eq!(const_decode_to_array(prefixed.as_bytes()), Ok(*bytes));
846 }
847
848 Ok(())
849 }
850
851 proptest::proptest! {
852 #![proptest_config(proptest::prelude::ProptestConfig {
853 cases: 1024,
854 ..Default::default()
855 })]
856
857 #[test]
858 fn fuzz_encode(s in ".+") {
859 encode(s.as_bytes())?;
860 }
861
862 #[test]
863 fn fuzz_check_true(s in "[0-9a-fA-F]+") {
864 let s = s.as_bytes();
865 prop_assert!(crate::check_raw(s));
866 prop_assert!(crate::const_check_raw(s));
867 if s.len() % 2 == 0 {
868 prop_assert!(crate::check(s).is_ok());
869 prop_assert!(crate::const_check(s).is_ok());
870 }
871 }
872
873 #[test]
874 fn fuzz_check_false(s in ".{16}[0-9a-fA-F]+") {
875 let s = s.as_bytes();
876 prop_assert!(crate::check(s).is_err());
877 prop_assert!(crate::const_check(s).is_err());
878 prop_assert!(!crate::check_raw(s));
879 prop_assert!(!crate::const_check_raw(s));
880 }
881 }
882}