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