Coverage Report

Created: 2026-03-31 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/faster-hex-0.10.0/src/encode.rs
Line
Count
Source
1
#[cfg(target_arch = "x86")]
2
use core::arch::x86::*;
3
#[cfg(target_arch = "x86_64")]
4
use core::arch::x86_64::*;
5
6
#[cfg(feature = "alloc")]
7
use alloc::{string::String, vec};
8
9
#[cfg(not(feature = "alloc"))]
10
use heapless::{String, Vec};
11
12
use crate::error::Error;
13
14
static TABLE_LOWER: &[u8] = b"0123456789abcdef";
15
static TABLE_UPPER: &[u8] = b"0123456789ABCDEF";
16
17
#[cfg(feature = "alloc")]
18
0
fn hex_string_custom_case(src: &[u8], upper_case: bool) -> String {
19
0
    let mut buffer = vec![0; src.len() * 2];
20
0
    if upper_case {
21
0
        hex_encode_upper(src, &mut buffer).expect("hex_string");
22
0
    } else {
23
0
        hex_encode(src, &mut buffer).expect("hex_string");
24
0
    }
25
26
0
    if cfg!(debug_assertions) {
27
0
        String::from_utf8(buffer).unwrap()
28
    } else {
29
        // Saftey: We just wrote valid utf8 hex string into the dst
30
0
        unsafe { String::from_utf8_unchecked(buffer) }
31
    }
32
0
}
33
34
#[cfg(not(feature = "alloc"))]
35
fn hex_string_custom_case<const N: usize>(src: &[u8], upper_case: bool) -> String<N> {
36
    let mut buffer = Vec::<_, N>::new();
37
    buffer
38
        .resize(src.len() * 2, 0)
39
        .expect("String<N> capacity too short");
40
    if upper_case {
41
        hex_encode_upper(src, &mut buffer).expect("hex_string");
42
    } else {
43
        hex_encode(src, &mut buffer).expect("hex_string");
44
    }
45
46
    if cfg!(debug_assertions) {
47
        String::from_utf8(buffer).unwrap()
48
    } else {
49
        // Saftey: We just wrote valid utf8 hex string into the dst
50
        unsafe { String::from_utf8_unchecked(buffer) }
51
    }
52
}
53
54
#[cfg(feature = "alloc")]
55
0
pub fn hex_string(src: &[u8]) -> String {
56
0
    hex_string_custom_case(src, false)
57
0
}
58
59
#[cfg(not(feature = "alloc"))]
60
pub fn hex_string<const N: usize>(src: &[u8]) -> String<N> {
61
    hex_string_custom_case(src, false)
62
}
63
64
#[cfg(feature = "alloc")]
65
0
pub fn hex_string_upper(src: &[u8]) -> String {
66
0
    hex_string_custom_case(src, true)
67
0
}
68
69
#[cfg(not(feature = "alloc"))]
70
pub fn hex_string_upper<const N: usize>(src: &[u8]) -> String<N> {
71
    hex_string_custom_case(src, true)
72
}
73
74
2.00k
pub fn hex_encode_custom<'a>(
75
2.00k
    src: &[u8],
76
2.00k
    dst: &'a mut [u8],
77
2.00k
    upper_case: bool,
78
2.00k
) -> Result<&'a mut str, Error> {
79
2.00k
    unsafe fn mut_str(buffer: &mut [u8]) -> &mut str {
80
2.00k
        if cfg!(debug_assertions) {
81
2.00k
            core::str::from_utf8_mut(buffer).unwrap()
82
        } else {
83
0
            core::str::from_utf8_unchecked_mut(buffer)
84
        }
85
2.00k
    }
86
87
2.00k
    let expect_dst_len = src
88
2.00k
        .len()
89
2.00k
        .checked_mul(2)
90
2.00k
        .ok_or(Error::InvalidLength(src.len()))?;
91
2.00k
    if dst.len() < expect_dst_len {
92
0
        return Err(Error::InvalidLength(expect_dst_len));
93
2.00k
    }
94
95
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
96
    {
97
2.00k
        match crate::vectorization_support() {
98
2.00k
            crate::Vectorization::AVX2 => unsafe { hex_encode_avx2(src, dst, upper_case) },
99
0
            crate::Vectorization::SSE41 => unsafe { hex_encode_sse41(src, dst, upper_case) },
100
0
            crate::Vectorization::None => hex_encode_custom_case_fallback(src, dst, upper_case),
101
        }
102
        // Safety: We just wrote valid utf8 hex string into the dst
103
2.00k
        return Ok(unsafe { mut_str(dst) });
104
    }
105
    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
106
    {
107
        hex_encode_custom_case_fallback(src, dst, upper_case);
108
        // Saftey: We just wrote valid utf8 hex string into the dst
109
        Ok(unsafe { mut_str(dst) })
110
    }
111
2.00k
}
112
113
/// Hex encode src into dst.
114
/// The length of dst must be at least src.len() * 2.
115
2.00k
pub fn hex_encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
116
2.00k
    hex_encode_custom(src, dst, false)
117
2.00k
}
118
119
0
pub fn hex_encode_upper<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut str, Error> {
120
0
    hex_encode_custom(src, dst, true)
121
0
}
122
123
#[deprecated(since = "0.3.0", note = "please use `hex_encode` instead")]
124
0
pub fn hex_to(src: &[u8], dst: &mut [u8]) -> Result<(), Error> {
125
0
    hex_encode(src, dst).map(|_| ())
126
0
}
127
128
#[target_feature(enable = "avx2")]
129
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
130
2.00k
unsafe fn hex_encode_avx2(mut src: &[u8], dst: &mut [u8], upper_case: bool) {
131
2.00k
    let ascii_zero = _mm256_set1_epi8(b'0' as i8);
132
2.00k
    let nines = _mm256_set1_epi8(9);
133
2.00k
    let ascii_a = if upper_case {
134
0
        _mm256_set1_epi8((b'A' - 9 - 1) as i8)
135
    } else {
136
2.00k
        _mm256_set1_epi8((b'a' - 9 - 1) as i8)
137
    };
138
2.00k
    let and4bits = _mm256_set1_epi8(0xf);
139
140
2.00k
    let mut i = 0_isize;
141
2.00k
    while src.len() >= 32 {
142
0
        // https://stackoverflow.com/questions/47425851/whats-the-difference-between-mm256-lddqu-si256-and-mm256-loadu-si256
143
0
        let invec = _mm256_loadu_si256(src.as_ptr() as *const _);
144
0
145
0
        let masked1 = _mm256_and_si256(invec, and4bits);
146
0
        let masked2 = _mm256_and_si256(_mm256_srli_epi64(invec, 4), and4bits);
147
0
148
0
        // return 0xff corresponding to the elements > 9, or 0x00 otherwise
149
0
        let cmpmask1 = _mm256_cmpgt_epi8(masked1, nines);
150
0
        let cmpmask2 = _mm256_cmpgt_epi8(masked2, nines);
151
0
152
0
        // add '0' or the offset depending on the masks
153
0
        let masked1 = _mm256_add_epi8(masked1, _mm256_blendv_epi8(ascii_zero, ascii_a, cmpmask1));
154
0
        let masked2 = _mm256_add_epi8(masked2, _mm256_blendv_epi8(ascii_zero, ascii_a, cmpmask2));
155
0
156
0
        // interleave masked1 and masked2 bytes
157
0
        let res1 = _mm256_unpacklo_epi8(masked2, masked1);
158
0
        let res2 = _mm256_unpackhi_epi8(masked2, masked1);
159
0
160
0
        // Store everything into the right destination now
161
0
        let base = dst.as_mut_ptr().offset(i * 2);
162
0
        let base1 = base.offset(0) as *mut _;
163
0
        let base2 = base.offset(16) as *mut _;
164
0
        let base3 = base.offset(32) as *mut _;
165
0
        let base4 = base.offset(48) as *mut _;
166
0
        _mm256_storeu2_m128i(base3, base1, res1);
167
0
        _mm256_storeu2_m128i(base4, base2, res2);
168
0
        src = &src[32..];
169
0
        i += 32;
170
0
    }
171
172
2.00k
    let i = i as usize;
173
2.00k
    hex_encode_sse41(src, &mut dst[i * 2..], upper_case);
174
2.00k
}
175
176
// copied from https://github.com/Matherunner/bin2hex-sse/blob/master/base16_sse4.cpp
177
#[target_feature(enable = "sse4.1")]
178
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
179
2.00k
unsafe fn hex_encode_sse41(mut src: &[u8], dst: &mut [u8], upper_case: bool) {
180
2.00k
    let ascii_zero = _mm_set1_epi8(b'0' as i8);
181
2.00k
    let nines = _mm_set1_epi8(9);
182
2.00k
    let ascii_a = if upper_case {
183
0
        _mm_set1_epi8((b'A' - 9 - 1) as i8)
184
    } else {
185
2.00k
        _mm_set1_epi8((b'a' - 9 - 1) as i8)
186
    };
187
2.00k
    let and4bits = _mm_set1_epi8(0xf);
188
189
2.00k
    let mut i = 0_isize;
190
4.00k
    while src.len() >= 16 {
191
2.00k
        let invec = _mm_loadu_si128(src.as_ptr() as *const _);
192
2.00k
193
2.00k
        let masked1 = _mm_and_si128(invec, and4bits);
194
2.00k
        let masked2 = _mm_and_si128(_mm_srli_epi64(invec, 4), and4bits);
195
2.00k
196
2.00k
        // return 0xff corresponding to the elements > 9, or 0x00 otherwise
197
2.00k
        let cmpmask1 = _mm_cmpgt_epi8(masked1, nines);
198
2.00k
        let cmpmask2 = _mm_cmpgt_epi8(masked2, nines);
199
2.00k
200
2.00k
        // add '0' or the offset depending on the masks
201
2.00k
        let masked1 = _mm_add_epi8(masked1, _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask1));
202
2.00k
        let masked2 = _mm_add_epi8(masked2, _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask2));
203
2.00k
204
2.00k
        // interleave masked1 and masked2 bytes
205
2.00k
        let res1 = _mm_unpacklo_epi8(masked2, masked1);
206
2.00k
        let res2 = _mm_unpackhi_epi8(masked2, masked1);
207
2.00k
208
2.00k
        _mm_storeu_si128(dst.as_mut_ptr().offset(i * 2) as *mut _, res1);
209
2.00k
        _mm_storeu_si128(dst.as_mut_ptr().offset(i * 2 + 16) as *mut _, res2);
210
2.00k
        src = &src[16..];
211
2.00k
        i += 16;
212
2.00k
    }
213
214
2.00k
    let i = i as usize;
215
2.00k
    hex_encode_custom_case_fallback(src, &mut dst[i * 2..], upper_case);
216
2.00k
}
217
218
#[inline]
219
16.0k
fn hex_lower(byte: u8) -> u8 {
220
16.0k
    TABLE_LOWER[byte as usize]
221
16.0k
}
222
223
#[inline]
224
0
fn hex_upper(byte: u8) -> u8 {
225
0
    TABLE_UPPER[byte as usize]
226
0
}
227
228
2.00k
fn hex_encode_custom_case_fallback(src: &[u8], dst: &mut [u8], upper_case: bool) {
229
2.00k
    if upper_case {
230
0
        for (byte, slots) in src.iter().zip(dst.chunks_exact_mut(2)) {
231
0
            slots[0] = hex_upper((*byte >> 4) & 0xf);
232
0
            slots[1] = hex_upper(*byte & 0xf);
233
0
        }
234
    } else {
235
8.00k
        for (byte, slots) in src.iter().zip(dst.chunks_exact_mut(2)) {
236
8.00k
            slots[0] = hex_lower((*byte >> 4) & 0xf);
237
8.00k
            slots[1] = hex_lower(*byte & 0xf);
238
8.00k
        }
239
    }
240
2.00k
}
241
242
0
pub fn hex_encode_fallback(src: &[u8], dst: &mut [u8]) {
243
0
    hex_encode_custom_case_fallback(src, dst, false)
244
0
}
245
246
0
pub fn hex_encode_upper_fallback(src: &[u8], dst: &mut [u8]) {
247
0
    hex_encode_custom_case_fallback(src, dst, true)
248
0
}
249
250
#[cfg(test)]
251
mod tests {
252
    use crate::encode::{hex_encode, hex_encode_custom_case_fallback};
253
254
    use crate::hex_encode_fallback;
255
    use core::str;
256
    use proptest::proptest;
257
258
    fn _test_encode_fallback(s: &String, upper_case: bool) {
259
        let mut buffer = vec![0; s.as_bytes().len() * 2];
260
        hex_encode_custom_case_fallback(s.as_bytes(), &mut buffer, upper_case);
261
262
        let encode = unsafe { str::from_utf8_unchecked(&buffer[..s.as_bytes().len() * 2]) };
263
        if upper_case {
264
            assert_eq!(encode, hex::encode_upper(s));
265
        } else {
266
            assert_eq!(encode, hex::encode(s));
267
        }
268
    }
269
270
    proptest! {
271
        #[test]
272
        fn test_encode_fallback(ref s in ".*") {
273
            _test_encode_fallback(s, true);
274
            _test_encode_fallback(s, false);
275
        }
276
    }
277
278
    #[test]
279
    fn test_encode_zero_length_src_should_be_ok() {
280
        let src = b"";
281
        let mut dst = [0u8; 10];
282
        assert!(hex_encode(src, &mut dst).is_ok());
283
284
        // this function have no return value, so we just execute it and expect no panic
285
        hex_encode_fallback(src, &mut dst);
286
    }
287
}