1#![cfg_attr(not(any(test, feature = "std")), no_std)]
89#![warn(missing_docs)]
90#![cfg_attr(docsrs, feature(doc_cfg))]
91#![cfg_attr(
92 feature = "alloc",
93 doc = r#"
94# Owned output and capacity reuse
95
96[`hex_decode_vec`] returns owned decoded bytes; [`hex_string`] returns owned
97text. [`hex_append`] preserves existing text and returns only its new suffix.
98These functions follow the allocator's normal error handling. Allocation failures
99are not codec errors.
100
101```
102use faster_hex::{hex_append, hex_decode_vec};
103
104let bytes = hex_decode_vec(b"00aB")?;
105let mut text = String::with_capacity(64);
106text.push_str("id: ");
107assert_eq!(hex_append(&bytes, &mut text), "00ab");
108assert_eq!(text, "id: 00ab");
109# Ok::<(), faster_hex::Error>(())
110```
111"#
112)]
113#![cfg_attr(
114 feature = "serde",
115 doc = r##"
116# Serde adapters
117
118The default `#[serde(with = "faster_hex")]` adapter writes lowercase hex with a
119`0x` prefix and accepts either letter case when reading. A required prefix is
120exactly `0x`, never `0X`. Named modules select the wire policy:
121
122| Module | Prefix | Serialization | Accepted letters |
123| --- | --- | --- | --- |
124| [`withpfx_ignorecase`] (default) | `0x` | Lowercase | Either case |
125| [`nopfx_ignorecase`] | None | Lowercase | Either case |
126| [`withpfx_lowercase`] | `0x` | Lowercase | Lowercase |
127| [`nopfx_lowercase`] | None | Lowercase | Lowercase |
128| [`withpfx_uppercase`] | `0x` | Uppercase | Uppercase |
129| [`nopfx_uppercase`] | None | Uppercase | Uppercase |
130
131Each policy also has an `option_` counterpart, an `array` submodule, and a
132`deserialize_bounded` function. Present byte values use strings, including in
133binary formats. Option adapters preserve the format's `Some`/`None` tags; an empty
134present value stays distinct from `None`. For missing struct fields, add
135`#[serde(default)]` alongside the `with` attribute.
136
137Use [`array`](mod@crate::array) for exact-length arrays. Generic adapters instead
138collect into [`FromIterator<u8>`](core::iter::FromIterator) containers; bounded
139collectors can panic when full. [`deserialize_bounded`] limits decoded bytes before output allocation,
140but does not bound the format's input storage or a custom collector's allocations.
141
142```
143#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
144struct Record {
145 #[serde(with = "faster_hex::array")]
146 id: [u8; 2],
147 #[serde(default, with = "faster_hex::option_nopfx_lowercase::array")]
148 extra: Option<[u8; 2]>,
149}
150
151let record = Record { id: [0xab, 1], extra: Some([0xcd, 2]) };
152let json = serde_json::to_string(&record)?;
153assert_eq!(json, r#"{"id":"0xab01","extra":"cd02"}"#);
154assert_eq!(serde_json::from_str::<Record>(&json)?, record);
155assert_eq!(serde_json::from_str::<Record>(r#"{"id":"0xab01"}"#)?.extra, None);
156# Ok::<(), serde_json::Error>(())
157```
158"##
159)]
160
161#[cfg(feature = "alloc")]
162extern crate alloc;
163
164mod decode;
165mod encode;
166mod error;
167mod format;
168
169#[cfg(any(test, fuzzing))]
173#[doc(hidden)]
174#[allow(missing_docs)]
175pub mod fuzzing;
176
177#[cfg(feature = "heapless-08")]
178#[cfg_attr(docsrs, doc(cfg(feature = "heapless-08")))]
179pub mod heapless_08;
180
181#[cfg(feature = "serde")]
182mod serde;
183
184pub use crate::decode::{
185 hex_check, hex_check_with_case, hex_decode, hex_decode_array, hex_decode_array_with_case,
186 hex_decode_with_case, CheckCase,
187};
188pub use crate::encode::{hex_encode, hex_encode_upper};
189
190#[cfg(feature = "alloc")]
191pub use crate::encode::{hex_append, hex_append_upper, hex_string, hex_string_upper};
192
193#[cfg(feature = "alloc")]
194pub use crate::decode::{hex_decode_vec, hex_decode_vec_with_case};
195
196pub use crate::error::Error;
197pub use crate::format::Hex;
198
199#[cfg(feature = "serde")]
200pub use crate::serde::withpfx_ignorecase::array;
201
202#[cfg(feature = "serde")]
203pub use crate::serde::{
204 deserialize, deserialize_bounded, nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase,
205 option_nopfx_ignorecase, option_nopfx_lowercase, option_nopfx_uppercase,
206 option_withpfx_ignorecase, option_withpfx_lowercase, option_withpfx_uppercase, serialize,
207 withpfx_ignorecase, withpfx_lowercase, withpfx_uppercase,
208};
209
210#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
211#[derive(Copy, Clone, PartialEq, Eq, Debug)]
212#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
213#[allow(dead_code, reason = "Static ISA baselines bypass runtime detection")]
214pub(crate) enum Vectorization {
215 None = 0,
216 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
217 SSE41 = 1,
218 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
219 AVX2 = 2,
220 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
221 AVX512 = 128,
223 #[cfg(target_arch = "aarch64")]
224 Neon = 3,
225}
226
227#[inline(always)]
228#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
229pub(crate) fn vectorization_support() -> Vectorization {
230 #[cfg(all(
231 any(target_arch = "x86", target_arch = "x86_64"),
232 target_feature = "avx512bw",
233 not(miri)
234 ))]
235 {
236 return Vectorization::AVX512;
237 }
238
239 #[cfg(all(
240 any(target_arch = "x86", target_arch = "x86_64"),
241 target_feature = "avx2",
242 not(target_feature = "avx512bw"),
243 not(miri)
244 ))]
245 {
246 return Vectorization::AVX2;
247 }
248
249 #[cfg(all(
250 any(target_arch = "x86", target_arch = "x86_64"),
251 target_feature = "sse",
252 not(target_feature = "avx2"),
253 not(miri)
254 ))]
255 {
256 use core::sync::atomic::{AtomicU8, Ordering};
257 static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
258
259 return match FLAGS.load(Ordering::Relaxed) {
261 0 => Vectorization::None,
262 1 => Vectorization::SSE41,
263 2 => Vectorization::AVX2,
264 128 => Vectorization::AVX512,
265 _ => {
266 let backend = vectorization_support_no_cache_x86();
267 FLAGS.store(backend as u8, Ordering::Relaxed);
268 backend
269 }
270 };
271 }
272
273 #[cfg(all(target_arch = "aarch64", target_feature = "neon", not(miri)))]
274 {
275 return Vectorization::Neon;
276 }
277
278 #[allow(unreachable_code)]
279 Vectorization::None
280}
281
282#[cfg(all(
283 any(target_arch = "x86", target_arch = "x86_64"),
284 target_feature = "sse",
285 not(target_feature = "avx2"),
286 not(miri)
287))]
288#[cold]
289fn vectorization_support_no_cache_x86() -> Vectorization {
290 #[cfg(target_arch = "x86")]
291 use core::arch::x86::__cpuid_count;
292 #[cfg(target_arch = "x86_64")]
293 use core::arch::x86_64::__cpuid_count;
294
295 if cfg!(target_env = "sgx") || !cfg!(target_feature = "sse") {
298 return Vectorization::None;
299 }
300
301 let max_leaf = __cpuid_count(0, 0).eax;
304 if max_leaf < 1 {
305 return Vectorization::None;
306 }
307 let proc_info_ecx = __cpuid_count(1, 0).ecx;
308 let have_sse4 = (proc_info_ecx >> 19) & 1 == 1;
309 if !have_sse4 {
311 return Vectorization::None;
312 }
313
314 let have_xsave = (proc_info_ecx >> 26) & 1 == 1;
315 let have_osxsave = (proc_info_ecx >> 27) & 1 == 1;
316 let have_avx = (proc_info_ecx >> 28) & 1 == 1;
317 if max_leaf >= 7 && have_xsave && have_osxsave && have_avx {
318 return unsafe { avx_support_no_cache_x86() };
320 }
321 Vectorization::SSE41
322}
323
324#[target_feature(enable = "xsave")]
327#[cfg(all(
328 any(target_arch = "x86", target_arch = "x86_64"),
329 target_feature = "sse",
330 not(target_feature = "avx2"),
331 not(miri)
332))]
333#[cold]
334unsafe fn avx_support_no_cache_x86() -> Vectorization {
335 #[cfg(target_arch = "x86")]
336 use core::arch::x86::{__cpuid_count, _xgetbv};
337 #[cfg(target_arch = "x86_64")]
338 use core::arch::x86_64::{__cpuid_count, _xgetbv};
339
340 let xcr0 = _xgetbv(0);
341 let os_avx_support = xcr0 & 6 == 6;
342 if os_avx_support {
343 let extended_features_ebx = __cpuid_count(7, 0).ebx;
344 let have_avx2 = (extended_features_ebx >> 5) & 1 == 1;
345 if have_avx2 {
346 let avx512 = (1 << 16) | (1 << 30); if xcr0 & 0xe6 == 0xe6 && extended_features_ebx & avx512 == avx512 {
350 return Vectorization::AVX512;
351 }
352 return Vectorization::AVX2;
353 }
354 }
355 Vectorization::SSE41
356}
357
358#[cfg(test)]
359mod tests;