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