Skip to main content

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