#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(
feature = "alloc",
doc = r#"
# Owned output and capacity reuse
[`hex_decode_vec`] returns owned decoded bytes; [`hex_string`] returns owned
text. [`hex_append`] preserves existing text and returns only its new suffix.
These functions follow the allocator's normal error handling. Allocation failures
are not codec errors.
```
use faster_hex::{hex_append, hex_decode_vec};
let bytes = hex_decode_vec(b"00aB")?;
let mut text = String::with_capacity(64);
text.push_str("id: ");
assert_eq!(hex_append(&bytes, &mut text), "00ab");
assert_eq!(text, "id: 00ab");
# Ok::<(), faster_hex::Error>(())
```
"#
)]
#![cfg_attr(
feature = "serde",
doc = r##"
# Serde adapters
The default `#[serde(with = "faster_hex")]` adapter writes lowercase hex with a
`0x` prefix and accepts either letter case when reading. A required prefix is
exactly `0x`, never `0X`. Named modules select the wire policy:
| Module | Prefix | Serialization | Accepted letters |
| --- | --- | --- | --- |
| [`withpfx_ignorecase`] (default) | `0x` | Lowercase | Either case |
| [`nopfx_ignorecase`] | None | Lowercase | Either case |
| [`withpfx_lowercase`] | `0x` | Lowercase | Lowercase |
| [`nopfx_lowercase`] | None | Lowercase | Lowercase |
| [`withpfx_uppercase`] | `0x` | Uppercase | Uppercase |
| [`nopfx_uppercase`] | None | Uppercase | Uppercase |
Each policy also has an `option_` counterpart, an `array` submodule, and a
`deserialize_bounded` function. Present byte values use strings, including in
binary formats. Option adapters preserve the format's `Some`/`None` tags; an empty
present value stays distinct from `None`. For missing struct fields, add
`#[serde(default)]` alongside the `with` attribute.
Use [`array`](mod@crate::array) for exact-length arrays. Generic adapters instead
collect into [`FromIterator<u8>`](core::iter::FromIterator) containers; bounded
collectors can panic when full. [`deserialize_bounded`] limits decoded bytes before output allocation,
but does not bound the format's input storage or a custom collector's allocations.
```
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct Record {
#[serde(with = "faster_hex::array")]
id: [u8; 2],
#[serde(default, with = "faster_hex::option_nopfx_lowercase::array")]
extra: Option<[u8; 2]>,
}
let record = Record { id: [0xab, 1], extra: Some([0xcd, 2]) };
let json = serde_json::to_string(&record)?;
assert_eq!(json, r#"{"id":"0xab01","extra":"cd02"}"#);
assert_eq!(serde_json::from_str::<Record>(&json)?, record);
assert_eq!(serde_json::from_str::<Record>(r#"{"id":"0xab01"}"#)?.extra, None);
# Ok::<(), serde_json::Error>(())
```
"##
)]
#[cfg(feature = "alloc")]
extern crate alloc;
mod decode;
mod encode;
mod error;
mod format;
#[cfg(any(test, fuzzing))]
#[doc(hidden)]
#[allow(missing_docs)]
pub mod fuzzing;
#[cfg(feature = "heapless-08")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless-08")))]
pub mod heapless_08;
#[cfg(feature = "serde")]
mod serde;
pub use crate::decode::{
hex_check, hex_check_with_case, hex_decode, hex_decode_array, hex_decode_array_with_case,
hex_decode_with_case, CheckCase,
};
pub use crate::encode::{hex_encode, hex_encode_upper};
#[cfg(feature = "alloc")]
pub use crate::encode::{hex_append, hex_append_upper, hex_string, hex_string_upper};
#[cfg(feature = "alloc")]
pub use crate::decode::{hex_decode_vec, hex_decode_vec_with_case};
pub use crate::error::Error;
pub use crate::format::Hex;
#[cfg(feature = "serde")]
pub use crate::serde::withpfx_ignorecase::array;
#[cfg(feature = "serde")]
pub use crate::serde::{
deserialize, deserialize_bounded, nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase,
option_nopfx_ignorecase, option_nopfx_lowercase, option_nopfx_uppercase,
option_withpfx_ignorecase, option_withpfx_lowercase, option_withpfx_uppercase, serialize,
withpfx_ignorecase, withpfx_lowercase, withpfx_uppercase,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[allow(dead_code, reason = "Static ISA baselines bypass runtime detection")]
pub(crate) enum Vectorization {
None = 0,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
SSE41 = 1,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
AVX2 = 2,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
AVX512 = 128,
#[cfg(target_arch = "aarch64")]
Neon = 3,
}
#[inline(always)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn vectorization_support() -> Vectorization {
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "avx512bw",
not(miri)
))]
{
return Vectorization::AVX512;
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "avx2",
not(target_feature = "avx512bw"),
not(miri)
))]
{
return Vectorization::AVX2;
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse",
not(target_feature = "avx2"),
not(miri)
))]
{
use core::sync::atomic::{AtomicU8, Ordering};
static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
return match FLAGS.load(Ordering::Relaxed) {
0 => Vectorization::None,
1 => Vectorization::SSE41,
2 => Vectorization::AVX2,
128 => Vectorization::AVX512,
_ => {
let backend = vectorization_support_no_cache_x86();
FLAGS.store(backend as u8, Ordering::Relaxed);
backend
}
};
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))]
{
return Vectorization::Neon;
}
#[allow(unreachable_code)]
Vectorization::None
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse",
not(target_feature = "avx2"),
not(miri)
))]
#[cold]
fn vectorization_support_no_cache_x86() -> Vectorization {
#[cfg(target_arch = "x86")]
use core::arch::x86::__cpuid_count;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::__cpuid_count;
if cfg!(target_env = "sgx") || !cfg!(target_feature = "sse") {
return Vectorization::None;
}
let max_leaf = __cpuid_count(0, 0).eax;
if max_leaf < 1 {
return Vectorization::None;
}
let proc_info_ecx = __cpuid_count(1, 0).ecx;
let have_sse4 = (proc_info_ecx >> 19) & 1 == 1;
if !have_sse4 {
return Vectorization::None;
}
let have_xsave = (proc_info_ecx >> 26) & 1 == 1;
let have_osxsave = (proc_info_ecx >> 27) & 1 == 1;
let have_avx = (proc_info_ecx >> 28) & 1 == 1;
if max_leaf >= 7 && have_xsave && have_osxsave && have_avx {
return unsafe { avx_support_no_cache_x86() };
}
Vectorization::SSE41
}
#[target_feature(enable = "xsave")]
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse",
not(target_feature = "avx2"),
not(miri)
))]
#[cold]
unsafe fn avx_support_no_cache_x86() -> Vectorization {
#[cfg(target_arch = "x86")]
use core::arch::x86::{__cpuid_count, _xgetbv};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{__cpuid_count, _xgetbv};
let xcr0 = _xgetbv(0);
let os_avx_support = xcr0 & 6 == 6;
if os_avx_support {
let extended_features_ebx = __cpuid_count(7, 0).ebx;
let have_avx2 = (extended_features_ebx >> 5) & 1 == 1;
if have_avx2 {
let avx512 = (1 << 16) | (1 << 30); if xcr0 & 0xe6 == 0xe6 && extended_features_ebx & avx512 == avx512 {
return Vectorization::AVX512;
}
return Vectorization::AVX2;
}
}
Vectorization::SSE41
}
#[cfg(test)]
mod tests;