1#[cfg(target_arch = "aarch64")]
28use std::arch::aarch64::*;
29
30#[cfg(target_arch = "x86_64")]
31use std::arch::x86_64::*;
32
33#[cfg(target_arch = "aarch64")]
39const HEX_NIBBLE: &[u8; 16] = b"0123456789abcdef";
40
41#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
44const HEX_ENCODE_LUT: &[u8; 512] = b"000102030405060708090a0b0c0d0e0f\
45101112131415161718191a1b1c1d1e1f\
46202122232425262728292a2b2c2d2e2f\
47303132333435363738393a3b3c3d3e3f\
48404142434445464748494a4b4c4d4e4f\
49505152535455565758595a5b5c5d5e5f\
50606162636465666768696a6b6c6d6e6f\
51707172737475767778797a7b7c7d7e7f\
52808182838485868788898a8b8c8d8e8f\
53909192939495969798999a9b9c9d9e9f\
54a0a1a2a3a4a5a6a7a8a9aaabacadaeaf\
55b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\
56c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
57d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\
58e0e1e2e3e4e5e6e7e8e9eaebecedeeef\
59f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
60
61const HEX_DECODE_LUT: [u8; 256] = {
64 let mut table = [0u8; 256];
65 let mut i = 0;
66 while i < 256 {
67 table[i] = match i as u8 {
68 b'0'..=b'9' => (i as u8) - b'0',
69 b'a'..=b'f' => (i as u8) - b'a' + 10,
70 b'A'..=b'F' => (i as u8) - b'A' + 10,
71 _ => 0,
72 };
73 i += 1;
74 }
75 table
76};
77
78#[cfg(target_arch = "aarch64")]
89#[inline]
90pub fn bytes_to_hex_32(bytes: &[u8; 32]) -> String {
91 unsafe {
92 let mut s = String::with_capacity(64);
94 let buf = s.as_mut_vec().as_mut_ptr();
95 let hex_lut = vld1q_u8(HEX_NIBBLE.as_ptr());
96
97 for chunk_idx in 0..2 {
98 let offset = chunk_idx * 16;
99 let out_offset = chunk_idx * 32;
100
101 let input = vld1q_u8(bytes.as_ptr().add(offset));
102 let hi_nibbles = vshrq_n_u8(input, 4);
103 let lo_nibbles = vandq_u8(input, vdupq_n_u8(0x0f));
104 let hi_hex = vqtbl1q_u8(hex_lut, hi_nibbles);
105 let lo_hex = vqtbl1q_u8(hex_lut, lo_nibbles);
106 let result_lo = vzip1q_u8(hi_hex, lo_hex);
107 let result_hi = vzip2q_u8(hi_hex, lo_hex);
108
109 vst1q_u8(buf.add(out_offset), result_lo);
110 vst1q_u8(buf.add(out_offset + 16), result_hi);
111 }
112
113 s.as_mut_vec().set_len(64);
115 s
116 }
117}
118
119#[cfg(target_arch = "aarch64")]
122#[inline]
123pub fn bytes_to_hex_16(bytes: &[u8; 16]) -> String {
124 unsafe {
125 let mut s = String::with_capacity(32);
127 let buf = s.as_mut_vec().as_mut_ptr();
128 let hex_lut = vld1q_u8(HEX_NIBBLE.as_ptr());
129 let input = vld1q_u8(bytes.as_ptr());
130
131 let hi_nibbles = vshrq_n_u8(input, 4);
132 let lo_nibbles = vandq_u8(input, vdupq_n_u8(0x0f));
133 let hi_hex = vqtbl1q_u8(hex_lut, hi_nibbles);
134 let lo_hex = vqtbl1q_u8(hex_lut, lo_nibbles);
135 let result_lo = vzip1q_u8(hi_hex, lo_hex);
136 let result_hi = vzip2q_u8(hi_hex, lo_hex);
137
138 vst1q_u8(buf, result_lo);
139 vst1q_u8(buf.add(16), result_hi);
140
141 s.as_mut_vec().set_len(32);
143 s
144 }
145}
146
147#[cfg(target_arch = "x86_64")]
161#[target_feature(enable = "avx2")]
162#[inline]
163unsafe fn hex_encode_32_avx2(bytes: &[u8; 32], buf: *mut u8) {
164 let and4bits = _mm256_set1_epi8(0x0f);
166 let nines = _mm256_set1_epi8(9);
167 let ascii_zero = _mm256_set1_epi8(b'0' as i8);
168 let ascii_a_offset = _mm256_set1_epi8((b'a' - 9 - 1) as i8);
170
171 let invec = _mm256_loadu_si256(bytes.as_ptr() as *const __m256i);
173
174 let lo_nibbles = _mm256_and_si256(invec, and4bits);
177 let hi_nibbles = _mm256_and_si256(_mm256_srli_epi64(invec, 4), and4bits);
178
179 let lo_gt9 = _mm256_cmpgt_epi8(lo_nibbles, nines);
181 let hi_gt9 = _mm256_cmpgt_epi8(hi_nibbles, nines);
182
183 let lo_hex = _mm256_add_epi8(
187 lo_nibbles,
188 _mm256_blendv_epi8(ascii_zero, ascii_a_offset, lo_gt9),
189 );
190 let hi_hex = _mm256_add_epi8(
191 hi_nibbles,
192 _mm256_blendv_epi8(ascii_zero, ascii_a_offset, hi_gt9),
193 );
194
195 let res1 = _mm256_unpacklo_epi8(hi_hex, lo_hex);
200 let res2 = _mm256_unpackhi_epi8(hi_hex, lo_hex);
201
202 _mm256_storeu2_m128i(
208 buf.add(32) as *mut __m128i, buf as *mut __m128i, res1,
211 );
212 _mm256_storeu2_m128i(
213 buf.add(48) as *mut __m128i, buf.add(16) as *mut __m128i, res2,
216 );
217}
218
219#[cfg(target_arch = "x86_64")]
222#[target_feature(enable = "sse2")]
223#[inline]
224unsafe fn hex_encode_32_sse2(bytes: &[u8; 32], buf: *mut u8) {
225 let mask_lo = _mm_set1_epi8(0x0f);
226 let nine = _mm_set1_epi8(9);
227 let ascii_zero = _mm_set1_epi8(b'0' as i8);
228 let letter_offset = _mm_set1_epi8(0x27); for chunk_idx in 0..2 {
231 let offset = chunk_idx * 16;
232 let out_offset = chunk_idx * 32;
233
234 let input = _mm_loadu_si128(bytes.as_ptr().add(offset) as *const __m128i);
235
236 let hi_nibbles = _mm_and_si128(_mm_srli_epi16(input, 4), mask_lo);
238 let lo_nibbles = _mm_and_si128(input, mask_lo);
239
240 let hi_gt9 = _mm_cmpgt_epi8(hi_nibbles, nine);
242 let lo_gt9 = _mm_cmpgt_epi8(lo_nibbles, nine);
243
244 let hi_hex = _mm_add_epi8(
245 _mm_add_epi8(hi_nibbles, ascii_zero),
246 _mm_and_si128(hi_gt9, letter_offset),
247 );
248 let lo_hex = _mm_add_epi8(
249 _mm_add_epi8(lo_nibbles, ascii_zero),
250 _mm_and_si128(lo_gt9, letter_offset),
251 );
252
253 let result_lo = _mm_unpacklo_epi8(hi_hex, lo_hex);
255 let result_hi = _mm_unpackhi_epi8(hi_hex, lo_hex);
256
257 _mm_storeu_si128(buf.add(out_offset) as *mut __m128i, result_lo);
258 _mm_storeu_si128(buf.add(out_offset + 16) as *mut __m128i, result_hi);
259 }
260}
261
262#[cfg(target_arch = "x86_64")]
267#[inline]
268pub fn bytes_to_hex_32(bytes: &[u8; 32]) -> String {
269 unsafe {
270 let mut s = String::with_capacity(64);
271 let buf = s.as_mut_vec().as_mut_ptr();
272
273 if is_x86_feature_detected!("avx2") {
275 hex_encode_32_avx2(bytes, buf);
276 } else {
277 hex_encode_32_sse2(bytes, buf);
278 }
279
280 s.as_mut_vec().set_len(64);
281 s
282 }
283}
284
285#[cfg(target_arch = "x86_64")]
287#[target_feature(enable = "sse2")]
288#[inline]
289unsafe fn hex_encode_16_sse2(bytes: &[u8; 16], buf: *mut u8) {
290 let mask_lo = _mm_set1_epi8(0x0f);
291 let nine = _mm_set1_epi8(9);
292 let ascii_zero = _mm_set1_epi8(b'0' as i8);
293 let letter_offset = _mm_set1_epi8(0x27);
294
295 let input = _mm_loadu_si128(bytes.as_ptr() as *const __m128i);
296
297 let hi_nibbles = _mm_and_si128(_mm_srli_epi16(input, 4), mask_lo);
298 let lo_nibbles = _mm_and_si128(input, mask_lo);
299
300 let hi_gt9 = _mm_cmpgt_epi8(hi_nibbles, nine);
301 let lo_gt9 = _mm_cmpgt_epi8(lo_nibbles, nine);
302
303 let hi_hex = _mm_add_epi8(
304 _mm_add_epi8(hi_nibbles, ascii_zero),
305 _mm_and_si128(hi_gt9, letter_offset),
306 );
307 let lo_hex = _mm_add_epi8(
308 _mm_add_epi8(lo_nibbles, ascii_zero),
309 _mm_and_si128(lo_gt9, letter_offset),
310 );
311
312 let result_lo = _mm_unpacklo_epi8(hi_hex, lo_hex);
313 let result_hi = _mm_unpackhi_epi8(hi_hex, lo_hex);
314
315 _mm_storeu_si128(buf as *mut __m128i, result_lo);
316 _mm_storeu_si128(buf.add(16) as *mut __m128i, result_hi);
317}
318
319#[cfg(target_arch = "x86_64")]
321#[inline]
322pub fn bytes_to_hex_16(bytes: &[u8; 16]) -> String {
323 unsafe {
324 let mut s = String::with_capacity(32);
325 let buf = s.as_mut_vec().as_mut_ptr();
326 hex_encode_16_sse2(bytes, buf);
327 s.as_mut_vec().set_len(32);
328 s
329 }
330}
331
332#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
338#[inline]
339pub fn bytes_to_hex_32(bytes: &[u8; 32]) -> String {
340 unsafe {
341 let mut s = String::with_capacity(64);
342 let buf = s.as_mut_vec().as_mut_ptr();
343 for (i, &b) in bytes.iter().enumerate() {
344 let idx = (b as usize) * 2;
345 *buf.add(i * 2) = HEX_ENCODE_LUT[idx];
346 *buf.add(i * 2 + 1) = HEX_ENCODE_LUT[idx + 1];
347 }
348 s.as_mut_vec().set_len(64);
349 s
350 }
351}
352
353#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
355#[inline]
356pub fn bytes_to_hex_16(bytes: &[u8; 16]) -> String {
357 unsafe {
358 let mut s = String::with_capacity(32);
359 let buf = s.as_mut_vec().as_mut_ptr();
360 for (i, &b) in bytes.iter().enumerate() {
361 let idx = (b as usize) * 2;
362 *buf.add(i * 2) = HEX_ENCODE_LUT[idx];
363 *buf.add(i * 2 + 1) = HEX_ENCODE_LUT[idx + 1];
364 }
365 s.as_mut_vec().set_len(32);
366 s
367 }
368}
369
370pub fn bytes_to_hex_string(bytes: &[u8]) -> String {
383 if bytes.len() == 32 {
385 return bytes_to_hex_32(bytes.try_into().unwrap());
386 }
387 if bytes.len() == 16 {
388 return bytes_to_hex_16(bytes.try_into().unwrap());
389 }
390
391 let out_len = bytes.len().checked_mul(2).expect("hex string length overflow");
392
393 #[cfg(target_arch = "aarch64")]
394 unsafe {
395 let mut s = String::with_capacity(out_len);
397 let out_ptr = s.as_mut_vec().as_mut_ptr();
398 let chunks = bytes.len() / 16;
399 let hex_lut = vld1q_u8(HEX_NIBBLE.as_ptr());
400
401 for i in 0..chunks {
403 let input = vld1q_u8(bytes.as_ptr().add(i * 16));
404 let hi = vshrq_n_u8(input, 4);
405 let lo = vandq_u8(input, vdupq_n_u8(0x0f));
406 let hi_hex = vqtbl1q_u8(hex_lut, hi);
407 let lo_hex = vqtbl1q_u8(hex_lut, lo);
408 vst1q_u8(out_ptr.add(i * 32), vzip1q_u8(hi_hex, lo_hex));
409 vst1q_u8(out_ptr.add(i * 32 + 16), vzip2q_u8(hi_hex, lo_hex));
410 }
411
412 let remainder_start = chunks * 16;
414 let mut out_idx = chunks * 32;
415 for &b in &bytes[remainder_start..] {
416 *out_ptr.add(out_idx) = HEX_NIBBLE[(b >> 4) as usize];
417 *out_ptr.add(out_idx + 1) = HEX_NIBBLE[(b & 0xf) as usize];
418 out_idx += 2;
419 }
420
421 s.as_mut_vec().set_len(out_len);
422 s
423 }
424
425 #[cfg(target_arch = "x86_64")]
426 unsafe {
427 let mut s = String::with_capacity(out_len);
429 let out_ptr = s.as_mut_vec().as_mut_ptr();
430 let chunks = bytes.len() / 16;
431
432 let mask_lo = _mm_set1_epi8(0x0f);
434 let nine = _mm_set1_epi8(9);
435 let ascii_zero = _mm_set1_epi8(b'0' as i8);
436 let letter_offset = _mm_set1_epi8(0x27);
437
438 for i in 0..chunks {
440 let input = _mm_loadu_si128(bytes.as_ptr().add(i * 16) as *const __m128i);
441
442 let hi = _mm_and_si128(_mm_srli_epi16(input, 4), mask_lo);
443 let lo = _mm_and_si128(input, mask_lo);
444
445 let hi_gt9 = _mm_cmpgt_epi8(hi, nine);
446 let lo_gt9 = _mm_cmpgt_epi8(lo, nine);
447
448 let hi_hex = _mm_add_epi8(
449 _mm_add_epi8(hi, ascii_zero),
450 _mm_and_si128(hi_gt9, letter_offset),
451 );
452 let lo_hex = _mm_add_epi8(
453 _mm_add_epi8(lo, ascii_zero),
454 _mm_and_si128(lo_gt9, letter_offset),
455 );
456
457 _mm_storeu_si128(out_ptr.add(i * 32) as *mut __m128i, _mm_unpacklo_epi8(hi_hex, lo_hex));
458 _mm_storeu_si128(out_ptr.add(i * 32 + 16) as *mut __m128i, _mm_unpackhi_epi8(hi_hex, lo_hex));
459 }
460
461 const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
463 let remainder_start = chunks * 16;
464 let mut out_idx = chunks * 32;
465 for &b in &bytes[remainder_start..] {
466 *out_ptr.add(out_idx) = HEX_CHARS[(b >> 4) as usize];
467 *out_ptr.add(out_idx + 1) = HEX_CHARS[(b & 0xf) as usize];
468 out_idx += 2;
469 }
470
471 s.as_mut_vec().set_len(out_len);
472 s
473 }
474
475 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
476 unsafe {
477 let mut s = String::with_capacity(out_len);
479 let out_ptr = s.as_mut_vec().as_mut_ptr();
480 for (i, &b) in bytes.iter().enumerate() {
481 let idx = (b as usize) * 2;
482 *out_ptr.add(i * 2) = HEX_ENCODE_LUT[idx];
483 *out_ptr.add(i * 2 + 1) = HEX_ENCODE_LUT[idx + 1];
484 }
485 s.as_mut_vec().set_len(out_len);
486 s
487 }
488}
489
490#[cfg(target_arch = "aarch64")]
504#[inline]
505pub fn hex_to_bytes_32(hex: &str) -> [u8; 32] {
506 let h = hex.as_bytes();
507
508 if h.len() >= 64 {
510 return unsafe { hex_decode_32_neon(h) };
511 }
512
513 hex_to_bytes_32_scalar_padded(h)
515}
516
517#[cfg(target_arch = "aarch64")]
527#[inline]
528unsafe fn hex_decode_32_neon(h: &[u8]) -> [u8; 32] {
529 let mut result = [0u8; 32];
530
531 let mask_0f = vdupq_n_u8(0x0F);
532 let mask_40 = vdupq_n_u8(0x40);
533 let nine = vdupq_n_u8(9);
534
535 let hex_0 = vld1q_u8(h.as_ptr());
537 let hex_1 = vld1q_u8(h.as_ptr().add(16));
538 let hex_2 = vld1q_u8(h.as_ptr().add(32));
539 let hex_3 = vld1q_u8(h.as_ptr().add(48));
540
541 let lo0 = vandq_u8(hex_0, mask_0f);
544 let lo1 = vandq_u8(hex_1, mask_0f);
545 let lo2 = vandq_u8(hex_2, mask_0f);
546 let lo3 = vandq_u8(hex_3, mask_0f);
547
548 let is_letter0 = vtstq_u8(hex_0, mask_40);
549 let is_letter1 = vtstq_u8(hex_1, mask_40);
550 let is_letter2 = vtstq_u8(hex_2, mask_40);
551 let is_letter3 = vtstq_u8(hex_3, mask_40);
552
553 let n0 = vaddq_u8(lo0, vandq_u8(is_letter0, nine));
554 let n1 = vaddq_u8(lo1, vandq_u8(is_letter1, nine));
555 let n2 = vaddq_u8(lo2, vandq_u8(is_letter2, nine));
556 let n3 = vaddq_u8(lo3, vandq_u8(is_letter3, nine));
557
558 let evens_a = vuzp1q_u8(n0, n1);
561 let odds_a = vuzp2q_u8(n0, n1);
562 let bytes_a = vsliq_n_u8(odds_a, evens_a, 4);
563
564 let evens_b = vuzp1q_u8(n2, n3);
565 let odds_b = vuzp2q_u8(n2, n3);
566 let bytes_b = vsliq_n_u8(odds_b, evens_b, 4);
567
568 vst1q_u8(result.as_mut_ptr(), bytes_a);
569 vst1q_u8(result.as_mut_ptr().add(16), bytes_b);
570
571 result
572}
573
574#[cfg(target_arch = "aarch64")]
578#[inline]
579unsafe fn neon_all_hex(c: uint8x16_t) -> u8 {
580 let is_digit = vandq_u8(vcgeq_u8(c, vdupq_n_u8(b'0')), vcleq_u8(c, vdupq_n_u8(b'9')));
581 let is_upper = vandq_u8(vcgeq_u8(c, vdupq_n_u8(b'A')), vcleq_u8(c, vdupq_n_u8(b'F')));
582 let is_lower = vandq_u8(vcgeq_u8(c, vdupq_n_u8(b'a')), vcleq_u8(c, vdupq_n_u8(b'f')));
583 vminvq_u8(vorrq_u8(vorrq_u8(is_digit, is_upper), is_lower))
584}
585
586#[cfg(target_arch = "aarch64")]
590#[inline]
591unsafe fn hex_decode_32_neon_checked(h: &[u8]) -> Option<[u8; 32]> {
592 let hex_0 = vld1q_u8(h.as_ptr());
593 let hex_1 = vld1q_u8(h.as_ptr().add(16));
594 let hex_2 = vld1q_u8(h.as_ptr().add(32));
595 let hex_3 = vld1q_u8(h.as_ptr().add(48));
596 if (neon_all_hex(hex_0) & neon_all_hex(hex_1) & neon_all_hex(hex_2) & neon_all_hex(hex_3)) != 0xFF {
597 return None;
598 }
599 Some(hex_decode_32_neon(h))
600}
601
602#[cfg(target_arch = "x86_64")]
604#[inline]
605pub fn hex_to_bytes_32(hex: &str) -> [u8; 32] {
606 let h = hex.as_bytes();
607
608 if h.len() >= 64 {
609 let arr: &[u8; 64] = h[..64].try_into().unwrap();
611 return unsafe { hex_decode_32_sse2(arr) };
612 }
613
614 hex_to_bytes_32_scalar_padded(h)
615}
616
617#[cfg(target_arch = "x86_64")]
626#[target_feature(enable = "sse2")]
627#[inline]
628unsafe fn hex_decode_32_sse2(h: &[u8; 64]) -> [u8; 32] {
629 let mut result = [0u8; 32];
630
631 let mask_0f = _mm_set1_epi8(0x0F);
633 let mask_40 = _mm_set1_epi8(0x40);
634 let nine = _mm_set1_epi8(9);
635 let hi_mask = _mm_set1_epi16(0x00F0u16 as i16);
636 let lo_mask = _mm_set1_epi16(0x000Fu16 as i16);
637 let zero = _mm_setzero_si128();
638
639 for chunk in 0..4 {
641 let in_offset = chunk * 16;
642 let out_offset = chunk * 8;
643
644 let hex_chars = _mm_loadu_si128(h.as_ptr().add(in_offset) as *const __m128i);
645
646 let lo = _mm_and_si128(hex_chars, mask_0f);
649 let masked = _mm_and_si128(hex_chars, mask_40);
650 let is_letter = _mm_cmpeq_epi8(masked, mask_40);
651 let nine_if_letter = _mm_and_si128(is_letter, nine);
652 let nibbles = _mm_add_epi8(lo, nine_if_letter);
653
654 let hi_nibbles = _mm_slli_epi16(nibbles, 4);
656 let hi = _mm_and_si128(hi_nibbles, hi_mask);
657 let lo_shifted = _mm_and_si128(_mm_srli_epi16(nibbles, 8), lo_mask);
658 let combined = _mm_or_si128(hi, lo_shifted);
659
660 let packed = _mm_packus_epi16(combined, zero);
661 _mm_storel_epi64(result.as_mut_ptr().add(out_offset) as *mut __m128i, packed);
662 }
663
664 result
665}
666
667#[cfg(target_arch = "x86_64")]
670#[target_feature(enable = "sse2")]
671#[inline]
672unsafe fn sse2_chunk_all_hex(c: __m128i) -> bool {
673 let is_digit = _mm_and_si128(_mm_cmpgt_epi8(c, _mm_set1_epi8(0x2F)), _mm_cmpgt_epi8(_mm_set1_epi8(0x3A), c));
674 let is_upper = _mm_and_si128(_mm_cmpgt_epi8(c, _mm_set1_epi8(0x40)), _mm_cmpgt_epi8(_mm_set1_epi8(0x47), c));
675 let is_lower = _mm_and_si128(_mm_cmpgt_epi8(c, _mm_set1_epi8(0x60)), _mm_cmpgt_epi8(_mm_set1_epi8(0x67), c));
676 let valid = _mm_or_si128(_mm_or_si128(is_digit, is_upper), is_lower);
677 _mm_movemask_epi8(valid) == 0xFFFF
678}
679
680#[cfg(target_arch = "x86_64")]
682#[target_feature(enable = "sse2")]
683#[inline]
684unsafe fn hex_decode_32_sse2_checked(h: &[u8; 64]) -> Option<[u8; 32]> {
685 let mut chunk = 0;
686 while chunk < 4 {
687 let c = _mm_loadu_si128(h.as_ptr().add(chunk * 16) as *const __m128i);
688 if !sse2_chunk_all_hex(c) {
689 return None;
690 }
691 chunk += 1;
692 }
693 Some(hex_decode_32_sse2(h))
694}
695
696#[cfg(target_arch = "x86_64")]
705#[target_feature(enable = "sse2")]
706#[inline]
707unsafe fn hex_decode_16_sse2(h: &[u8; 32]) -> [u8; 16] {
708 let mut result = [0u8; 16];
709
710 let mask_0f = _mm_set1_epi8(0x0F);
712 let mask_40 = _mm_set1_epi8(0x40);
713 let nine = _mm_set1_epi8(9);
714 let hi_mask = _mm_set1_epi16(0x00F0u16 as i16);
715 let lo_mask = _mm_set1_epi16(0x000Fu16 as i16);
716 let zero = _mm_setzero_si128();
717
718 for chunk in 0..2 {
720 let in_offset = chunk * 16;
721 let out_offset = chunk * 8;
722
723 let hex_chars = _mm_loadu_si128(h.as_ptr().add(in_offset) as *const __m128i);
724
725 let lo = _mm_and_si128(hex_chars, mask_0f);
728 let masked = _mm_and_si128(hex_chars, mask_40);
729 let is_letter = _mm_cmpeq_epi8(masked, mask_40);
730 let nine_if_letter = _mm_and_si128(is_letter, nine);
731 let nibbles = _mm_add_epi8(lo, nine_if_letter);
732
733 let hi_nibbles = _mm_slli_epi16(nibbles, 4);
735 let hi = _mm_and_si128(hi_nibbles, hi_mask);
736 let lo_shifted = _mm_and_si128(_mm_srli_epi16(nibbles, 8), lo_mask);
737 let combined = _mm_or_si128(hi, lo_shifted);
738
739 let packed = _mm_packus_epi16(combined, zero);
740 _mm_storel_epi64(result.as_mut_ptr().add(out_offset) as *mut __m128i, packed);
741 }
742
743 result
744}
745
746#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
748#[inline]
749pub fn hex_to_bytes_32(hex: &str) -> [u8; 32] {
750 let h = hex.as_bytes();
751
752 if h.len() >= 64 {
753 let mut bytes = [0u8; 32];
754 for i in 0..32 {
755 bytes[i] = (HEX_DECODE_LUT[h[i * 2] as usize] << 4)
756 | HEX_DECODE_LUT[h[i * 2 + 1] as usize];
757 }
758 return bytes;
759 }
760
761 hex_to_bytes_32_scalar_padded(h)
762}
763
764#[inline]
766fn hex_to_bytes_32_scalar_padded(h: &[u8]) -> [u8; 32] {
767 let mut bytes = [0u8; 32];
768 let hex_len = h.len();
769 let start_idx = (64 - hex_len) / 2;
770 let mut out_idx = start_idx / 2;
771
772 let mut i = 0;
773 while i + 1 < hex_len && out_idx < 32 {
774 bytes[out_idx] = (HEX_DECODE_LUT[h[i] as usize] << 4)
775 | HEX_DECODE_LUT[h[i + 1] as usize];
776 out_idx += 1;
777 i += 2;
778 }
779 bytes
780}
781
782#[cfg(target_arch = "aarch64")]
789#[inline]
790pub fn hex_to_bytes_32_checked(hex: &str) -> Option<[u8; 32]> {
791 let h = hex.as_bytes();
792 if h.len() != 64 {
793 return None;
794 }
795 unsafe { hex_decode_32_neon_checked(h) }
796}
797
798#[cfg(target_arch = "x86_64")]
800#[inline]
801pub fn hex_to_bytes_32_checked(hex: &str) -> Option<[u8; 32]> {
802 let h = hex.as_bytes();
803 if h.len() != 64 {
804 return None;
805 }
806 let arr: &[u8; 64] = h[..64].try_into().unwrap();
807 unsafe { hex_decode_32_sse2_checked(arr) }
808}
809
810#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
812#[inline]
813pub fn hex_to_bytes_32_checked(hex: &str) -> Option<[u8; 32]> {
814 let h = hex.as_bytes();
815 if h.len() != 64 || !h.iter().all(u8::is_ascii_hexdigit) {
816 return None;
817 }
818 Some(hex_to_bytes_32(hex))
819}
820
821#[cfg(target_arch = "aarch64")]
831#[inline]
832pub fn hex_to_bytes_16(hex: &str) -> [u8; 16] {
833 let h = hex.as_bytes();
834
835 if h.len() >= 32 {
836 return unsafe { hex_decode_16_neon(h) };
837 }
838
839 hex_to_bytes_16_scalar_padded(h)
840}
841
842#[cfg(target_arch = "aarch64")]
848#[inline]
849unsafe fn hex_decode_16_neon(h: &[u8]) -> [u8; 16] {
850 let mut result = [0u8; 16];
851
852 let mask_0f = vdupq_n_u8(0x0F);
853 let mask_40 = vdupq_n_u8(0x40);
854 let nine = vdupq_n_u8(9);
855
856 let hex_0 = vld1q_u8(h.as_ptr());
858 let hex_1 = vld1q_u8(h.as_ptr().add(16));
859
860 let lo0 = vandq_u8(hex_0, mask_0f);
862 let lo1 = vandq_u8(hex_1, mask_0f);
863
864 let is_letter0 = vtstq_u8(hex_0, mask_40);
865 let is_letter1 = vtstq_u8(hex_1, mask_40);
866
867 let n0 = vaddq_u8(lo0, vandq_u8(is_letter0, nine));
868 let n1 = vaddq_u8(lo1, vandq_u8(is_letter1, nine));
869
870 let evens = vuzp1q_u8(n0, n1);
872 let odds = vuzp2q_u8(n0, n1);
873 let bytes = vsliq_n_u8(odds, evens, 4);
874
875 vst1q_u8(result.as_mut_ptr(), bytes);
876 result
877}
878
879#[cfg(target_arch = "x86_64")]
880#[inline]
881pub fn hex_to_bytes_16(hex: &str) -> [u8; 16] {
882 let h = hex.as_bytes();
883
884 if h.len() >= 32 {
885 let arr: &[u8; 32] = h[..32].try_into().unwrap();
887 return unsafe { hex_decode_16_sse2(arr) };
888 }
889
890 hex_to_bytes_16_scalar_padded(h)
891}
892
893#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
894#[inline]
895pub fn hex_to_bytes_16(hex: &str) -> [u8; 16] {
896 let h = hex.as_bytes();
897
898 if h.len() >= 32 {
899 let mut bytes = [0u8; 16];
900 for i in 0..16 {
901 bytes[i] = (HEX_DECODE_LUT[h[i * 2] as usize] << 4)
902 | HEX_DECODE_LUT[h[i * 2 + 1] as usize];
903 }
904 return bytes;
905 }
906
907 hex_to_bytes_16_scalar_padded(h)
908}
909
910#[inline]
911fn hex_to_bytes_16_scalar_padded(h: &[u8]) -> [u8; 16] {
912 let mut bytes = [0u8; 16];
913 let hex_len = h.len();
914 let start_idx = (32 - hex_len) / 2;
915 let mut out_idx = start_idx / 2;
916
917 let mut i = 0;
918 while i + 1 < hex_len && out_idx < 16 {
919 bytes[out_idx] = (HEX_DECODE_LUT[h[i] as usize] << 4)
920 | HEX_DECODE_LUT[h[i + 1] as usize];
921 out_idx += 1;
922 i += 2;
923 }
924 bytes
925}
926
927#[allow(clippy::uninit_vec)]
937pub fn hex_string_to_bytes(s: &str) -> Vec<u8> {
938 let h = s.as_bytes();
939 let out_len = h.len() / 2;
940 let mut result = Vec::with_capacity(out_len);
941
942 #[cfg(target_arch = "aarch64")]
943 unsafe {
944 result.set_len(out_len);
945 let out_ptr: *mut u8 = result.as_mut_ptr();
946
947 let mask_0f = vdupq_n_u8(0x0F);
948 let mask_40 = vdupq_n_u8(0x40);
949 let nine = vdupq_n_u8(9);
950
951 let chunks = out_len / 16; for chunk in 0..chunks {
953 let in_offset = chunk * 32;
954 let out_offset = chunk * 16;
955
956 let hex_0 = vld1q_u8(h.as_ptr().add(in_offset));
958 let hex_1 = vld1q_u8(h.as_ptr().add(in_offset + 16));
959
960 let lo0 = vandq_u8(hex_0, mask_0f);
962 let lo1 = vandq_u8(hex_1, mask_0f);
963
964 let is_letter0 = vtstq_u8(hex_0, mask_40);
965 let is_letter1 = vtstq_u8(hex_1, mask_40);
966
967 let n0 = vaddq_u8(lo0, vandq_u8(is_letter0, nine));
968 let n1 = vaddq_u8(lo1, vandq_u8(is_letter1, nine));
969
970 let evens = vuzp1q_u8(n0, n1);
972 let odds = vuzp2q_u8(n0, n1);
973 let bytes = vsliq_n_u8(odds, evens, 4);
974
975 vst1q_u8(out_ptr.add(out_offset), bytes);
976 }
977
978 let remainder_start = chunks * 32;
980 let mut out_idx = chunks * 16;
981 let mut i = remainder_start;
982 while i + 1 < h.len() {
983 *out_ptr.add(out_idx) = (HEX_DECODE_LUT[h[i] as usize] << 4)
984 | HEX_DECODE_LUT[h[i + 1] as usize];
985 out_idx += 1;
986 i += 2;
987 }
988 }
989
990 #[cfg(target_arch = "x86_64")]
991 unsafe {
992 result.set_len(out_len);
993 let out_ptr: *mut u8 = result.as_mut_ptr();
994
995 let mask_0f = _mm_set1_epi8(0x0F);
996 let mask_40 = _mm_set1_epi8(0x40);
997 let nine = _mm_set1_epi8(9);
998 let hi_mask = _mm_set1_epi16(0x00F0u16 as i16);
999 let lo_mask = _mm_set1_epi16(0x000Fu16 as i16);
1000 let zero = _mm_setzero_si128();
1001
1002 let chunks = out_len / 8; for chunk in 0..chunks {
1004 let in_offset = chunk * 16;
1005 let out_offset = chunk * 8;
1006
1007 let hex_chars = _mm_loadu_si128(h.as_ptr().add(in_offset) as *const __m128i);
1008
1009 let lo = _mm_and_si128(hex_chars, mask_0f);
1011 let masked = _mm_and_si128(hex_chars, mask_40);
1012 let is_letter = _mm_cmpeq_epi8(masked, mask_40);
1013 let nine_if_letter = _mm_and_si128(is_letter, nine);
1014 let nibbles = _mm_add_epi8(lo, nine_if_letter);
1015
1016 let hi_nibbles = _mm_slli_epi16(nibbles, 4);
1018 let hi = _mm_and_si128(hi_nibbles, hi_mask);
1019 let lo_shifted = _mm_and_si128(_mm_srli_epi16(nibbles, 8), lo_mask);
1020 let combined = _mm_or_si128(hi, lo_shifted);
1021
1022 let packed = _mm_packus_epi16(combined, zero);
1023 _mm_storel_epi64(out_ptr.add(out_offset) as *mut __m128i, packed);
1024 }
1025
1026 let remainder_start = chunks * 16;
1028 let mut out_idx = chunks * 8;
1029 let mut i = remainder_start;
1030 while i + 1 < h.len() {
1031 *out_ptr.add(out_idx) = (HEX_DECODE_LUT[h[i] as usize] << 4)
1032 | HEX_DECODE_LUT[h[i + 1] as usize];
1033 out_idx += 1;
1034 i += 2;
1035 }
1036 }
1037
1038 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
1039 {
1040 for chunk in h.chunks(2) {
1041 if chunk.len() == 2 {
1042 result.push(
1043 (HEX_DECODE_LUT[chunk[0] as usize] << 4) | HEX_DECODE_LUT[chunk[1] as usize]
1044 );
1045 }
1046 }
1047 }
1048
1049 result
1050}
1051
1052#[cfg(target_arch = "aarch64")]
1055#[inline]
1056fn all_ascii_hex(h: &[u8]) -> bool {
1057 let chunks = h.len() / 16;
1058 unsafe {
1059 let mut acc = 0xFFu8;
1060 for i in 0..chunks {
1061 acc &= neon_all_hex(vld1q_u8(h.as_ptr().add(i * 16)));
1062 }
1063 if acc != 0xFF {
1064 return false;
1065 }
1066 }
1067 h[chunks * 16..].iter().all(u8::is_ascii_hexdigit)
1068}
1069
1070#[cfg(target_arch = "x86_64")]
1072#[inline]
1073fn all_ascii_hex(h: &[u8]) -> bool {
1074 let chunks = h.len() / 16;
1075 unsafe {
1076 for i in 0..chunks {
1077 let c = _mm_loadu_si128(h.as_ptr().add(i * 16) as *const __m128i);
1078 if !sse2_chunk_all_hex(c) {
1079 return false;
1080 }
1081 }
1082 }
1083 h[chunks * 16..].iter().all(u8::is_ascii_hexdigit)
1084}
1085
1086#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
1088#[inline]
1089fn all_ascii_hex(h: &[u8]) -> bool {
1090 h.iter().all(u8::is_ascii_hexdigit)
1091}
1092
1093#[inline]
1099pub fn hex_string_to_bytes_checked(s: &str) -> Option<Vec<u8>> {
1100 let h = s.as_bytes();
1101 if h.len() % 2 != 0 || !all_ascii_hex(h) {
1102 return None;
1103 }
1104 Some(hex_string_to_bytes(s))
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use super::*;
1110
1111 #[test]
1112 fn checked_varlen_decode_accepts_valid_and_rejects_garbage() {
1113 for len_bytes in [0usize, 1, 7, 8, 16, 39, 78, 256] {
1116 let bytes: Vec<u8> = (0..len_bytes).map(|i| (i * 7 + 3) as u8).collect();
1117 let hex = bytes_to_hex_string(&bytes);
1118 assert_eq!(hex_string_to_bytes_checked(&hex), Some(hex_string_to_bytes(&hex)), "{len_bytes}B");
1119 assert_eq!(hex_string_to_bytes_checked(&hex).unwrap(), bytes, "{len_bytes}B roundtrip");
1120 }
1121 assert!(hex_string_to_bytes_checked("DEADBEEF").is_some());
1123 assert_eq!(hex_string_to_bytes_checked("abc"), None);
1125 assert_eq!(hex_string_to_bytes_checked("00112g3344556677"), None, "bad char in SIMD chunk");
1127 assert_eq!(hex_string_to_bytes_checked("00112233445566778z99"), None, "bad char in remainder");
1129 assert_eq!(hex_string_to_bytes_checked(""), Some(Vec::new()));
1131 }
1132
1133 #[test]
1134 fn checked_decode_accepts_valid_and_rejects_garbage() {
1135 let lower = "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210";
1137 let upper = lower.to_uppercase();
1138 assert_eq!(hex_to_bytes_32_checked(lower), Some(hex_to_bytes_32(lower)));
1139 assert_eq!(hex_to_bytes_32_checked(&upper), Some(hex_to_bytes_32(lower)));
1140 assert_eq!(hex_to_bytes_32_checked(lower).unwrap()[0], 0x00);
1141 assert_eq!(hex_to_bytes_32_checked(lower).unwrap()[31], 0x10);
1142
1143 assert_eq!(hex_to_bytes_32_checked(&lower[..63]), None);
1145 assert_eq!(hex_to_bytes_32_checked(&format!("{lower}0")), None);
1146 assert_eq!(hex_to_bytes_32_checked(""), None);
1147
1148 let mut bad = lower.to_string();
1150 bad.replace_range(0..1, "g");
1151 assert_eq!(hex_to_bytes_32_checked(&bad), None, "non-hex at start");
1152 let mut bad = lower.to_string();
1153 bad.replace_range(32..33, "Z");
1154 assert_eq!(hex_to_bytes_32_checked(&bad), None, "non-hex in middle");
1155 let mut bad = lower.to_string();
1156 bad.replace_range(63..64, " ");
1157 assert_eq!(hex_to_bytes_32_checked(&bad), None, "non-hex at end");
1158
1159 for bad_char in ['/', ':', '@', 'G', '`', 'g'] {
1162 let mut s = lower.to_string();
1163 s.replace_range(10..11, &bad_char.to_string());
1164 assert_eq!(hex_to_bytes_32_checked(&s), None, "boundary char {bad_char:?} must reject");
1165 }
1166 for ok_char in ['0', '9', 'a', 'f', 'A', 'F'] {
1168 let mut s = lower.to_string();
1169 s.replace_range(10..11, &ok_char.to_string());
1170 assert!(hex_to_bytes_32_checked(&s).is_some(), "valid char {ok_char:?} must accept");
1171 }
1172 }
1173
1174 #[test]
1175 fn test_hex_encode_32() {
1176 let bytes: [u8; 32] = [
1177 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
1178 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
1179 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
1180 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
1181 ];
1182 let hex = bytes_to_hex_32(&bytes);
1183 assert_eq!(hex, "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210");
1184 }
1185
1186 #[test]
1187 fn test_hex_decode_32() {
1188 let hex = "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210";
1189 let bytes = hex_to_bytes_32(hex);
1190 assert_eq!(bytes[0], 0x00);
1191 assert_eq!(bytes[15], 0xff);
1192 assert_eq!(bytes[31], 0x10);
1193 }
1194
1195 #[test]
1196 fn test_roundtrip() {
1197 let original: [u8; 32] = [42; 32];
1198 let hex = bytes_to_hex_32(&original);
1199 let decoded = hex_to_bytes_32(&hex);
1200 assert_eq!(original, decoded);
1201 }
1202
1203 #[test]
1204 fn test_hex_decode_16() {
1205 let hex = "00112233445566778899aabbccddeeff";
1206 let bytes = hex_to_bytes_16(hex);
1207 assert_eq!(bytes[0], 0x00);
1208 assert_eq!(bytes[7], 0x77);
1209 assert_eq!(bytes[15], 0xff);
1210 }
1211
1212 #[test]
1213 fn test_hex_decode_uppercase() {
1214 let lowercase = "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210";
1216 let uppercase = "00112233445566778899AABBCCDDEEFF0123456789ABCDEFFEDCBA9876543210";
1217 assert_eq!(hex_to_bytes_32(lowercase), hex_to_bytes_32(uppercase));
1218 }
1219
1220 #[test]
1221 fn test_hex_string_to_bytes() {
1222 let hex = "deadbeef";
1223 let bytes = hex_string_to_bytes(hex);
1224 assert_eq!(bytes, vec![0xde, 0xad, 0xbe, 0xef]);
1225 }
1226
1227 #[test]
1228 fn test_hex_string_to_bytes_long() {
1229 let hex = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff";
1231 let bytes = hex_string_to_bytes(hex);
1232 assert_eq!(bytes.len(), 32);
1233 assert_eq!(bytes[0], 0x00);
1234 assert_eq!(bytes[15], 0xff);
1235 assert_eq!(bytes[31], 0xff);
1236 }
1237
1238 #[test]
1239 fn test_roundtrip_16() {
1240 let original: [u8; 16] = [0xab; 16];
1241 let hex = bytes_to_hex_16(&original);
1242 let decoded = hex_to_bytes_16(&hex);
1243 assert_eq!(original, decoded);
1244 }
1245
1246}