1#[cfg(target_arch = "x86")]
5use core::arch::x86::*;
6#[cfg(target_arch = "x86_64")]
7use core::arch::x86_64::*;
8
9#[cfg(target_arch = "aarch64")]
10use core::arch::aarch64::*;
11
12#[cfg(feature = "alloc")]
13use alloc::{string::String, vec::Vec};
14
15use core::mem::MaybeUninit;
16
17use crate::error::Error;
18
19const TABLE_LOWER: &[u8; 16] = b"0123456789abcdef";
23const TABLE_UPPER: &[u8; 16] = b"0123456789ABCDEF";
24
25#[cfg(feature = "alloc")]
26#[inline]
27fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String {
28 let len = src.len().checked_mul(2).expect("encoded length overflow");
29 let mut buffer = Vec::with_capacity(len);
30 encode(src, buffer.spare_capacity_mut(), upper_case).expect("capacity reserved for encoding");
31 unsafe {
34 buffer.set_len(len);
35 String::from_utf8_unchecked(buffer)
36 }
37}
38
39#[cfg(feature = "alloc")]
63#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
64#[inline]
65pub fn hex_string(src: &[u8]) -> String {
66 hex_string_custom_case(src, false)
67}
68
69#[cfg(feature = "alloc")]
85#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
86#[inline]
87pub fn hex_string_upper(src: &[u8]) -> String {
88 hex_string_custom_case(src, true)
89}
90
91#[inline]
92pub(crate) fn hex_encode_custom<'a>(
93 src: &[u8],
94 dst: &'a mut [u8],
95 upper_case: bool,
96) -> Result<&'a mut str, Error> {
97 let output = unsafe { core::slice::from_raw_parts_mut(dst.as_mut_ptr().cast(), dst.len()) };
100 encode(src, output, upper_case)
101}
102
103#[inline]
106pub(crate) fn encode<'a>(
107 src: &[u8],
108 dst: &'a mut [MaybeUninit<u8>],
109 upper_case: bool,
110) -> Result<&'a mut str, Error> {
111 let len = src.len().checked_mul(2).ok_or(Error::Overflow)?;
112 if dst.len() < len {
113 return Err(Error::OutputTooSmall { required: len });
114 }
115 let dst = &mut dst[..len];
116 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
117 {
118 match crate::vectorization_support() {
119 crate::Vectorization::AVX2 | crate::Vectorization::AVX512 => {
120 unsafe { hex_encode_avx2(src, dst, upper_case) }
122 }
123 crate::Vectorization::SSE41 => {
124 unsafe { hex_encode_sse41(src, dst, upper_case) }
126 }
127 crate::Vectorization::None => hex_encode_custom_case_fallback(src, dst, upper_case),
128 }
129 }
130 #[cfg(target_arch = "aarch64")]
131 {
132 if src.len() < 8 {
133 hex_encode_pairs(src, dst, upper_case);
134 } else {
135 match crate::vectorization_support() {
136 crate::Vectorization::Neon => {
137 unsafe { hex_encode_neon(src, dst, upper_case) }
139 }
140 crate::Vectorization::None => hex_encode_custom_case_fallback(src, dst, upper_case),
141 }
142 }
143 }
144 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
145 {
146 hex_encode_custom_case_fallback(src, dst, upper_case);
147 }
148 Ok(unsafe {
152 core::str::from_utf8_unchecked_mut(core::slice::from_raw_parts_mut(
153 dst.as_mut_ptr().cast(),
154 len,
155 ))
156 })
157}
158
159#[inline]
202pub fn hex_encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
203 hex_encode_custom(src, dst, false)
204}
205
206#[inline]
225pub fn hex_encode_upper<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
226 hex_encode_custom(src, dst, true)
227}
228
229#[cfg(feature = "alloc")]
260#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
261pub fn hex_append<'a>(src: &[u8], dst: &'a mut String) -> &'a mut str {
262 hex_append_custom(src, dst, false)
263}
264
265#[cfg(feature = "alloc")]
283#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
284pub fn hex_append_upper<'a>(src: &[u8], dst: &'a mut String) -> &'a mut str {
285 hex_append_custom(src, dst, true)
286}
287
288#[cfg(feature = "alloc")]
289fn hex_append_custom<'a>(src: &[u8], dst: &'a mut String, upper: bool) -> &'a mut str {
290 let base = dst.len();
291 let additional = src.len().checked_mul(2).expect("encoded length overflow");
292 let len = base
293 .checked_add(additional)
294 .expect("encoded length overflow");
295 dst.reserve(additional);
296 unsafe {
300 let bytes = dst.as_mut_vec();
301 encode(src, bytes.spare_capacity_mut(), upper).expect("capacity reserved for encoding");
302 bytes.set_len(len);
303 core::str::from_utf8_unchecked_mut(&mut bytes[base..])
304 }
305}
306
307#[target_feature(enable = "avx2")]
309#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
310#[inline]
311pub(crate) unsafe fn hex_encode_avx2(src: &[u8], dst: &mut [MaybeUninit<u8>], upper_case: bool) {
312 if src.len() < 32 {
313 return hex_encode_sse41(src, dst, upper_case);
314 }
315 let table = if upper_case { TABLE_UPPER } else { TABLE_LOWER };
316 let table = _mm256_broadcastsi128_si256(_mm_loadu_si128(table.as_ptr().cast()));
317 let (blocks, tail) = src.as_chunks::<32>();
318 for (input, output) in blocks.iter().zip(dst.as_chunks_mut::<64>().0) {
319 encode_avx2_32(input, output, table);
320 }
321 if !tail.is_empty() {
322 if let (Some(input), Some(output)) = (src.last_chunk::<32>(), dst.last_chunk_mut::<64>()) {
323 encode_avx2_32(input, output, table);
324 }
325 }
326}
327
328#[inline]
329#[target_feature(enable = "avx2")]
330#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
331unsafe fn encode_avx2_32(src: &[u8; 32], dst: &mut [MaybeUninit<u8>; 64], table: __m256i) {
332 let bytes = _mm256_loadu_si256(src.as_ptr().cast());
333 let mask = _mm256_set1_epi8(15);
334 let high = _mm256_and_si256(_mm256_srli_epi16::<4>(bytes), mask);
335 let low = _mm256_and_si256(bytes, mask);
336 let a = _mm256_unpacklo_epi8(high, low);
337 let b = _mm256_unpackhi_epi8(high, low);
338 _mm256_storeu_si256(
339 dst.as_mut_ptr().cast(),
340 _mm256_shuffle_epi8(table, _mm256_permute2x128_si256::<0x20>(a, b)),
341 );
342 _mm256_storeu_si256(
343 dst.as_mut_ptr().add(32).cast(),
344 _mm256_shuffle_epi8(table, _mm256_permute2x128_si256::<0x31>(a, b)),
345 );
346}
347
348#[target_feature(enable = "sse4.1")]
349#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
350pub(crate) unsafe fn hex_encode_sse41(src: &[u8], dst: &mut [MaybeUninit<u8>], upper_case: bool) {
351 if src.len() < 16 {
352 return hex_encode_custom_case_fallback(src, dst, upper_case);
353 }
354 let table = if upper_case { TABLE_UPPER } else { TABLE_LOWER };
355 let table = _mm_loadu_si128(table.as_ptr().cast());
356 let (blocks, tail) = src.as_chunks::<16>();
357 for (input, output) in blocks.iter().zip(dst.as_chunks_mut::<32>().0) {
358 encode_sse41_16(input, output, table);
359 }
360 if !tail.is_empty() {
361 if let (Some(input), Some(output)) = (src.last_chunk::<16>(), dst.last_chunk_mut::<32>()) {
362 encode_sse41_16(input, output, table);
363 }
364 }
365}
366
367#[inline]
368#[target_feature(enable = "sse4.1")]
369#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
370unsafe fn encode_sse41_16(src: &[u8; 16], dst: &mut [MaybeUninit<u8>; 32], table: __m128i) {
371 let bytes = _mm_loadu_si128(src.as_ptr().cast());
372 let mask = _mm_set1_epi8(15);
373 let high = _mm_shuffle_epi8(table, _mm_and_si128(_mm_srli_epi16::<4>(bytes), mask));
374 let low = _mm_shuffle_epi8(table, _mm_and_si128(bytes, mask));
375 _mm_storeu_si128(dst.as_mut_ptr().cast(), _mm_unpacklo_epi8(high, low));
376 _mm_storeu_si128(
377 dst.as_mut_ptr().add(16).cast(),
378 _mm_unpackhi_epi8(high, low),
379 );
380}
381
382#[inline]
383#[target_feature(enable = "neon")]
384#[cfg(target_arch = "aarch64")]
385pub(crate) unsafe fn hex_encode_neon(src: &[u8], dst: &mut [MaybeUninit<u8>], upper_case: bool) {
386 if src.len() < 8 {
387 return hex_encode_custom_case_fallback(src, dst, upper_case);
388 }
389 let table = if upper_case { TABLE_UPPER } else { TABLE_LOWER };
390 let table = vld1q_u8(table.as_ptr());
391 if src.len() < 16 {
392 if let (Some(input), Some(output)) = (src.first_chunk::<8>(), dst.first_chunk_mut::<16>()) {
393 encode_neon_8(input, output, table);
394 }
395 if src.len() > 8 {
396 if let (Some(input), Some(output)) = (src.last_chunk::<8>(), dst.last_chunk_mut::<16>())
397 {
398 encode_neon_8(input, output, table);
399 }
400 }
401 return;
402 }
403 let (batches, rest) = src.as_chunks::<64>();
404 let (outputs, remaining) = dst.as_chunks_mut::<128>();
405 for (input, output) in batches.iter().zip(outputs) {
406 for (input, output) in input
407 .as_chunks::<16>()
408 .0
409 .iter()
410 .zip(output.as_chunks_mut::<32>().0)
411 {
412 encode_neon_16(input, output, table);
413 }
414 }
415 let (blocks, tail) = rest.as_chunks::<16>();
416 for (input, output) in blocks.iter().zip(remaining.as_chunks_mut::<32>().0) {
417 encode_neon_16(input, output, table);
418 }
419 if !tail.is_empty() {
420 match (src.last_chunk::<16>(), dst.last_chunk_mut::<32>()) {
423 (Some(input), Some(output)) => encode_neon_16(input, output, table),
424 _ => hex_encode_custom_case_fallback(src, dst, upper_case),
425 }
426 }
427}
428
429#[inline]
430#[target_feature(enable = "neon")]
431#[cfg(target_arch = "aarch64")]
432unsafe fn encode_neon_16(src: &[u8; 16], dst: &mut [MaybeUninit<u8>; 32], table: uint8x16_t) {
433 let bytes = vld1q_u8(src.as_ptr());
434 let high = vqtbl1q_u8(table, vshrq_n_u8::<4>(bytes));
435 let low = vqtbl1q_u8(table, vandq_u8(bytes, vdupq_n_u8(15)));
436 vst2q_u8(dst.as_mut_ptr().cast(), uint8x16x2_t(high, low));
437}
438
439const fn encode_pairs(alphabet: &[u8; 16]) -> [[u8; 2]; 256] {
440 let mut pairs = [[0; 2]; 256];
441 let mut byte = 0;
442 while byte < pairs.len() {
443 pairs[byte] = [alphabet[byte >> 4], alphabet[byte & 15]];
444 byte += 1;
445 }
446 pairs
447}
448
449static PAIRS_LOWER: [[u8; 2]; 256] = encode_pairs(TABLE_LOWER);
450static PAIRS_UPPER: [[u8; 2]; 256] = encode_pairs(TABLE_UPPER);
451
452pub(crate) fn hex_encode_custom_case_fallback(
453 src: &[u8],
454 dst: &mut [MaybeUninit<u8>],
455 upper_case: bool,
456) {
457 if cfg!(any(
460 target_feature = "neon",
461 target_feature = "sse2",
462 target_feature = "simd128"
463 )) && src.len() >= 32
464 {
465 let letter = if upper_case { b'A' - 10 } else { b'a' - 10 };
466 let ascii = |nibble| nibble + if nibble < 10 { b'0' } else { letter };
467 for (&byte, pair) in src.iter().zip(dst.chunks_exact_mut(2)) {
468 pair[0].write(ascii(byte >> 4));
469 pair[1].write(ascii(byte & 15));
470 }
471 } else {
472 hex_encode_pairs(src, dst, upper_case);
473 }
474}
475
476#[inline]
477fn hex_encode_pairs(src: &[u8], dst: &mut [MaybeUninit<u8>], upper_case: bool) {
478 let table = if upper_case {
479 &PAIRS_UPPER
480 } else {
481 &PAIRS_LOWER
482 };
483 for (&byte, pair) in src.iter().zip(dst.as_chunks_mut::<2>().0) {
484 *pair = table[byte as usize].map(MaybeUninit::new);
485 }
486}
487
488#[cfg(test)]
489pub(crate) fn hex_encode_fallback(src: &[u8], dst: &mut [u8], upper: bool) {
490 let output = unsafe { core::slice::from_raw_parts_mut(dst.as_mut_ptr().cast(), dst.len()) };
493 hex_encode_custom_case_fallback(src, output, upper);
494}
495
496#[inline]
497#[target_feature(enable = "neon")]
498#[cfg(target_arch = "aarch64")]
499unsafe fn encode_neon_8(src: &[u8; 8], dst: &mut [MaybeUninit<u8>; 16], table: uint8x16_t) {
500 let bytes = vcombine_u8(vld1_u8(src.as_ptr()), vdup_n_u8(0));
501 let high = vqtbl1q_u8(table, vshrq_n_u8::<4>(bytes));
502 let low = vqtbl1q_u8(table, vandq_u8(bytes, vdupq_n_u8(15)));
503 vst1q_u8(dst.as_mut_ptr().cast(), vzip1q_u8(high, low));
504}