Skip to main content

faster_hex_thiserror/
decode.rs

1// avx2 decode modified from https://github.com/zbjornson/fast-hex/blob/master/src/hex.cc
2
3#[cfg(target_arch = "aarch64")]
4use core::arch::aarch64::*;
5#[cfg(target_arch = "x86")]
6use core::arch::x86::*;
7#[cfg(target_arch = "x86_64")]
8use core::arch::x86_64::*;
9
10use crate::error::Error;
11
12const NIL: u8 = u8::MAX;
13
14#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
15const T_MASK: i32 = 65535;
16
17const fn init_unhex_array(check_case: CheckCase) -> [u8; 256] {
18    let mut arr = [0; 256];
19    let mut i = 0;
20    while i < 256 {
21        arr[i] = match i as u8 {
22            b'0'..=b'9' => i as u8 - b'0',
23            b'a'..=b'f' => match check_case {
24                CheckCase::Lower | CheckCase::None => i as u8 - b'a' + 10,
25                _ => NIL,
26            },
27            b'A'..=b'F' => match check_case {
28                CheckCase::Upper | CheckCase::None => i as u8 - b'A' + 10,
29                _ => NIL,
30            },
31            _ => NIL,
32        };
33        i += 1;
34    }
35    arr
36}
37
38const fn init_unhex4_array(check_case: CheckCase) -> [u8; 256] {
39    let unhex_arr = init_unhex_array(check_case);
40
41    let mut unhex4_arr = [NIL; 256];
42    let mut i = 0;
43    while i < 256 {
44        if unhex_arr[i] != NIL {
45            unhex4_arr[i] = unhex_arr[i] << 4;
46        }
47        i += 1;
48    }
49    unhex4_arr
50}
51
52// ASCII -> hex
53pub(crate) static UNHEX: [u8; 256] = init_unhex_array(CheckCase::None);
54
55// ASCII -> hex, lower case
56pub(crate) static UNHEX_LOWER: [u8; 256] = init_unhex_array(CheckCase::Lower);
57
58// ASCII -> hex, upper case
59pub(crate) static UNHEX_UPPER: [u8; 256] = init_unhex_array(CheckCase::Upper);
60
61// ASCII -> hex << 4
62pub(crate) static UNHEX4: [u8; 256] = init_unhex4_array(CheckCase::None);
63
64const _0213: i32 = 0b11011000;
65
66// lower nibble
67#[inline]
68fn unhex_b(x: usize) -> u8 {
69    UNHEX[x]
70}
71
72// upper nibble, logically equivalent to unhex_b(x) << 4
73#[inline]
74fn unhex_a(x: usize) -> u8 {
75    UNHEX4[x]
76}
77
78#[inline]
79#[target_feature(enable = "avx2")]
80#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
81unsafe fn unhex_avx2(value: __m256i) -> __m256i {
82    let sr6 = _mm256_srai_epi16(value, 6);
83    let and15 = _mm256_and_si256(value, _mm256_set1_epi16(0xf));
84    let mul = _mm256_maddubs_epi16(sr6, _mm256_set1_epi16(9));
85    _mm256_add_epi16(mul, and15)
86}
87
88// (a << 4) | b;
89#[inline]
90#[target_feature(enable = "avx2")]
91#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
92unsafe fn nib2byte_avx2(a1: __m256i, b1: __m256i, a2: __m256i, b2: __m256i) -> __m256i {
93    let a4_1 = _mm256_slli_epi16(a1, 4);
94    let a4_2 = _mm256_slli_epi16(a2, 4);
95    let a4orb_1 = _mm256_or_si256(a4_1, b1);
96    let a4orb_2 = _mm256_or_si256(a4_2, b2);
97    let pck1 = _mm256_packus_epi16(a4orb_1, a4orb_2);
98    _mm256_permute4x64_epi64(pck1, _0213)
99}
100
101/// Check if the input is valid hex bytes slice
102pub fn hex_check(src: &[u8]) -> bool {
103    hex_check_with_case(src, CheckCase::None)
104}
105
106/// Check if the input is valid hex bytes slice with case check
107pub fn hex_check_with_case(src: &[u8], check_case: CheckCase) -> bool {
108    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
109    {
110        match crate::vectorization_support() {
111            crate::Vectorization::AVX2 | crate::Vectorization::SSE41 => unsafe {
112                hex_check_sse_with_case(src, check_case)
113            },
114            crate::Vectorization::None => hex_check_fallback_with_case(src, check_case),
115        }
116    }
117
118    #[cfg(target_arch = "aarch64")]
119    {
120        match crate::vectorization_support() {
121            crate::Vectorization::Neon => unsafe { hex_check_neon_with_case(src, check_case) },
122            crate::Vectorization::None => hex_check_fallback_with_case(src, check_case),
123        }
124    }
125
126    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
127    hex_check_fallback_with_case(src, check_case)
128}
129
130/// Check if the input is valid hex bytes slice
131pub fn hex_check_fallback(src: &[u8]) -> bool {
132    hex_check_fallback_with_case(src, CheckCase::None)
133}
134
135/// Check if the input is valid hex bytes slice with case check
136pub fn hex_check_fallback_with_case(src: &[u8], check_case: CheckCase) -> bool {
137    match check_case {
138        CheckCase::None => src.iter().all(|&x| UNHEX[x as usize] != NIL),
139        CheckCase::Lower => src.iter().all(|&x| UNHEX_LOWER[x as usize] != NIL),
140        CheckCase::Upper => src.iter().all(|&x| UNHEX_UPPER[x as usize] != NIL),
141    }
142}
143
144/// # Safety
145/// Check if a byte slice is valid.
146#[target_feature(enable = "sse4.1")]
147#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
148pub unsafe fn hex_check_sse(src: &[u8]) -> bool {
149    hex_check_sse_with_case(src, CheckCase::None)
150}
151
152#[derive(Eq, PartialEq)]
153#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
154pub enum CheckCase {
155    None,
156    Lower,
157    Upper,
158}
159
160/// # Safety
161/// Check if a byte slice is valid on given check_case.
162#[target_feature(enable = "sse4.1")]
163#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
164pub unsafe fn hex_check_sse_with_case(mut src: &[u8], check_case: CheckCase) -> bool {
165    let ascii_zero = _mm_set1_epi8((b'0' - 1) as i8);
166    let ascii_nine = _mm_set1_epi8((b'9' + 1) as i8);
167    let ascii_ua = _mm_set1_epi8((b'A' - 1) as i8);
168    let ascii_uf = _mm_set1_epi8((b'F' + 1) as i8);
169    let ascii_la = _mm_set1_epi8((b'a' - 1) as i8);
170    let ascii_lf = _mm_set1_epi8((b'f' + 1) as i8);
171
172    while src.len() >= 16 {
173        let unchecked = _mm_loadu_si128(src.as_ptr() as *const _);
174
175        let gt0 = _mm_cmpgt_epi8(unchecked, ascii_zero);
176        let lt9 = _mm_cmplt_epi8(unchecked, ascii_nine);
177        let valid_digit = _mm_and_si128(gt0, lt9);
178
179        let (valid_la_lf, valid_ua_uf) = match check_case {
180            CheckCase::None => {
181                let gtua = _mm_cmpgt_epi8(unchecked, ascii_ua);
182                let ltuf = _mm_cmplt_epi8(unchecked, ascii_uf);
183
184                let gtla = _mm_cmpgt_epi8(unchecked, ascii_la);
185                let ltlf = _mm_cmplt_epi8(unchecked, ascii_lf);
186
187                (
188                    Some(_mm_and_si128(gtla, ltlf)),
189                    Some(_mm_and_si128(gtua, ltuf)),
190                )
191            }
192            CheckCase::Lower => {
193                let gtla = _mm_cmpgt_epi8(unchecked, ascii_la);
194                let ltlf = _mm_cmplt_epi8(unchecked, ascii_lf);
195
196                (Some(_mm_and_si128(gtla, ltlf)), None)
197            }
198            CheckCase::Upper => {
199                let gtua = _mm_cmpgt_epi8(unchecked, ascii_ua);
200                let ltuf = _mm_cmplt_epi8(unchecked, ascii_uf);
201                (None, Some(_mm_and_si128(gtua, ltuf)))
202            }
203        };
204
205        let valid_letter = match (valid_la_lf, valid_ua_uf) {
206            (Some(valid_lower), Some(valid_upper)) => _mm_or_si128(valid_lower, valid_upper),
207            (Some(valid_lower), None) => valid_lower,
208            (None, Some(valid_upper)) => valid_upper,
209            _ => unreachable!(),
210        };
211
212        let ret = _mm_movemask_epi8(_mm_or_si128(valid_digit, valid_letter));
213
214        if ret != T_MASK {
215            return false;
216        }
217
218        src = &src[16..];
219    }
220    hex_check_fallback_with_case(src, check_case)
221}
222
223#[target_feature(enable = "neon")]
224#[cfg(target_arch = "aarch64")]
225pub unsafe fn hex_check_neon(src: &[u8]) -> bool {
226    hex_check_neon_with_case(src, CheckCase::None)
227}
228
229#[target_feature(enable = "neon")]
230#[cfg(target_arch = "aarch64")]
231pub unsafe fn hex_check_neon_with_case(mut src: &[u8], check_case: CheckCase) -> bool {
232    let ascii_zero = vdupq_n_u8(b'0' - 1);
233    let ascii_nine = vdupq_n_u8(b'9' + 1);
234    let ascii_ua = vdupq_n_u8(b'A' - 1);
235    let ascii_uf = vdupq_n_u8(b'F' + 1);
236    let ascii_la = vdupq_n_u8(b'a' - 1);
237    let ascii_lf = vdupq_n_u8(b'f' + 1);
238
239    while src.len() >= 16 {
240        let unchecked = vld1q_u8(src.as_ptr() as *const _);
241
242        let gt0 = vcgtq_u8(unchecked, ascii_zero);
243        let lt9 = vcltq_u8(unchecked, ascii_nine);
244        let valid_digit = vandq_u8(gt0, lt9);
245
246        let (valid_la_lf, valid_ua_uf) = match check_case {
247            CheckCase::None => {
248                let gtua = vcgtq_u8(unchecked, ascii_ua);
249                let ltuf = vcltq_u8(unchecked, ascii_uf);
250
251                let gtla = vcgtq_u8(unchecked, ascii_la);
252                let ltlf = vcltq_u8(unchecked, ascii_lf);
253
254                (Some(vandq_u8(gtla, ltlf)), Some(vandq_u8(gtua, ltuf)))
255            }
256            CheckCase::Lower => {
257                let gtla = vcgtq_u8(unchecked, ascii_la);
258                let ltlf = vcltq_u8(unchecked, ascii_lf);
259
260                (Some(vandq_u8(gtla, ltlf)), None)
261            }
262            CheckCase::Upper => {
263                let gtua = vcgtq_u8(unchecked, ascii_ua);
264                let ltuf = vcltq_u8(unchecked, ascii_uf);
265
266                (None, Some(vandq_u8(gtua, ltuf)))
267            }
268        };
269
270        let valid_letter = match (valid_la_lf, valid_ua_uf) {
271            (Some(valid_lower), Some(valid_upper)) => vorrq_u8(valid_lower, valid_upper),
272            (Some(valid_lower), None) => valid_lower,
273            (None, Some(valid_upper)) => valid_upper,
274            _ => unreachable!(),
275        };
276
277        let ret = vminvq_u8(vorrq_u8(valid_digit, valid_letter));
278
279        if ret == 0 {
280            return false;
281        }
282
283        src = &src[16..];
284    }
285
286    hex_check_fallback_with_case(src, check_case)
287}
288
289/// Hex decode src into dst.
290/// The length of src must be even, and it's allowed to decode a zero length src.
291/// The length of dst must be at least src.len() / 2.
292pub fn hex_decode(src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
293    hex_decode_with_case(src, dst, CheckCase::None)
294}
295
296/// Hex decode src into dst.
297/// The length of src must be even, and it's allowed to decode a zero length src.
298/// The length of dst must be at least src.len() / 2.
299/// when check_case is CheckCase::Lower, the hex string must be lower case.
300/// when check_case is CheckCase::Upper, the hex string must be upper case.
301/// when check_case is CheckCase::None, the hex string can be lower case or upper case.
302pub fn hex_decode_with_case(
303    src: &[u8],
304    dst: &mut [u8],
305    check_case: CheckCase,
306) -> Result<(), Error> {
307    let len = dst.len().checked_mul(2).ok_or(Error::Overflow)?;
308    if src.len() < len || ((src.len() & 1) != 0) {
309        return Err(Error::InvalidLength(len));
310    }
311
312    if !hex_check_with_case(src, check_case) {
313        return Err(Error::InvalidChar);
314    }
315    hex_decode_unchecked(src, dst);
316    Ok(())
317}
318
319pub fn hex_decode_unchecked(src: &[u8], dst: &mut [u8]) {
320    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
321    {
322        match crate::vectorization_support() {
323            crate::Vectorization::AVX2 => unsafe { hex_decode_avx2(src, dst) },
324            crate::Vectorization::None | crate::Vectorization::SSE41 => {
325                hex_decode_fallback(src, dst)
326            }
327        }
328    }
329    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
330    hex_decode_fallback(src, dst);
331}
332
333#[target_feature(enable = "avx2")]
334#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
335unsafe fn hex_decode_avx2(mut src: &[u8], mut dst: &mut [u8]) {
336    // 0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1,
337    // 0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1
338    let mask_a = _mm256_setr_epi8(
339        0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1, 0, -1, 2, -1, 4, -1, 6, -1, 8,
340        -1, 10, -1, 12, -1, 14, -1,
341    );
342
343    // 1, -1, 3, -1, 5, -1, 7, -1, 9, -1, 11, -1, 13, -1, 15, -1,
344    // 1, -1, 3, -1, 5, -1, 7, -1, 9, -1, 11, -1, 13, -1, 15, -1
345    let mask_b = _mm256_setr_epi8(
346        1, -1, 3, -1, 5, -1, 7, -1, 9, -1, 11, -1, 13, -1, 15, -1, 1, -1, 3, -1, 5, -1, 7, -1, 9,
347        -1, 11, -1, 13, -1, 15, -1,
348    );
349
350    while dst.len() >= 32 {
351        let av1 = _mm256_loadu_si256(src.as_ptr() as *const _);
352        let av2 = _mm256_loadu_si256(src[32..].as_ptr() as *const _);
353
354        let mut a1 = _mm256_shuffle_epi8(av1, mask_a);
355        let mut b1 = _mm256_shuffle_epi8(av1, mask_b);
356        let mut a2 = _mm256_shuffle_epi8(av2, mask_a);
357        let mut b2 = _mm256_shuffle_epi8(av2, mask_b);
358
359        a1 = unhex_avx2(a1);
360        a2 = unhex_avx2(a2);
361        b1 = unhex_avx2(b1);
362        b2 = unhex_avx2(b2);
363
364        let bytes = nib2byte_avx2(a1, b1, a2, b2);
365
366        //dst does not need to be aligned on any particular boundary
367        _mm256_storeu_si256(dst.as_mut_ptr() as *mut _, bytes);
368        dst = &mut dst[32..];
369        src = &src[64..];
370    }
371    hex_decode_fallback(src, dst)
372}
373
374pub fn hex_decode_fallback(src: &[u8], dst: &mut [u8]) {
375    for (slot, bytes) in dst.iter_mut().zip(src.chunks_exact(2)) {
376        let a = unhex_a(bytes[0] as usize);
377        let b = unhex_b(bytes[1] as usize);
378        *slot = a | b;
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use crate::decode::NIL;
385    use crate::{
386        decode::{
387            hex_check_fallback, hex_check_fallback_with_case, hex_decode_fallback, CheckCase,
388        },
389        encode::hex_string,
390    };
391    use proptest::proptest;
392
393    #[cfg(not(feature = "alloc"))]
394    const CAPACITY: usize = 128;
395
396    fn _test_decode_fallback(s: &String) {
397        let len = s.as_bytes().len();
398        let mut dst = Vec::with_capacity(len);
399        dst.resize(len, 0);
400
401        #[cfg(feature = "alloc")]
402        let hex_string = hex_string(s.as_bytes());
403        #[cfg(not(feature = "alloc"))]
404        let hex_string = hex_string::<CAPACITY>(s.as_bytes());
405
406        hex_decode_fallback(hex_string.as_bytes(), &mut dst);
407
408        assert_eq!(&dst[..], s.as_bytes());
409    }
410
411    #[cfg(feature = "alloc")]
412    proptest! {
413        #[test]
414        fn test_decode_fallback(ref s in ".+") {
415            _test_decode_fallback(s);
416        }
417    }
418
419    #[cfg(not(feature = "alloc"))]
420    proptest! {
421        #[test]
422        fn test_decode_fallback(ref s in ".{1,16}") {
423            _test_decode_fallback(s);
424        }
425    }
426
427    fn _test_check_fallback_true(s: &String) {
428        assert!(hex_check_fallback(s.as_bytes()));
429        match (
430            s.contains(char::is_lowercase),
431            s.contains(char::is_uppercase),
432        ) {
433            (true, true) => {
434                assert!(!hex_check_fallback_with_case(
435                    s.as_bytes(),
436                    CheckCase::Lower
437                ));
438                assert!(!hex_check_fallback_with_case(
439                    s.as_bytes(),
440                    CheckCase::Upper
441                ));
442            }
443            (true, false) => {
444                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
445                assert!(!hex_check_fallback_with_case(
446                    s.as_bytes(),
447                    CheckCase::Upper
448                ));
449            }
450            (false, true) => {
451                assert!(!hex_check_fallback_with_case(
452                    s.as_bytes(),
453                    CheckCase::Lower
454                ));
455                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
456            }
457            (false, false) => {
458                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
459                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
460            }
461        }
462    }
463
464    proptest! {
465    #[test]
466        fn test_check_fallback_true(ref s in "[0-9a-fA-F]+") {
467            _test_check_fallback_true(s);
468        }
469    }
470
471    fn _test_check_fallback_false(s: &String) {
472        assert!(!hex_check_fallback(s.as_bytes()));
473        assert!(!hex_check_fallback_with_case(
474            s.as_bytes(),
475            CheckCase::Upper
476        ));
477        assert!(!hex_check_fallback_with_case(
478            s.as_bytes(),
479            CheckCase::Lower
480        ));
481    }
482
483    proptest! {
484        #[test]
485        fn test_check_fallback_false(ref s in ".{16}[^0-9a-fA-F]+") {
486            _test_check_fallback_false(s);
487        }
488    }
489
490    #[test]
491    fn test_init_static_array_is_right() {
492        static OLD_UNHEX: [u8; 256] = [
493            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
494            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
495            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 1, 2, 3, 4, 5,
496            6, 7, 8, 9, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL,
497            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
498            NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL, NIL, NIL, NIL,
499            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
500            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
501            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
502            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
503            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
504            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
505            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
506            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
507            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
508        ];
509
510        static OLD_UNHEX4: [u8; 256] = [
511            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
512            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
513            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 16, 32, 48,
514            64, 80, 96, 112, 128, 144, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224,
515            240, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
516            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224, 240, NIL,
517            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
518            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
519            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
520            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
521            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
522            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
523            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
524            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
525            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
526        ];
527
528        assert_eq!(OLD_UNHEX, crate::decode::UNHEX);
529        assert_eq!(OLD_UNHEX4, crate::decode::UNHEX4);
530    }
531}
532
533#[cfg(all(
534    test,
535    any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
536))]
537mod test_simd {
538    use crate::decode::{
539        hex_check, hex_check_fallback, hex_check_fallback_with_case, hex_check_with_case,
540        hex_decode, hex_decode_unchecked, hex_decode_with_case, CheckCase,
541    };
542    #[cfg(target_arch = "aarch64")]
543    use crate::decode::{hex_check_neon, hex_check_neon_with_case};
544    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
545    use crate::decode::{hex_check_sse, hex_check_sse_with_case};
546    #[cfg(target_arch = "aarch64")]
547    use std::arch::is_aarch64_feature_detected;
548
549    use proptest::proptest;
550
551    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
552    fn _test_check_sse_with_case(s: &String, check_case: CheckCase, expect_result: bool) {
553        if is_x86_feature_detected!("sse4.1") {
554            assert_eq!(
555                unsafe { hex_check_sse_with_case(s.as_bytes(), check_case) },
556                expect_result
557            )
558        }
559    }
560
561    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
562    fn _test_check_sse_true(s: &String) {
563        if is_x86_feature_detected!("sse4.1") {
564            assert!(unsafe { hex_check_sse(s.as_bytes()) });
565        }
566    }
567
568    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
569    proptest! {
570    #[test]
571    fn test_check_sse_true(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
572            _test_check_sse_true(s);
573            _test_check_sse_with_case(s, CheckCase::None, true);
574            match (s.contains(char::is_lowercase), s.contains(char::is_uppercase)){
575                (true, true) => {
576                    _test_check_sse_with_case(s, CheckCase::Lower, false);
577                    _test_check_sse_with_case(s, CheckCase::Upper, false);
578                },
579                (true, false) => {
580                    _test_check_sse_with_case(s, CheckCase::Lower, true);
581                    _test_check_sse_with_case(s, CheckCase::Upper, false);
582                },
583                (false, true) => {
584                    _test_check_sse_with_case(s, CheckCase::Lower, false);
585                    _test_check_sse_with_case(s, CheckCase::Upper, true);
586                },
587                (false, false) => {
588                    _test_check_sse_with_case(s, CheckCase::Lower, true);
589                    _test_check_sse_with_case(s, CheckCase::Upper, true);
590                }
591            }
592        }
593    }
594
595    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
596    fn _test_check_sse_false(s: &String) {
597        if is_x86_feature_detected!("sse4.1") {
598            assert!(!unsafe { hex_check_sse(s.as_bytes()) });
599        }
600    }
601    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
602    proptest! {
603        #[test]
604        fn test_check_sse_false(ref s in ".{16}[^0-9a-fA-F]+") {
605            _test_check_sse_false(s);
606            _test_check_sse_with_case(s, CheckCase::None, false);
607            _test_check_sse_with_case(s, CheckCase::Lower, false);
608            _test_check_sse_with_case(s, CheckCase::Upper, false);
609        }
610    }
611
612    #[cfg(target_arch = "aarch64")]
613    fn _test_check_neon_with_case(s: &String, check_case: CheckCase, expect_result: bool) {
614        if is_aarch64_feature_detected!("neon") {
615            assert_eq!(
616                unsafe { hex_check_neon_with_case(s.as_bytes(), check_case) },
617                expect_result
618            )
619        }
620    }
621
622    #[cfg(target_arch = "aarch64")]
623    fn _test_check_neon_true(s: &String) {
624        if is_aarch64_feature_detected!("neon") {
625            assert!(unsafe { hex_check_neon(s.as_bytes()) });
626        }
627    }
628
629    #[cfg(target_arch = "aarch64")]
630    proptest! {
631    #[test]
632    fn test_check_neon_true(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
633            _test_check_neon_true(s);
634            _test_check_neon_with_case(s, CheckCase::None, true);
635            match (s.contains(char::is_lowercase), s.contains(char::is_uppercase)){
636                (true, true) => {
637                    _test_check_neon_with_case(s, CheckCase::Lower, false);
638                    _test_check_neon_with_case(s, CheckCase::Upper, false);
639                },
640                (true, false) => {
641                    _test_check_neon_with_case(s, CheckCase::Lower, true);
642                    _test_check_neon_with_case(s, CheckCase::Upper, false);
643                },
644                (false, true) => {
645                    _test_check_neon_with_case(s, CheckCase::Lower, false);
646                    _test_check_neon_with_case(s, CheckCase::Upper, true);
647                },
648                (false, false) => {
649                    _test_check_neon_with_case(s, CheckCase::Lower, true);
650                    _test_check_neon_with_case(s, CheckCase::Upper, true);
651                }
652            }
653        }
654    }
655
656    #[cfg(target_arch = "aarch64")]
657    fn _test_check_neon_false(s: &String) {
658        if is_aarch64_feature_detected!("neon") {
659            assert!(!unsafe { hex_check_neon(s.as_bytes()) });
660        }
661    }
662    #[cfg(target_arch = "aarch64")]
663    proptest! {
664        #[test]
665        fn test_check_neon_false(ref s in ".{16}[^0-9a-fA-F]+") {
666            _test_check_neon_false(s);
667            _test_check_neon_with_case(s, CheckCase::None, false);
668            _test_check_neon_with_case(s, CheckCase::Lower, false);
669            _test_check_neon_with_case(s, CheckCase::Upper, false);
670        }
671    }
672
673    #[test]
674    fn test_decode_zero_length_src_should_not_be_ok() {
675        let src = b"";
676        let mut dst = [0u8; 10];
677        assert!(
678            matches!(hex_decode(src, &mut dst), Err(crate::Error::InvalidLength(len)) if len == 20)
679        );
680        assert!(
681            matches!(hex_decode_with_case(src, &mut dst, CheckCase::None), Err(crate::Error::InvalidLength(len)) if len == 20)
682        );
683        assert!(hex_check(src));
684        assert!(hex_check_with_case(src, CheckCase::None));
685        assert!(hex_check_fallback(src));
686        assert!(hex_check_fallback_with_case(src, CheckCase::None));
687
688        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
689        if is_x86_feature_detected!("sse4.1") {
690            assert!(unsafe { hex_check_sse_with_case(src, CheckCase::None) });
691            assert!(unsafe { hex_check_sse(src) });
692        }
693
694        #[cfg(target_arch = "aarch64")]
695        if is_aarch64_feature_detected!("neon") {
696            assert!(unsafe { hex_check_neon_with_case(src, CheckCase::None) });
697            assert!(unsafe { hex_check_neon(src) });
698        }
699
700        // this function have no return value, so we just execute it and expect no panic
701        hex_decode_unchecked(src, &mut dst);
702    }
703
704    // If `dst's length` is greater than `src's length * 2`, `hex_decode` should return error
705    #[test]
706    fn test_if_dst_len_gt_expect_len_should_return_error() {
707        let short_str = b"8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3"; // 62 bytes
708        {
709            let mut dst = [0u8; 31];
710            let result = hex_decode(short_str.as_slice(), &mut dst);
711            assert!(result.is_ok());
712        }
713
714        {
715            let mut dst = [0u8; 32];
716            let result = hex_decode(short_str.as_slice(), &mut dst);
717            assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 64))
718        }
719
720        {
721            let mut dst = [0u8; 33];
722            let result = hex_decode(short_str.as_slice(), &mut dst);
723            assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 66))
724        }
725    }
726
727    // if both `src` and `dst` are empty, it's ok
728    // if `src` is empty, but `dst` is not empty, it should be reported as error
729    #[test]
730    fn test_decode_zero_src() {
731        let zero_src = b"";
732        {
733            let mut zero_dst = [];
734            assert!(hex_decode(zero_src, &mut zero_dst).is_ok());
735        }
736
737        {
738            let mut non_zero_dst = [0u8; 1];
739            assert!(
740                matches!(hex_decode(zero_src, &mut non_zero_dst), Err(crate::Error::InvalidLength(len)) if len == 2)
741            );
742        }
743    }
744}