Skip to main content

faster_hex/
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 src.len() >= 64 && 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    #[test]
394    fn test_unchecked_decode_stops_at_source_end() {
395        use crate::hex_decode_unchecked;
396
397        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
398        if cfg!(target_feature = "sse")
399            && !cfg!(target_env = "sgx")
400            && is_x86_feature_detected!("avx2")
401        {
402            assert_eq!(crate::vectorization_support(), crate::Vectorization::AVX2);
403        }
404
405        for &(src_len, dst_len) in &[
406            (0, 32),
407            (1, 32),
408            (31, 32),
409            (32, 32),
410            (63, 32),
411            (64, 33),
412            (96, 64),
413            (127, 64),
414            (128, 65),
415        ] {
416            let src = vec![b'a'; src_len];
417            let mut dst = vec![0x5a; dst_len];
418            let mut expected = dst.clone();
419            expected[..(src_len / 2).min(dst_len)].fill(0xaa);
420
421            hex_decode_unchecked(&src, &mut dst);
422            assert_eq!(dst, expected, "src_len={src_len}, dst_len={dst_len}");
423        }
424    }
425
426    #[cfg(not(feature = "alloc"))]
427    const CAPACITY: usize = 128;
428
429    fn _test_decode_fallback(s: &String) {
430        let len = s.as_bytes().len();
431        let mut dst = Vec::with_capacity(len);
432        dst.resize(len, 0);
433
434        #[cfg(feature = "alloc")]
435        let hex_string = hex_string(s.as_bytes());
436        #[cfg(not(feature = "alloc"))]
437        let hex_string = hex_string::<CAPACITY>(s.as_bytes());
438
439        hex_decode_fallback(hex_string.as_bytes(), &mut dst);
440
441        assert_eq!(&dst[..], s.as_bytes());
442    }
443
444    #[cfg(feature = "alloc")]
445    proptest! {
446        #[test]
447        fn test_decode_fallback(ref s in ".+") {
448            _test_decode_fallback(s);
449        }
450    }
451
452    #[cfg(not(feature = "alloc"))]
453    proptest! {
454        #[test]
455        fn test_decode_fallback(ref s in ".{1,16}") {
456            _test_decode_fallback(s);
457        }
458    }
459
460    fn _test_check_fallback_true(s: &String) {
461        assert!(hex_check_fallback(s.as_bytes()));
462        match (
463            s.contains(char::is_lowercase),
464            s.contains(char::is_uppercase),
465        ) {
466            (true, true) => {
467                assert!(!hex_check_fallback_with_case(
468                    s.as_bytes(),
469                    CheckCase::Lower
470                ));
471                assert!(!hex_check_fallback_with_case(
472                    s.as_bytes(),
473                    CheckCase::Upper
474                ));
475            }
476            (true, false) => {
477                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
478                assert!(!hex_check_fallback_with_case(
479                    s.as_bytes(),
480                    CheckCase::Upper
481                ));
482            }
483            (false, true) => {
484                assert!(!hex_check_fallback_with_case(
485                    s.as_bytes(),
486                    CheckCase::Lower
487                ));
488                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
489            }
490            (false, false) => {
491                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
492                assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
493            }
494        }
495    }
496
497    proptest! {
498    #[test]
499        fn test_check_fallback_true(ref s in "[0-9a-fA-F]+") {
500            _test_check_fallback_true(s);
501        }
502    }
503
504    fn _test_check_fallback_false(s: &String) {
505        assert!(!hex_check_fallback(s.as_bytes()));
506        assert!(!hex_check_fallback_with_case(
507            s.as_bytes(),
508            CheckCase::Upper
509        ));
510        assert!(!hex_check_fallback_with_case(
511            s.as_bytes(),
512            CheckCase::Lower
513        ));
514    }
515
516    proptest! {
517        #[test]
518        fn test_check_fallback_false(ref s in ".{16}[^0-9a-fA-F]+") {
519            _test_check_fallback_false(s);
520        }
521    }
522
523    #[test]
524    fn test_init_static_array_is_right() {
525        static OLD_UNHEX: [u8; 256] = [
526            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
527            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
528            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 1, 2, 3, 4, 5,
529            6, 7, 8, 9, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL,
530            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
531            NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL, NIL, NIL, NIL,
532            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
533            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
534            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
535            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
536            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
537            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
538            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
539            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
540            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
541        ];
542
543        static OLD_UNHEX4: [u8; 256] = [
544            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
545            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
546            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 16, 32, 48,
547            64, 80, 96, 112, 128, 144, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224,
548            240, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
549            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224, 240, NIL,
550            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
551            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
552            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
553            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
554            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
555            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
556            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
557            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
558            NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
559        ];
560
561        assert_eq!(OLD_UNHEX, crate::decode::UNHEX);
562        assert_eq!(OLD_UNHEX4, crate::decode::UNHEX4);
563    }
564}
565
566#[cfg(all(
567    test,
568    any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
569))]
570mod test_simd {
571    use crate::decode::{
572        hex_check, hex_check_fallback, hex_check_fallback_with_case, hex_check_with_case,
573        hex_decode, hex_decode_unchecked, hex_decode_with_case, CheckCase,
574    };
575    #[cfg(target_arch = "aarch64")]
576    use crate::decode::{hex_check_neon, hex_check_neon_with_case};
577    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
578    use crate::decode::{hex_check_sse, hex_check_sse_with_case};
579    #[cfg(target_arch = "aarch64")]
580    use std::arch::is_aarch64_feature_detected;
581
582    use proptest::proptest;
583
584    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
585    fn _test_check_sse_with_case(s: &String, check_case: CheckCase, expect_result: bool) {
586        if is_x86_feature_detected!("sse4.1") {
587            assert_eq!(
588                unsafe { hex_check_sse_with_case(s.as_bytes(), check_case) },
589                expect_result
590            )
591        }
592    }
593
594    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
595    fn _test_check_sse_true(s: &String) {
596        if is_x86_feature_detected!("sse4.1") {
597            assert!(unsafe { hex_check_sse(s.as_bytes()) });
598        }
599    }
600
601    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
602    proptest! {
603    #[test]
604    fn test_check_sse_true(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
605            _test_check_sse_true(s);
606            _test_check_sse_with_case(s, CheckCase::None, true);
607            match (s.contains(char::is_lowercase), s.contains(char::is_uppercase)){
608                (true, true) => {
609                    _test_check_sse_with_case(s, CheckCase::Lower, false);
610                    _test_check_sse_with_case(s, CheckCase::Upper, false);
611                },
612                (true, false) => {
613                    _test_check_sse_with_case(s, CheckCase::Lower, true);
614                    _test_check_sse_with_case(s, CheckCase::Upper, false);
615                },
616                (false, true) => {
617                    _test_check_sse_with_case(s, CheckCase::Lower, false);
618                    _test_check_sse_with_case(s, CheckCase::Upper, true);
619                },
620                (false, false) => {
621                    _test_check_sse_with_case(s, CheckCase::Lower, true);
622                    _test_check_sse_with_case(s, CheckCase::Upper, true);
623                }
624            }
625        }
626    }
627
628    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
629    fn _test_check_sse_false(s: &String) {
630        if is_x86_feature_detected!("sse4.1") {
631            assert!(!unsafe { hex_check_sse(s.as_bytes()) });
632        }
633    }
634    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
635    proptest! {
636        #[test]
637        fn test_check_sse_false(ref s in ".{16}[^0-9a-fA-F]+") {
638            _test_check_sse_false(s);
639            _test_check_sse_with_case(s, CheckCase::None, false);
640            _test_check_sse_with_case(s, CheckCase::Lower, false);
641            _test_check_sse_with_case(s, CheckCase::Upper, false);
642        }
643    }
644
645    #[cfg(target_arch = "aarch64")]
646    fn _test_check_neon_with_case(s: &String, check_case: CheckCase, expect_result: bool) {
647        if is_aarch64_feature_detected!("neon") {
648            assert_eq!(
649                unsafe { hex_check_neon_with_case(s.as_bytes(), check_case) },
650                expect_result
651            )
652        }
653    }
654
655    #[cfg(target_arch = "aarch64")]
656    fn _test_check_neon_true(s: &String) {
657        if is_aarch64_feature_detected!("neon") {
658            assert!(unsafe { hex_check_neon(s.as_bytes()) });
659        }
660    }
661
662    #[cfg(target_arch = "aarch64")]
663    proptest! {
664    #[test]
665    fn test_check_neon_true(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
666            _test_check_neon_true(s);
667            _test_check_neon_with_case(s, CheckCase::None, true);
668            match (s.contains(char::is_lowercase), s.contains(char::is_uppercase)){
669                (true, true) => {
670                    _test_check_neon_with_case(s, CheckCase::Lower, false);
671                    _test_check_neon_with_case(s, CheckCase::Upper, false);
672                },
673                (true, false) => {
674                    _test_check_neon_with_case(s, CheckCase::Lower, true);
675                    _test_check_neon_with_case(s, CheckCase::Upper, false);
676                },
677                (false, true) => {
678                    _test_check_neon_with_case(s, CheckCase::Lower, false);
679                    _test_check_neon_with_case(s, CheckCase::Upper, true);
680                },
681                (false, false) => {
682                    _test_check_neon_with_case(s, CheckCase::Lower, true);
683                    _test_check_neon_with_case(s, CheckCase::Upper, true);
684                }
685            }
686        }
687    }
688
689    #[cfg(target_arch = "aarch64")]
690    fn _test_check_neon_false(s: &String) {
691        if is_aarch64_feature_detected!("neon") {
692            assert!(!unsafe { hex_check_neon(s.as_bytes()) });
693        }
694    }
695    #[cfg(target_arch = "aarch64")]
696    proptest! {
697        #[test]
698        fn test_check_neon_false(ref s in ".{16}[^0-9a-fA-F]+") {
699            _test_check_neon_false(s);
700            _test_check_neon_with_case(s, CheckCase::None, false);
701            _test_check_neon_with_case(s, CheckCase::Lower, false);
702            _test_check_neon_with_case(s, CheckCase::Upper, false);
703        }
704    }
705
706    #[test]
707    fn test_decode_zero_length_src_should_not_be_ok() {
708        let src = b"";
709        let mut dst = [0u8; 10];
710        assert!(
711            matches!(hex_decode(src, &mut dst), Err(crate::Error::InvalidLength(len)) if len == 20)
712        );
713        assert!(
714            matches!(hex_decode_with_case(src, &mut dst, CheckCase::None), Err(crate::Error::InvalidLength(len)) if len == 20)
715        );
716        assert!(hex_check(src));
717        assert!(hex_check_with_case(src, CheckCase::None));
718        assert!(hex_check_fallback(src));
719        assert!(hex_check_fallback_with_case(src, CheckCase::None));
720
721        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
722        if is_x86_feature_detected!("sse4.1") {
723            assert!(unsafe { hex_check_sse_with_case(src, CheckCase::None) });
724            assert!(unsafe { hex_check_sse(src) });
725        }
726
727        #[cfg(target_arch = "aarch64")]
728        if is_aarch64_feature_detected!("neon") {
729            assert!(unsafe { hex_check_neon_with_case(src, CheckCase::None) });
730            assert!(unsafe { hex_check_neon(src) });
731        }
732
733        // this function have no return value, so we just execute it and expect no panic
734        hex_decode_unchecked(src, &mut dst);
735    }
736
737    // If `dst's length` is greater than `src's length * 2`, `hex_decode` should return error
738    #[test]
739    fn test_if_dst_len_gt_expect_len_should_return_error() {
740        let short_str = b"8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3"; // 62 bytes
741        {
742            let mut dst = [0u8; 31];
743            let result = hex_decode(short_str.as_slice(), &mut dst);
744            assert!(result.is_ok());
745        }
746
747        {
748            let mut dst = [0u8; 32];
749            let result = hex_decode(short_str.as_slice(), &mut dst);
750            assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 64))
751        }
752
753        {
754            let mut dst = [0u8; 33];
755            let result = hex_decode(short_str.as_slice(), &mut dst);
756            assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 66))
757        }
758    }
759
760    // if both `src` and `dst` are empty, it's ok
761    // if `src` is empty, but `dst` is not empty, it should be reported as error
762    #[test]
763    fn test_decode_zero_src() {
764        let zero_src = b"";
765        {
766            let mut zero_dst = [];
767            assert!(hex_decode(zero_src, &mut zero_dst).is_ok());
768        }
769
770        {
771            let mut non_zero_dst = [0u8; 1];
772            assert!(
773                matches!(hex_decode(zero_src, &mut non_zero_dst), Err(crate::Error::InvalidLength(len)) if len == 2)
774            );
775        }
776    }
777}