Skip to main content

faster_hex/
encode.rs

1#[cfg(target_arch = "x86")]
2use core::arch::x86::*;
3#[cfg(target_arch = "x86_64")]
4use core::arch::x86_64::*;
5
6#[cfg(target_arch = "aarch64")]
7use core::arch::aarch64::*;
8
9#[cfg(feature = "alloc")]
10use alloc::{string::String, vec};
11
12#[cfg(not(feature = "alloc"))]
13use heapless::{String, Vec};
14
15use crate::error::Error;
16
17static TABLE_LOWER: &[u8] = b"0123456789abcdef";
18static TABLE_UPPER: &[u8] = b"0123456789ABCDEF";
19
20#[cfg(feature = "alloc")]
21fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String {
22    let mut buffer = vec![0; src.len() * 2];
23    if upper_case {
24        hex_encode_upper(src, &mut buffer).expect("hex_string");
25    } else {
26        hex_encode(src, &mut buffer).expect("hex_string");
27    }
28
29    if cfg!(debug_assertions) {
30        String::from_utf8(buffer).unwrap()
31    } else {
32        // Safety: We just wrote valid utf8 hex string into the dst
33        unsafe { String::from_utf8_unchecked(buffer) }
34    }
35}
36
37#[cfg(not(feature = "alloc"))]
38fn hex_string_custom_case<const N: usize>(src: &[u8], upper_case: bool) -> String<N> {
39    let mut buffer = Vec::<_, N>::new();
40    buffer
41        .resize(src.len() * 2, 0)
42        .expect("String<N> capacity too short");
43    if upper_case {
44        hex_encode_upper(src, &mut buffer).expect("hex_string");
45    } else {
46        hex_encode(src, &mut buffer).expect("hex_string");
47    }
48
49    if cfg!(debug_assertions) {
50        String::from_utf8(buffer).unwrap()
51    } else {
52        // Safety: We just wrote valid utf8 hex string into the dst
53        unsafe { String::from_utf8_unchecked(buffer) }
54    }
55}
56
57#[cfg(feature = "alloc")]
58pub fn hex_string(src: &[u8]) -> String {
59    hex_string_custom_case(src, false)
60}
61
62#[cfg(not(feature = "alloc"))]
63pub fn hex_string<const N: usize>(src: &[u8]) -> String<N> {
64    hex_string_custom_case(src, false)
65}
66
67#[cfg(feature = "alloc")]
68pub fn hex_string_upper(src: &[u8]) -> String {
69    hex_string_custom_case(src, true)
70}
71
72#[cfg(not(feature = "alloc"))]
73pub fn hex_string_upper<const N: usize>(src: &[u8]) -> String<N> {
74    hex_string_custom_case(src, true)
75}
76
77/// Hex encode src into dst, selecting uppercase digits when requested.
78/// The returned string covers only the written prefix; extra dst bytes remain unchanged.
79pub fn hex_encode_custom<'a>(
80    src: &[u8],
81    dst: &'a mut [u8],
82    upper_case: bool,
83) -> Result<&'a mut str, Error> {
84    unsafe fn mut_str(buffer: &mut [u8]) -> &mut str {
85        if cfg!(debug_assertions) {
86            core::str::from_utf8_mut(buffer).unwrap()
87        } else {
88            core::str::from_utf8_unchecked_mut(buffer)
89        }
90    }
91
92    let expect_dst_len = src
93        .len()
94        .checked_mul(2)
95        .ok_or(Error::InvalidLength(src.len()))?;
96    if dst.len() < expect_dst_len {
97        return Err(Error::InvalidLength(expect_dst_len));
98    }
99
100    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
101    {
102        match crate::vectorization_support() {
103            crate::Vectorization::AVX2 => unsafe { hex_encode_avx2(src, dst, upper_case) },
104            crate::Vectorization::SSE41 => unsafe { hex_encode_sse41(src, dst, upper_case) },
105            crate::Vectorization::None => hex_encode_custom_case_fallback(src, dst, upper_case),
106        }
107    }
108    #[cfg(target_arch = "aarch64")]
109    {
110        match crate::vectorization_support() {
111            crate::Vectorization::Neon => unsafe { hex_encode_neon(src, dst, upper_case) },
112            crate::Vectorization::None => hex_encode_custom_case_fallback(src, dst, upper_case),
113        }
114    }
115    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
116    {
117        hex_encode_custom_case_fallback(src, dst, upper_case);
118    }
119    // Safety: Every backend writes ASCII into exactly this prefix.
120    Ok(unsafe { mut_str(&mut dst[..expect_dst_len]) })
121}
122
123/// Hex encode src into dst.
124/// The length of dst must be at least src.len() * 2.
125/// The returned string covers only the written prefix; extra dst bytes remain unchanged.
126pub fn hex_encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
127    hex_encode_custom(src, dst, false)
128}
129
130/// Hex encode src into dst using uppercase digits.
131/// The length of dst must be at least src.len() * 2.
132/// The returned string covers only the written prefix; extra dst bytes remain unchanged.
133pub fn hex_encode_upper<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
134    hex_encode_custom(src, dst, true)
135}
136
137#[deprecated(since = "0.3.0", note = "please use `hex_encode` instead")]
138pub fn hex_to(src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
139    hex_encode(src, dst).map(|_| ())
140}
141
142#[target_feature(enable = "avx2")]
143#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
144unsafe fn hex_encode_avx2(mut src: &[u8], dst: &mut [u8], upper_case: bool) {
145    let ascii_zero = _mm256_set1_epi8(b'0' as i8);
146    let nines = _mm256_set1_epi8(9);
147    let ascii_a = if upper_case {
148        _mm256_set1_epi8((b'A' - 9 - 1) as i8)
149    } else {
150        _mm256_set1_epi8((b'a' - 9 - 1) as i8)
151    };
152    let and4bits = _mm256_set1_epi8(0xf);
153
154    let mut i = 0_isize;
155    while src.len() >= 32 {
156        // https://stackoverflow.com/questions/47425851/whats-the-difference-between-mm256-lddqu-si256-and-mm256-loadu-si256
157        let invec = _mm256_loadu_si256(src.as_ptr() as *const _);
158
159        let masked1 = _mm256_and_si256(invec, and4bits);
160        let masked2 = _mm256_and_si256(_mm256_srli_epi64(invec, 4), and4bits);
161
162        // return 0xff corresponding to the elements > 9, or 0x00 otherwise
163        let cmpmask1 = _mm256_cmpgt_epi8(masked1, nines);
164        let cmpmask2 = _mm256_cmpgt_epi8(masked2, nines);
165
166        // add '0' or the offset depending on the masks
167        let masked1 = _mm256_add_epi8(masked1, _mm256_blendv_epi8(ascii_zero, ascii_a, cmpmask1));
168        let masked2 = _mm256_add_epi8(masked2, _mm256_blendv_epi8(ascii_zero, ascii_a, cmpmask2));
169
170        // interleave masked1 and masked2 bytes
171        let res1 = _mm256_unpacklo_epi8(masked2, masked1);
172        let res2 = _mm256_unpackhi_epi8(masked2, masked1);
173
174        // Store everything into the right destination now
175        let base = dst.as_mut_ptr().offset(i * 2);
176        let base1 = base.offset(0) as *mut _;
177        let base2 = base.offset(16) as *mut _;
178        let base3 = base.offset(32) as *mut _;
179        let base4 = base.offset(48) as *mut _;
180        _mm256_storeu2_m128i(base3, base1, res1);
181        _mm256_storeu2_m128i(base4, base2, res2);
182        src = &src[32..];
183        i += 32;
184    }
185
186    let i = i as usize;
187    hex_encode_sse41(src, &mut dst[i * 2..], upper_case);
188}
189
190// copied from https://github.com/Matherunner/bin2hex-sse/blob/master/base16_sse4.cpp
191#[target_feature(enable = "sse4.1")]
192#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
193unsafe fn hex_encode_sse41(mut src: &[u8], dst: &mut [u8], upper_case: bool) {
194    let ascii_zero = _mm_set1_epi8(b'0' as i8);
195    let nines = _mm_set1_epi8(9);
196    let ascii_a = if upper_case {
197        _mm_set1_epi8((b'A' - 9 - 1) as i8)
198    } else {
199        _mm_set1_epi8((b'a' - 9 - 1) as i8)
200    };
201    let and4bits = _mm_set1_epi8(0xf);
202
203    let mut i = 0_isize;
204    while src.len() >= 16 {
205        let invec = _mm_loadu_si128(src.as_ptr() as *const _);
206
207        let masked1 = _mm_and_si128(invec, and4bits);
208        let masked2 = _mm_and_si128(_mm_srli_epi64(invec, 4), and4bits);
209
210        // return 0xff corresponding to the elements > 9, or 0x00 otherwise
211        let cmpmask1 = _mm_cmpgt_epi8(masked1, nines);
212        let cmpmask2 = _mm_cmpgt_epi8(masked2, nines);
213
214        // add '0' or the offset depending on the masks
215        let masked1 = _mm_add_epi8(masked1, _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask1));
216        let masked2 = _mm_add_epi8(masked2, _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask2));
217
218        // interleave masked1 and masked2 bytes
219        let res1 = _mm_unpacklo_epi8(masked2, masked1);
220        let res2 = _mm_unpackhi_epi8(masked2, masked1);
221
222        _mm_storeu_si128(dst.as_mut_ptr().offset(i * 2) as *mut _, res1);
223        _mm_storeu_si128(dst.as_mut_ptr().offset(i * 2 + 16) as *mut _, res2);
224        src = &src[16..];
225        i += 16;
226    }
227
228    let i = i as usize;
229    hex_encode_custom_case_fallback(src, &mut dst[i * 2..], upper_case);
230}
231
232#[target_feature(enable = "neon")]
233#[cfg(target_arch = "aarch64")]
234unsafe fn hex_encode_neon(mut src: &[u8], dst: &mut [u8], upper_case: bool) {
235    let ascii_zero = vdupq_n_u8(b'0');
236    let nines = vdupq_n_u8(9);
237    let ascii_a = if upper_case {
238        vdupq_n_u8(b'A' - 9 - 1)
239    } else {
240        vdupq_n_u8(b'a' - 9 - 1)
241    };
242    let and4bits = vdupq_n_u8(0xf);
243
244    let mut i = 0_isize;
245
246    while src.len() >= 16 {
247        let invec = vld1q_u8(src.as_ptr() as *const _);
248
249        let masked1 = vandq_u8(invec, and4bits);
250        let masked2 = vandq_u8(vshrq_n_u8::<4>(invec), and4bits);
251
252        // return 0xff corresponding to the elements > 9, or 0x00 otherwise
253        let cmpmask1 = vcgtq_u8(masked1, nines);
254        let cmpmask2 = vcgtq_u8(masked2, nines);
255
256        // add '0' or the offset depending on the masks
257        let masked1 = vaddq_u8(masked1, vbslq_u8(cmpmask1, ascii_a, ascii_zero));
258        let masked2 = vaddq_u8(masked2, vbslq_u8(cmpmask2, ascii_a, ascii_zero));
259
260        // interleave masked1 and masked2 bytes
261        let res1 = vzip1q_u8(masked2, masked1);
262        let res2 = vzip2q_u8(masked2, masked1);
263
264        vst1q_u8(dst.as_mut_ptr().offset(i * 2) as *mut _, res1);
265        vst1q_u8(dst.as_mut_ptr().offset(i * 2 + 16) as *mut _, res2);
266
267        src = &src[16..];
268        i += 16;
269    }
270
271    let i = i as usize;
272    hex_encode_custom_case_fallback(src, &mut dst[i * 2..], upper_case);
273}
274
275#[inline]
276fn hex_lower(byte: u8) -> u8 {
277    TABLE_LOWER[byte as usize]
278}
279
280#[inline]
281fn hex_upper(byte: u8) -> u8 {
282    TABLE_UPPER[byte as usize]
283}
284
285fn hex_encode_custom_case_fallback(src: &[u8], dst: &mut [u8], upper_case: bool) {
286    if upper_case {
287        for (byte, slots) in src.iter().zip(dst.chunks_exact_mut(2)) {
288            slots[0] = hex_upper((*byte >> 4) & 0xf);
289            slots[1] = hex_upper(*byte & 0xf);
290        }
291    } else {
292        for (byte, slots) in src.iter().zip(dst.chunks_exact_mut(2)) {
293            slots[0] = hex_lower((*byte >> 4) & 0xf);
294            slots[1] = hex_lower(*byte & 0xf);
295        }
296    }
297}
298
299pub fn hex_encode_fallback(src: &[u8], dst: &mut [u8]) {
300    hex_encode_custom_case_fallback(src, dst, false)
301}
302
303pub fn hex_encode_upper_fallback(src: &[u8], dst: &mut [u8]) {
304    hex_encode_custom_case_fallback(src, dst, true)
305}
306
307#[cfg(test)]
308mod tests {
309    use crate::encode::{hex_encode, hex_encode_custom_case_fallback, hex_encode_upper};
310
311    use crate::hex_encode_fallback;
312    use core::str;
313    use proptest::proptest;
314
315    fn _test_encode_fallback(s: &String, upper_case: bool) {
316        let mut buffer = vec![0; s.as_bytes().len() * 2];
317        hex_encode_custom_case_fallback(s.as_bytes(), &mut buffer, upper_case);
318
319        let encode = unsafe { str::from_utf8_unchecked(&buffer[..s.as_bytes().len() * 2]) };
320        if upper_case {
321            assert_eq!(encode, hex::encode_upper(s));
322        } else {
323            assert_eq!(encode, hex::encode(s));
324        }
325    }
326
327    proptest! {
328        #[test]
329        fn test_encode_fallback(ref s in ".*") {
330            _test_encode_fallback(s, true);
331            _test_encode_fallback(s, false);
332        }
333    }
334
335    #[test]
336    fn test_encode_oversized_dst_returns_written_prefix() {
337        let mut lower = [0xff; 4];
338        assert_eq!(hex_encode(&[0xab], &mut lower).unwrap(), "ab");
339        assert_eq!(lower, [b'a', b'b', 0xff, 0xff]);
340
341        let mut upper = [0xff; 4];
342        assert_eq!(hex_encode_upper(&[0xab], &mut upper).unwrap(), "AB");
343        assert_eq!(upper, [b'A', b'B', 0xff, 0xff]);
344
345        let mut empty = [0xff; 2];
346        assert_eq!(hex_encode(b"", &mut empty).unwrap(), "");
347        assert_eq!(empty, [0xff; 2]);
348    }
349
350    #[test]
351    fn test_encode_zero_length_src_should_be_ok() {
352        let src = b"";
353        let mut dst = [0u8; 10];
354        assert!(hex_encode(src, &mut dst).is_ok());
355
356        // this function have no return value, so we just execute it and expect no panic
357        hex_encode_fallback(src, &mut dst);
358    }
359}