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