generic_array/
hex.rs

1//! Generic array are commonly used as a return value for hash digests, so
2//! it's a good idea to allow to hexlify them easily. This module implements
3//! `std::fmt::LowerHex` and `std::fmt::UpperHex` traits.
4//!
5//! Example:
6//!
7//! ```rust
8//! use generic_array::arr;
9//! use generic_array::typenum;
10//!
11//! let array = arr![10u8, 20, 30];
12//! assert_eq!(format!("{:x}", array), "0a141e");
13//! ```
14
15use core::{cmp::min, fmt, ops::Add, str};
16
17use typenum::*;
18
19use crate::{ArrayLength, GenericArray};
20
21#[inline(always)]
22fn hex_encode_fallback<const UPPER: bool>(src: &[u8], dst: &mut [u8]) {
23    if dst.len() < src.len() * 2 {
24        unsafe { core::hint::unreachable_unchecked() };
25    }
26
27    let alphabet = match UPPER {
28        true => b"0123456789ABCDEF",
29        false => b"0123456789abcdef",
30    };
31
32    dst.chunks_exact_mut(2).zip(src).for_each(|(s, c)| {
33        s[0] = alphabet[(c >> 4) as usize];
34        s[1] = alphabet[(c & 0xF) as usize];
35    });
36}
37
38#[inline]
39fn hex_encode<const UPPER: bool>(src: &[u8], dst: &mut [u8]) {
40    debug_assert!(dst.len() >= (src.len() * 2));
41
42    #[cfg(any(miri, not(feature = "faster-hex")))]
43    hex_encode_fallback::<UPPER>(src, dst);
44
45    // the `unwrap_unchecked` is to avoid the length checks
46    #[cfg(all(feature = "faster-hex", not(miri)))]
47    match UPPER {
48        true => unsafe { faster_hex::hex_encode_upper(src, dst).unwrap_unchecked() },
49        false => unsafe { faster_hex::hex_encode(src, dst).unwrap_unchecked() },
50    };
51}
52
53fn generic_hex<N, const UPPER: bool>(
54    arr: &GenericArray<u8, N>,
55    f: &mut fmt::Formatter<'_>,
56) -> fmt::Result
57where
58    N: ArrayLength + Add<N>,
59    Sum<N, N>: ArrayLength,
60{
61    let max_digits = N::USIZE * 2;
62    let max_digits = match f.precision() {
63        Some(precision) if precision < max_digits => precision,
64        _ => max_digits,
65    };
66
67    // ceil(max_digits / 2)
68    let max_bytes = (max_digits >> 1) + (max_digits & 1);
69
70    let input = {
71        // LLVM can't seem to automatically prove this
72        if max_bytes > N::USIZE {
73            unsafe { core::hint::unreachable_unchecked() };
74        }
75
76        &arr[..max_bytes]
77    };
78
79    if N::USIZE <= 1024 {
80        // For small arrays use a stack allocated buffer of 2x number of bytes
81        let mut buf = GenericArray::<u8, Sum<N, N>>::default();
82
83        if N::USIZE < 16 {
84            // for the smallest inputs, don't bother limiting to max_bytes,
85            // just process the entire array. When "faster-hex" is enabled,
86            // this avoids its logic that winds up going to the fallback anyway
87            hex_encode_fallback::<UPPER>(arr, &mut buf);
88        } else {
89            hex_encode::<UPPER>(input, &mut buf);
90        }
91
92        f.write_str(unsafe { str::from_utf8_unchecked(buf.get_unchecked(..max_digits)) })?;
93    } else {
94        // For large array use chunks of up to 1024 bytes (2048 hex chars)
95        let mut buf = [0u8; 2048];
96        let mut digits_left = max_digits;
97
98        for chunk in input.chunks(1024) {
99            hex_encode::<UPPER>(chunk, &mut buf);
100
101            let n = min(chunk.len() * 2, digits_left);
102            // SAFETY: n will always be within bounds due to the above min
103            f.write_str(unsafe { str::from_utf8_unchecked(buf.get_unchecked(..n)) })?;
104            digits_left -= n;
105        }
106    }
107    Ok(())
108}
109
110impl<N: ArrayLength> fmt::LowerHex for GenericArray<u8, N>
111where
112    N: Add<N>,
113    Sum<N, N>: ArrayLength,
114{
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        generic_hex::<_, false>(self, f)
117    }
118}
119
120impl<N: ArrayLength> fmt::UpperHex for GenericArray<u8, N>
121where
122    N: Add<N>,
123    Sum<N, N>: ArrayLength,
124{
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        generic_hex::<_, true>(self, f)
127    }
128}