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