lowercase_hex/
lib.rs

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