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;
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}