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