1#[cfg(target_arch = "x86")]
4use core::arch::x86::*;
5#[cfg(target_arch = "x86_64")]
6use core::arch::x86_64::*;
7
8use crate::error::Error;
9
10const NIL: u8 = u8::MAX;
11
12#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
13const T_MASK: i32 = 65535;
14
15const fn init_unhex_array(check_case: CheckCase) -> [u8; 256] {
16 let mut arr = [0; 256];
17 let mut i = 0;
18 while i < 256 {
19 arr[i] = match i as u8 {
20 b'0'..=b'9' => i as u8 - b'0',
21 b'a'..=b'f' => match check_case {
22 CheckCase::Lower | CheckCase::None => i as u8 - b'a' + 10,
23 _ => NIL,
24 },
25 b'A'..=b'F' => match check_case {
26 CheckCase::Upper | CheckCase::None => i as u8 - b'A' + 10,
27 _ => NIL,
28 },
29 _ => NIL,
30 };
31 i += 1;
32 }
33 arr
34}
35
36const fn init_unhex4_array(check_case: CheckCase) -> [u8; 256] {
37 let unhex_arr = init_unhex_array(check_case);
38
39 let mut unhex4_arr = [NIL; 256];
40 let mut i = 0;
41 while i < 256 {
42 if unhex_arr[i] != NIL {
43 unhex4_arr[i] = unhex_arr[i] << 4;
44 }
45 i += 1;
46 }
47 unhex4_arr
48}
49
50pub(crate) static UNHEX: [u8; 256] = init_unhex_array(CheckCase::None);
52
53pub(crate) static UNHEX_LOWER: [u8; 256] = init_unhex_array(CheckCase::Lower);
55
56pub(crate) static UNHEX_UPPER: [u8; 256] = init_unhex_array(CheckCase::Upper);
58
59pub(crate) static UNHEX4: [u8; 256] = init_unhex4_array(CheckCase::None);
61
62const _0213: i32 = 0b11011000;
63
64#[inline]
66fn unhex_b(x: usize) -> u8 {
67 UNHEX[x]
68}
69
70#[inline]
72fn unhex_a(x: usize) -> u8 {
73 UNHEX4[x]
74}
75
76#[inline]
77#[target_feature(enable = "avx2")]
78#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
79unsafe fn unhex_avx2(value: __m256i) -> __m256i {
80 let sr6 = _mm256_srai_epi16(value, 6);
81 let and15 = _mm256_and_si256(value, _mm256_set1_epi16(0xf));
82 let mul = _mm256_maddubs_epi16(sr6, _mm256_set1_epi16(9));
83 _mm256_add_epi16(mul, and15)
84}
85
86#[inline]
88#[target_feature(enable = "avx2")]
89#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
90unsafe fn nib2byte_avx2(a1: __m256i, b1: __m256i, a2: __m256i, b2: __m256i) -> __m256i {
91 let a4_1 = _mm256_slli_epi16(a1, 4);
92 let a4_2 = _mm256_slli_epi16(a2, 4);
93 let a4orb_1 = _mm256_or_si256(a4_1, b1);
94 let a4orb_2 = _mm256_or_si256(a4_2, b2);
95 let pck1 = _mm256_packus_epi16(a4orb_1, a4orb_2);
96 _mm256_permute4x64_epi64(pck1, _0213)
97}
98
99pub fn hex_check(src: &[u8]) -> bool {
101 hex_check_with_case(src, CheckCase::None)
102}
103
104pub fn hex_check_with_case(src: &[u8], check_case: CheckCase) -> bool {
106 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
107 {
108 match crate::vectorization_support() {
109 crate::Vectorization::AVX2 | crate::Vectorization::SSE41 => unsafe {
110 hex_check_sse_with_case(src, check_case)
111 },
112 crate::Vectorization::None => hex_check_fallback_with_case(src, check_case),
113 }
114 }
115
116 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
117 hex_check_fallback_with_case(src, check_case)
118}
119
120pub fn hex_check_fallback(src: &[u8]) -> bool {
122 hex_check_fallback_with_case(src, CheckCase::None)
123}
124
125pub fn hex_check_fallback_with_case(src: &[u8], check_case: CheckCase) -> bool {
127 match check_case {
128 CheckCase::None => src.iter().all(|&x| UNHEX[x as usize] != NIL),
129 CheckCase::Lower => src.iter().all(|&x| UNHEX_LOWER[x as usize] != NIL),
130 CheckCase::Upper => src.iter().all(|&x| UNHEX_UPPER[x as usize] != NIL),
131 }
132}
133
134#[target_feature(enable = "sse4.1")]
137#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
138pub unsafe fn hex_check_sse(src: &[u8]) -> bool {
139 hex_check_sse_with_case(src, CheckCase::None)
140}
141
142#[derive(Eq, PartialEq)]
143pub enum CheckCase {
144 None,
145 Lower,
146 Upper,
147}
148
149#[target_feature(enable = "sse4.1")]
152#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
153pub unsafe fn hex_check_sse_with_case(mut src: &[u8], check_case: CheckCase) -> bool {
154 let ascii_zero = _mm_set1_epi8((b'0' - 1) as i8);
155 let ascii_nine = _mm_set1_epi8((b'9' + 1) as i8);
156 let ascii_ua = _mm_set1_epi8((b'A' - 1) as i8);
157 let ascii_uf = _mm_set1_epi8((b'F' + 1) as i8);
158 let ascii_la = _mm_set1_epi8((b'a' - 1) as i8);
159 let ascii_lf = _mm_set1_epi8((b'f' + 1) as i8);
160
161 while src.len() >= 16 {
162 let unchecked = _mm_loadu_si128(src.as_ptr() as *const _);
163
164 let gt0 = _mm_cmpgt_epi8(unchecked, ascii_zero);
165 let lt9 = _mm_cmplt_epi8(unchecked, ascii_nine);
166 let valid_digit = _mm_and_si128(gt0, lt9);
167
168 let (valid_la_lf, valid_ua_uf) = match check_case {
169 CheckCase::None => {
170 let gtua = _mm_cmpgt_epi8(unchecked, ascii_ua);
171 let ltuf = _mm_cmplt_epi8(unchecked, ascii_uf);
172
173 let gtla = _mm_cmpgt_epi8(unchecked, ascii_la);
174 let ltlf = _mm_cmplt_epi8(unchecked, ascii_lf);
175
176 (
177 Some(_mm_and_si128(gtla, ltlf)),
178 Some(_mm_and_si128(gtua, ltuf)),
179 )
180 }
181 CheckCase::Lower => {
182 let gtla = _mm_cmpgt_epi8(unchecked, ascii_la);
183 let ltlf = _mm_cmplt_epi8(unchecked, ascii_lf);
184
185 (Some(_mm_and_si128(gtla, ltlf)), None)
186 }
187 CheckCase::Upper => {
188 let gtua = _mm_cmpgt_epi8(unchecked, ascii_ua);
189 let ltuf = _mm_cmplt_epi8(unchecked, ascii_uf);
190 (None, Some(_mm_and_si128(gtua, ltuf)))
191 }
192 };
193
194 let valid_letter = match (valid_la_lf, valid_ua_uf) {
195 (Some(valid_lower), Some(valid_upper)) => _mm_or_si128(valid_lower, valid_upper),
196 (Some(valid_lower), None) => valid_lower,
197 (None, Some(valid_upper)) => valid_upper,
198 _ => unreachable!(),
199 };
200
201 let ret = _mm_movemask_epi8(_mm_or_si128(valid_digit, valid_letter));
202
203 if ret != T_MASK {
204 return false;
205 }
206
207 src = &src[16..];
208 }
209 hex_check_fallback_with_case(src, check_case)
210}
211
212pub fn hex_decode(src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
216 hex_decode_with_case(src, dst, CheckCase::None)
217}
218
219pub fn hex_decode_with_case(
226 src: &[u8],
227 dst: &mut [u8],
228 check_case: CheckCase,
229) -> Result<(), Error> {
230 let len = dst.len().checked_mul(2).ok_or(Error::Overflow)?;
231 if src.len() < len || ((src.len() & 1) != 0) {
232 return Err(Error::InvalidLength(len));
233 }
234
235 if !hex_check_with_case(src, check_case) {
236 return Err(Error::InvalidChar);
237 }
238 hex_decode_unchecked(src, dst);
239 Ok(())
240}
241
242pub fn hex_decode_unchecked(src: &[u8], dst: &mut [u8]) {
243 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
244 {
245 match crate::vectorization_support() {
246 crate::Vectorization::AVX2 => unsafe { hex_decode_avx2(src, dst) },
247 crate::Vectorization::None | crate::Vectorization::SSE41 => {
248 hex_decode_fallback(src, dst)
249 }
250 }
251 }
252 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
253 hex_decode_fallback(src, dst);
254}
255
256#[target_feature(enable = "avx2")]
257#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
258unsafe fn hex_decode_avx2(mut src: &[u8], mut dst: &mut [u8]) {
259 let mask_a = _mm256_setr_epi8(
262 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,
263 -1, 10, -1, 12, -1, 14, -1,
264 );
265
266 let mask_b = _mm256_setr_epi8(
269 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,
270 -1, 11, -1, 13, -1, 15, -1,
271 );
272
273 while dst.len() >= 32 {
274 let av1 = _mm256_loadu_si256(src.as_ptr() as *const _);
275 let av2 = _mm256_loadu_si256(src[32..].as_ptr() as *const _);
276
277 let mut a1 = _mm256_shuffle_epi8(av1, mask_a);
278 let mut b1 = _mm256_shuffle_epi8(av1, mask_b);
279 let mut a2 = _mm256_shuffle_epi8(av2, mask_a);
280 let mut b2 = _mm256_shuffle_epi8(av2, mask_b);
281
282 a1 = unhex_avx2(a1);
283 a2 = unhex_avx2(a2);
284 b1 = unhex_avx2(b1);
285 b2 = unhex_avx2(b2);
286
287 let bytes = nib2byte_avx2(a1, b1, a2, b2);
288
289 _mm256_storeu_si256(dst.as_mut_ptr() as *mut _, bytes);
291 dst = &mut dst[32..];
292 src = &src[64..];
293 }
294 hex_decode_fallback(src, dst)
295}
296
297pub fn hex_decode_fallback(src: &[u8], dst: &mut [u8]) {
298 for (slot, bytes) in dst.iter_mut().zip(src.chunks_exact(2)) {
299 let a = unhex_a(bytes[0] as usize);
300 let b = unhex_b(bytes[1] as usize);
301 *slot = a | b;
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use crate::decode::NIL;
308 use crate::{
309 decode::{
310 hex_check_fallback, hex_check_fallback_with_case, hex_decode_fallback, CheckCase,
311 },
312 encode::hex_string,
313 };
314 use proptest::proptest;
315
316 #[cfg(not(feature = "alloc"))]
317 const CAPACITY: usize = 128;
318
319 fn _test_decode_fallback(s: &String) {
320 let len = s.as_bytes().len();
321 let mut dst = Vec::with_capacity(len);
322 dst.resize(len, 0);
323
324 #[cfg(feature = "alloc")]
325 let hex_string = hex_string(s.as_bytes());
326 #[cfg(not(feature = "alloc"))]
327 let hex_string = hex_string::<CAPACITY>(s.as_bytes());
328
329 hex_decode_fallback(hex_string.as_bytes(), &mut dst);
330
331 assert_eq!(&dst[..], s.as_bytes());
332 }
333
334 #[cfg(feature = "alloc")]
335 proptest! {
336 #[test]
337 fn test_decode_fallback(ref s in ".+") {
338 _test_decode_fallback(s);
339 }
340 }
341
342 #[cfg(not(feature = "alloc"))]
343 proptest! {
344 #[test]
345 fn test_decode_fallback(ref s in ".{1,16}") {
346 _test_decode_fallback(s);
347 }
348 }
349
350 fn _test_check_fallback_true(s: &String) {
351 assert!(hex_check_fallback(s.as_bytes()));
352 match (
353 s.contains(char::is_lowercase),
354 s.contains(char::is_uppercase),
355 ) {
356 (true, true) => {
357 assert!(!hex_check_fallback_with_case(
358 s.as_bytes(),
359 CheckCase::Lower
360 ));
361 assert!(!hex_check_fallback_with_case(
362 s.as_bytes(),
363 CheckCase::Upper
364 ));
365 }
366 (true, false) => {
367 assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
368 assert!(!hex_check_fallback_with_case(
369 s.as_bytes(),
370 CheckCase::Upper
371 ));
372 }
373 (false, true) => {
374 assert!(!hex_check_fallback_with_case(
375 s.as_bytes(),
376 CheckCase::Lower
377 ));
378 assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
379 }
380 (false, false) => {
381 assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Lower));
382 assert!(hex_check_fallback_with_case(s.as_bytes(), CheckCase::Upper));
383 }
384 }
385 }
386
387 proptest! {
388 #[test]
389 fn test_check_fallback_true(ref s in "[0-9a-fA-F]+") {
390 _test_check_fallback_true(s);
391 }
392 }
393
394 fn _test_check_fallback_false(s: &String) {
395 assert!(!hex_check_fallback(s.as_bytes()));
396 assert!(!hex_check_fallback_with_case(
397 s.as_bytes(),
398 CheckCase::Upper
399 ));
400 assert!(!hex_check_fallback_with_case(
401 s.as_bytes(),
402 CheckCase::Lower
403 ));
404 }
405
406 proptest! {
407 #[test]
408 fn test_check_fallback_false(ref s in ".{16}[^0-9a-fA-F]+") {
409 _test_check_fallback_false(s);
410 }
411 }
412
413 #[test]
414 fn test_init_static_array_is_right() {
415 static OLD_UNHEX: [u8; 256] = [
416 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
417 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
418 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 1, 2, 3, 4, 5,
419 6, 7, 8, 9, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL,
420 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
421 NIL, NIL, NIL, NIL, NIL, NIL, 10, 11, 12, 13, 14, 15, NIL, NIL, NIL, NIL, NIL, NIL,
422 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
423 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
424 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
425 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
426 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
427 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
428 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
429 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
430 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
431 ];
432
433 static OLD_UNHEX4: [u8; 256] = [
434 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
435 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
436 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 0, 16, 32, 48,
437 64, 80, 96, 112, 128, 144, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224,
438 240, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
439 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, 160, 176, 192, 208, 224, 240, NIL,
440 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
441 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
442 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
443 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
444 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
445 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
446 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
447 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
448 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL,
449 ];
450
451 assert_eq!(OLD_UNHEX, crate::decode::UNHEX);
452 assert_eq!(OLD_UNHEX4, crate::decode::UNHEX4);
453 }
454}
455
456#[cfg(all(test, any(target_arch = "x86", target_arch = "x86_64")))]
457mod test_sse {
458 use crate::decode::{
459 hex_check, hex_check_fallback, hex_check_fallback_with_case, hex_check_sse,
460 hex_check_sse_with_case, hex_check_with_case, hex_decode, hex_decode_unchecked,
461 hex_decode_with_case, CheckCase,
462 };
463 use proptest::proptest;
464
465 fn _test_check_sse_with_case(s: &String, check_case: CheckCase, expect_result: bool) {
466 if is_x86_feature_detected!("sse4.1") {
467 assert_eq!(
468 unsafe { hex_check_sse_with_case(s.as_bytes(), check_case) },
469 expect_result
470 )
471 }
472 }
473
474 fn _test_check_sse_true(s: &String) {
475 if is_x86_feature_detected!("sse4.1") {
476 assert!(unsafe { hex_check_sse(s.as_bytes()) });
477 }
478 }
479
480 proptest! {
481 #[test]
482 fn test_check_sse_true(ref s in "([0-9a-fA-F][0-9a-fA-F])+") {
483 _test_check_sse_true(s);
484 _test_check_sse_with_case(s, CheckCase::None, true);
485 match (s.contains(char::is_lowercase), s.contains(char::is_uppercase)){
486 (true, true) => {
487 _test_check_sse_with_case(s, CheckCase::Lower, false);
488 _test_check_sse_with_case(s, CheckCase::Upper, false);
489 },
490 (true, false) => {
491 _test_check_sse_with_case(s, CheckCase::Lower, true);
492 _test_check_sse_with_case(s, CheckCase::Upper, false);
493 },
494 (false, true) => {
495 _test_check_sse_with_case(s, CheckCase::Lower, false);
496 _test_check_sse_with_case(s, CheckCase::Upper, true);
497 },
498 (false, false) => {
499 _test_check_sse_with_case(s, CheckCase::Lower, true);
500 _test_check_sse_with_case(s, CheckCase::Upper, true);
501 }
502 }
503 }
504 }
505
506 fn _test_check_sse_false(s: &String) {
507 if is_x86_feature_detected!("sse4.1") {
508 assert!(!unsafe { hex_check_sse(s.as_bytes()) });
509 }
510 }
511
512 proptest! {
513 #[test]
514 fn test_check_sse_false(ref s in ".{16}[^0-9a-fA-F]+") {
515 _test_check_sse_false(s);
516 _test_check_sse_with_case(s, CheckCase::None, false);
517 _test_check_sse_with_case(s, CheckCase::Lower, false);
518 _test_check_sse_with_case(s, CheckCase::Upper, false);
519 }
520 }
521
522 #[test]
523 fn test_decode_zero_length_src_should_not_be_ok() {
524 let src = b"";
525 let mut dst = [0u8; 10];
526 assert!(
527 matches!(hex_decode(src, &mut dst), Err(crate::Error::InvalidLength(len)) if len == 20)
528 );
529 assert!(
530 matches!(hex_decode_with_case(src, &mut dst, CheckCase::None), Err(crate::Error::InvalidLength(len)) if len == 20)
531 );
532 assert!(hex_check(src));
533 assert!(hex_check_with_case(src, CheckCase::None));
534 assert!(hex_check_fallback(src));
535 assert!(hex_check_fallback_with_case(src, CheckCase::None));
536
537 if is_x86_feature_detected!("sse4.1") {
538 assert!(unsafe { hex_check_sse_with_case(src, CheckCase::None) });
539 assert!(unsafe { hex_check_sse(src) });
540 }
541
542 hex_decode_unchecked(src, &mut dst);
544 }
545
546 #[test]
548 fn test_if_dst_len_gt_expect_len_should_return_error() {
549 let short_str = b"8e40af02265360d59f4ecf9ae9ebf8f00a3118408f5a9cdcbcc9c0f93642f3"; {
551 let mut dst = [0u8; 31];
552 let result = hex_decode(short_str.as_slice(), &mut dst);
553 assert!(result.is_ok());
554 }
555
556 {
557 let mut dst = [0u8; 32];
558 let result = hex_decode(short_str.as_slice(), &mut dst);
559 assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 64))
560 }
561
562 {
563 let mut dst = [0u8; 33];
564 let result = hex_decode(short_str.as_slice(), &mut dst);
565 assert!(matches!(result, Err(crate::Error::InvalidLength(len)) if len == 66))
566 }
567 }
568
569 #[test]
572 fn test_decode_zero_src() {
573 let zero_src = b"";
574 {
575 let mut zero_dst = [];
576 assert!(hex_decode(zero_src, &mut zero_dst).is_ok());
577 }
578
579 {
580 let mut non_zero_dst = [0u8; 1];
581 assert!(
582 matches!(hex_decode(zero_src, &mut non_zero_dst), Err(crate::Error::InvalidLength(len)) if len == 2)
583 );
584 }
585 }
586}