1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6mod decode;
7mod encode;
8mod error;
9
10#[cfg(feature = "serde")]
11mod serde;
12
13pub use crate::decode::{
14 hex_check, hex_check_fallback, hex_check_with_case, hex_decode, hex_decode_fallback,
15 hex_decode_unchecked,
16};
17pub use crate::encode::{
18 hex_encode, hex_encode_fallback, hex_encode_upper, hex_encode_upper_fallback, hex_string,
19 hex_string_upper,
20};
21
22pub use crate::error::Error;
23
24#[cfg(feature = "serde")]
25pub use crate::serde::{
26 deserialize, nopfx_ignorecase, nopfx_lowercase, nopfx_uppercase, option_nopfx_ignorecase,
27 option_nopfx_lowercase, option_nopfx_uppercase, option_withpfx_ignorecase,
28 option_withpfx_lowercase, option_withpfx_uppercase, serialize, withpfx_ignorecase,
29 withpfx_lowercase, withpfx_uppercase,
30};
31
32#[allow(deprecated)]
33pub use crate::encode::hex_to;
34
35#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
36pub use crate::decode::{hex_check_sse, hex_check_sse_with_case};
37
38#[cfg(target_arch = "aarch64")]
39pub use crate::decode::{hex_check_neon, hex_check_neon_with_case};
40
41#[derive(Copy, Clone, PartialEq, Eq, Debug)]
42#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
43pub(crate) enum Vectorization {
44 None = 0,
45 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
46 SSE41 = 1,
47 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
48 AVX2 = 2,
49 #[cfg(target_arch = "aarch64")]
50 Neon = 3,
51}
52
53#[inline(always)]
54pub(crate) fn vectorization_support() -> Vectorization {
55 #[cfg(all(
56 any(target_arch = "x86", target_arch = "x86_64"),
57 target_feature = "sse"
58 ))]
59 {
60 use core::sync::atomic::{AtomicU8, Ordering};
61 static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
62
63 let current_flags = FLAGS.load(Ordering::Relaxed);
65 if current_flags != u8::MAX {
67 return match current_flags {
68 0 => Vectorization::None,
69 1 => Vectorization::SSE41,
70 2 => Vectorization::AVX2,
71 _ => unreachable!(),
72 };
73 }
74
75 let val = vectorization_support_no_cache_x86();
76
77 FLAGS.store(val as u8, Ordering::Relaxed);
78 return val;
79 }
80
81 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
82 {
83 use core::sync::atomic::{AtomicU8, Ordering};
85 static FLAGS: AtomicU8 = AtomicU8::new(u8::MAX);
86
87 let current_flags = FLAGS.load(Ordering::Relaxed);
88 if current_flags != u8::MAX {
89 return match current_flags {
90 0 => Vectorization::None,
91 3 => Vectorization::Neon,
92 _ => unreachable!(),
93 };
94 }
95
96 let val = vectorization_support_no_cache_arm();
97 FLAGS.store(val as u8, Ordering::Relaxed);
98 return val;
99 }
100
101 #[allow(unreachable_code)]
102 Vectorization::None
103}
104
105#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
106#[cold]
107fn vectorization_support_no_cache_x86() -> Vectorization {
108 #[cfg(target_arch = "x86")]
109 use core::arch::x86::__cpuid_count;
110 #[cfg(target_arch = "x86_64")]
111 use core::arch::x86_64::__cpuid_count;
112
113 if cfg!(target_env = "sgx") || !cfg!(target_feature = "sse") {
116 return Vectorization::None;
117 }
118
119 let proc_info_ecx = unsafe { __cpuid_count(1, 0) }.ecx;
120 let have_sse4 = (proc_info_ecx >> 19) & 1 == 1;
121 if !have_sse4 {
123 return Vectorization::None;
124 }
125
126 let have_xsave = (proc_info_ecx >> 26) & 1 == 1;
127 let have_osxsave = (proc_info_ecx >> 27) & 1 == 1;
128 let have_avx = (proc_info_ecx >> 28) & 1 == 1;
129 if have_xsave && have_osxsave && have_avx {
130 if unsafe { avx2_support_no_cache_x86() } {
132 return Vectorization::AVX2;
133 }
134 }
135 Vectorization::SSE41
136}
137
138#[target_feature(enable = "xsave")]
141#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
142#[cold]
143unsafe fn avx2_support_no_cache_x86() -> bool {
144 #[cfg(target_arch = "x86")]
145 use core::arch::x86::{__cpuid_count, _xgetbv};
146 #[cfg(target_arch = "x86_64")]
147 use core::arch::x86_64::{__cpuid_count, _xgetbv};
148
149 let xcr0 = _xgetbv(0);
150 let os_avx_support = xcr0 & 6 == 6;
151 if os_avx_support {
152 let extended_features_ebx = __cpuid_count(7, 0).ebx;
153 let have_avx2 = (extended_features_ebx >> 5) & 1 == 1;
154 if have_avx2 {
155 return true;
156 }
157 }
158 false
159}
160
161#[cfg(target_arch = "aarch64")]
162#[cold]
163fn vectorization_support_no_cache_arm() -> Vectorization {
164 #[cfg(feature = "std")]
165 if std::arch::is_aarch64_feature_detected!("neon") {
166 return Vectorization::Neon;
167 }
168 #[cfg(target_feature = "neon")]
169 return Vectorization::Neon;
170
171 #[allow(unreachable_code)]
172 Vectorization::None
173}
174
175#[cfg(test)]
176mod tests {
177 use crate::decode::{hex_decode, hex_decode_with_case, CheckCase};
178 use crate::encode::{hex_encode, hex_string};
179 use crate::{hex_encode_upper, hex_string_upper, vectorization_support, Vectorization};
180 use proptest::proptest;
181
182 #[cfg(not(feature = "alloc"))]
183 const CAPACITY: usize = 128;
184
185 #[test]
186 fn test_feature_detection() {
187 let vector_support = vectorization_support();
188 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
189 {
190 match vector_support {
191 Vectorization::AVX2 => assert!(is_x86_feature_detected!("avx2")),
192 Vectorization::SSE41 => assert!(is_x86_feature_detected!("sse4.1")),
193 Vectorization::None => assert!(
194 !cfg!(target_feature = "sse")
195 || !is_x86_feature_detected!("avx2") && !is_x86_feature_detected!("sse4.1")
196 ),
197 }
198 }
199
200 #[cfg(target_arch = "aarch64")]
201 match vector_support {
202 Vectorization::Neon => assert!(std::arch::is_aarch64_feature_detected!("neon")),
203 Vectorization::None => assert!(
204 !cfg!(target_feature = "neon") || !std::arch::is_aarch64_feature_detected!("neon")
205 ),
206 }
207
208 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
209 assert_eq!(vector_support, Vectorization::None);
210 }
211
212 fn _test_hex_encode(s: &String) {
213 let mut buffer = vec![0; s.as_bytes().len() * 2];
214 {
215 let encode = &*hex_encode(s.as_bytes(), &mut buffer).unwrap();
216
217 #[cfg(feature = "alloc")]
218 let hex_string = hex_string(s.as_bytes());
219 #[cfg(not(feature = "alloc"))]
220 let hex_string = hex_string::<CAPACITY>(s.as_bytes());
221
222 assert_eq!(encode, hex::encode(s));
223 assert_eq!(hex_string.as_str(), hex::encode(s));
224 }
225
226 {
227 let encode_upper = &*hex_encode_upper(s.as_bytes(), &mut buffer).unwrap();
228
229 #[cfg(feature = "alloc")]
230 let hex_string_upper = hex_string_upper(s.as_bytes());
231 #[cfg(not(feature = "alloc"))]
232 let hex_string_upper = hex_string_upper::<CAPACITY>(s.as_bytes());
233
234 assert_eq!(encode_upper, hex::encode_upper(s));
235 assert_eq!(hex_string_upper.as_str(), hex::encode_upper(s));
236 }
237 }
238
239 #[cfg(feature = "alloc")]
240 proptest! {
241 #[test]
242 fn test_hex_encode(ref s in ".*") {
243 _test_hex_encode(s);
244 }
245 }
246
247 #[cfg(not(feature = "alloc"))]
248 proptest! {
249 #[test]
250 fn test_hex_encode(ref s in ".{0,16}") {
251 _test_hex_encode(s);
252 }
253 }
254
255 fn _test_hex_decode(s: &String) {
256 let len = s.as_bytes().len();
257 {
258 let mut dst = Vec::with_capacity(len);
259 dst.resize(len, 0);
260 #[cfg(feature = "alloc")]
261 let hex_string = hex_string(s.as_bytes());
262 #[cfg(not(feature = "alloc"))]
263 let hex_string = hex_string::<CAPACITY>(s.as_bytes());
264
265 hex_decode(hex_string.as_bytes(), &mut dst).unwrap();
266
267 hex_decode_with_case(hex_string.as_bytes(), &mut dst, CheckCase::Lower).unwrap();
268
269 assert_eq!(&dst[..], s.as_bytes());
270 }
271 {
272 let mut dst = Vec::with_capacity(len);
273 dst.resize(len, 0);
274 #[cfg(feature = "alloc")]
275 let hex_string_upper = hex_string_upper(s.as_bytes());
276 #[cfg(not(feature = "alloc"))]
277 let hex_string_upper = hex_string_upper::<CAPACITY>(s.as_bytes());
278
279 hex_decode_with_case(hex_string_upper.as_bytes(), &mut dst, CheckCase::Upper).unwrap();
280
281 assert_eq!(&dst[..], s.as_bytes());
282 }
283 }
284
285 #[cfg(feature = "alloc")]
286 proptest! {
287 #[test]
288 fn test_hex_decode(ref s in ".+") {
289 _test_hex_decode(s);
290 }
291 }
292
293 #[cfg(not(feature = "alloc"))]
294 proptest! {
295 #[test]
296 fn test_hex_decode(ref s in ".{1,16}") {
297 _test_hex_decode(s);
298 }
299 }
300
301 fn _test_hex_decode_check(s: &String, ok: bool) {
302 let len = s.as_bytes().len();
303 let mut dst = Vec::with_capacity(len / 2);
304 dst.resize(len / 2, 0);
305 assert!(hex_decode(s.as_bytes(), &mut dst).is_ok() == ok);
306 }
307
308 proptest! {
309 #[test]
310 fn test_hex_decode_check(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
311 _test_hex_decode_check(s, true);
312 }
313 }
314
315 proptest! {
316 #[test]
317 fn test_hex_decode_check_odd(ref s in "[0-9a-fA-F]{11}") {
318 _test_hex_decode_check(s, false);
319 }
320 }
321}