1use arrow::datatypes::ArrowNativeType;
27
28use crate::Result;
29use crate::error::_internal_err;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum HexCase {
34 Lower,
36 Upper,
38}
39
40const LOWER_DIGITS: &[u8; 16] = b"0123456789abcdef";
41const UPPER_DIGITS: &[u8; 16] = b"0123456789ABCDEF";
42
43const LOOKUP_LOWER: [[u8; 2]; 256] = build_lookup(LOWER_DIGITS);
46const LOOKUP_UPPER: [[u8; 2]; 256] = build_lookup(UPPER_DIGITS);
47
48const fn build_lookup(digits: &[u8; 16]) -> [[u8; 2]; 256] {
49 let mut table = [[0u8; 2]; 256];
50 let mut i = 0;
51 while i < 256 {
52 table[i][0] = digits[i >> 4];
53 table[i][1] = digits[i & 0xF];
54 i += 1;
55 }
56 table
57}
58
59impl HexCase {
60 #[inline]
61 const fn lookup(self) -> &'static [[u8; 2]; 256] {
62 match self {
63 HexCase::Lower => &LOOKUP_LOWER,
64 HexCase::Upper => &LOOKUP_UPPER,
65 }
66 }
67
68 #[inline]
69 const fn digits(self) -> &'static [u8; 16] {
70 match self {
71 HexCase::Lower => LOWER_DIGITS,
72 HexCase::Upper => UPPER_DIGITS,
73 }
74 }
75}
76
77pub trait ToHex: ArrowNativeType {
79 fn write_hex(self, case: HexCase, buf: &mut [u8; 16]) -> &[u8];
82}
83
84macro_rules! impl_to_hex_signed {
85 ($ty:ty) => {
86 impl ToHex for $ty {
87 #[inline(always)]
88 fn write_hex(self, case: HexCase, buf: &mut [u8; 16]) -> &[u8] {
89 encode_u64(self as i64 as u64, case, buf)
90 }
91 }
92 };
93}
94
95macro_rules! impl_to_hex_unsigned {
96 ($ty:ty) => {
97 impl ToHex for $ty {
98 #[inline(always)]
99 fn write_hex(self, case: HexCase, buf: &mut [u8; 16]) -> &[u8] {
100 encode_u64(self as u64, case, buf)
101 }
102 }
103 };
104}
105
106impl_to_hex_signed!(i8);
107impl_to_hex_signed!(i16);
108impl_to_hex_signed!(i32);
109impl_to_hex_signed!(i64);
110impl_to_hex_unsigned!(u8);
111impl_to_hex_unsigned!(u16);
112impl_to_hex_unsigned!(u32);
113impl_to_hex_unsigned!(u64);
114
115#[inline(always)]
120pub fn encode_bytes_into(bytes: &[u8], case: HexCase, out: &mut Vec<u8>) {
121 let lookup = case.lookup();
122 for &byte in bytes {
123 out.extend_from_slice(&lookup[byte as usize]);
124 }
125}
126
127#[inline(always)]
147pub fn encode_bytes_to_slice(bytes: &[u8], case: HexCase, out: &mut [u8]) -> Result<()> {
148 let expected = bytes.len() * 2;
149 if out.len() != expected {
150 return _internal_err!(
151 "hex output buffer is {} bytes, expected {expected}",
152 out.len()
153 );
154 }
155 let lookup = case.lookup();
156 for (&b, chunk) in bytes.iter().zip(out.chunks_exact_mut(2)) {
157 chunk.copy_from_slice(&lookup[b as usize]);
158 }
159 Ok(())
160}
161
162#[inline]
173pub fn encode_bytes(bytes: &[u8], case: HexCase) -> String {
174 let mut out = Vec::with_capacity(bytes.len() * 2);
175 encode_bytes_into(bytes, case, &mut out);
176 unsafe { String::from_utf8_unchecked(out) }
178}
179
180#[inline(always)]
202pub fn encode_u64(v: u64, case: HexCase, buf: &mut [u8; 16]) -> &[u8] {
203 let start = write_digits(v, case, buf);
204 &buf[start..]
205}
206
207#[inline(always)]
213fn write_digits(v: u64, case: HexCase, buf: &mut [u8; 16]) -> usize {
214 if v == 0 {
215 buf[15] = b'0';
216 return 15;
217 }
218
219 let lookup = case.lookup();
221 let mut pos = 16;
222 let mut rest = v;
223 while rest >= 0x10 {
224 pos -= 2;
225 let pair = lookup[(rest & 0xFF) as usize];
226 buf[pos] = pair[0];
227 buf[pos + 1] = pair[1];
228 rest >>= 8;
229 }
230 if rest > 0 {
231 pos -= 1;
233 buf[pos] = case.digits()[rest as usize];
234 }
235
236 pos
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 fn hex_u64(v: u64, case: HexCase) -> String {
244 let mut buf = [0u8; 16];
245 String::from_utf8(encode_u64(v, case, &mut buf).to_vec()).unwrap()
246 }
247
248 #[test]
249 fn encode_u64_zero() {
250 assert_eq!(hex_u64(0, HexCase::Lower), "0");
251 assert_eq!(hex_u64(0, HexCase::Upper), "0");
252 }
253
254 #[test]
255 fn encode_u64_single_nibble() {
256 for v in 1..=0xFu64 {
257 assert_eq!(hex_u64(v, HexCase::Lower), format!("{v:x}"));
258 assert_eq!(hex_u64(v, HexCase::Upper), format!("{v:X}"));
259 }
260 }
261
262 #[test]
263 fn encode_u64_digit_count_boundaries() {
264 for v in [
267 0x10u64,
268 0xFF,
269 0x100,
270 0xFFF,
271 0x1000,
272 0xFFFFF,
273 0xFFFF_FFFF,
274 0x1_0000_0000,
275 ] {
276 assert_eq!(hex_u64(v, HexCase::Lower), format!("{v:x}"));
277 assert_eq!(hex_u64(v, HexCase::Upper), format!("{v:X}"));
278 }
279 }
280
281 #[test]
282 fn encode_u64_max() {
283 assert_eq!(hex_u64(u64::MAX, HexCase::Lower), "ffffffffffffffff");
284 assert_eq!(hex_u64(u64::MAX, HexCase::Upper), "FFFFFFFFFFFFFFFF");
285 }
286
287 #[test]
288 fn encode_u64_signed_is_twos_complement() {
289 assert_eq!(hex_u64(-1i64 as u64, HexCase::Lower), "ffffffffffffffff");
292 assert_eq!(hex_u64(i64::MIN as u64, HexCase::Upper), "8000000000000000");
293 }
294
295 #[test]
296 fn encode_bytes_empty() {
297 assert_eq!(encode_bytes(&[], HexCase::Lower), "");
298 assert_eq!(encode_bytes(&[], HexCase::Upper), "");
299 }
300
301 #[test]
302 fn encode_bytes_examples() {
303 assert_eq!(encode_bytes(&[0x00], HexCase::Lower), "00");
304 assert_eq!(encode_bytes(&[0xAB], HexCase::Lower), "ab");
305 assert_eq!(encode_bytes(&[0xAB], HexCase::Upper), "AB");
306 assert_eq!(
307 encode_bytes(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower),
308 "deadbeef"
309 );
310 assert_eq!(
311 encode_bytes(&[0xde, 0xad, 0xbe, 0xef], HexCase::Upper),
312 "DEADBEEF"
313 );
314 }
315
316 #[test]
317 fn encode_bytes_covers_every_byte_value() {
318 let bytes: Vec<u8> = (0..=255u8).collect();
319
320 let expected: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
321 assert_eq!(encode_bytes(&bytes, HexCase::Lower), expected);
322
323 let expected: String = bytes.iter().map(|b| format!("{b:02X}")).collect();
324 assert_eq!(encode_bytes(&bytes, HexCase::Upper), expected);
325 }
326
327 #[test]
328 fn encode_bytes_into_appends_without_clearing() {
329 let mut out = b"prefix-".to_vec();
330 encode_bytes_into(&[0x01, 0x02], HexCase::Lower, &mut out);
331 assert_eq!(out, b"prefix-0102");
332 }
333
334 #[test]
335 fn encode_u64_reused_buffer_leaks_no_stale_digits() {
336 let mut buf = [0u8; 16];
337 assert_eq!(
338 encode_u64(u64::MAX, HexCase::Lower, &mut buf),
339 b"ffffffffffffffff"
340 );
341 assert_eq!(encode_u64(0, HexCase::Lower, &mut buf), b"0");
342 assert_eq!(encode_u64(0xAB, HexCase::Lower, &mut buf), b"ab");
343 }
344
345 #[test]
346 fn encode_bytes_to_slice_empty() -> Result<()> {
347 let mut out: [u8; 0] = [];
348 encode_bytes_to_slice(&[], HexCase::Lower, &mut out)?;
349 assert_eq!(out, [] as [u8; 0]);
350 Ok(())
351 }
352
353 #[test]
354 fn encode_bytes_to_slice_examples() -> Result<()> {
355 let mut out = [0u8; 8];
356 encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut out)?;
357 assert_eq!(&out, b"deadbeef");
358
359 let mut out = [0u8; 8];
360 encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Upper, &mut out)?;
361 assert_eq!(&out, b"DEADBEEF");
362 Ok(())
363 }
364
365 #[test]
366 fn encode_bytes_to_slice_agrees_with_encode_bytes() -> Result<()> {
367 let bytes: Vec<u8> = (0..=255u8).collect();
368 for case in [HexCase::Lower, HexCase::Upper] {
369 let mut out = vec![0u8; bytes.len() * 2];
370 encode_bytes_to_slice(&bytes, case, &mut out)?;
371 assert_eq!(String::from_utf8(out).unwrap(), encode_bytes(&bytes, case));
372 }
373 Ok(())
374 }
375
376 #[test]
377 fn encode_bytes_to_slice_rejects_wrong_length() {
378 let mut short = [0u8; 6];
381 let err =
382 encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut short)
383 .unwrap_err();
384 assert!(
385 err.message()
386 .contains("hex output buffer is 6 bytes, expected 8"),
387 "unexpected message: {err}"
388 );
389
390 let mut long = [0u8; 10];
392 assert!(
393 encode_bytes_to_slice(&[0xde, 0xad, 0xbe, 0xef], HexCase::Lower, &mut long)
394 .is_err()
395 );
396 }
397}