Skip to main content

fast_hex_lite/
encode.rs

1use crate::Error;
2
3/// Returns the required output length (in bytes) for encoding `n` bytes to hex.
4#[inline]
5pub const fn encoded_len(n: usize) -> usize {
6    n * 2
7}
8
9/// Encode bytes into hex (lowercase or uppercase) into the provided output slice.
10///
11/// Returns the number of bytes written on success.
12///
13/// # Errors
14///
15/// Returns [`Error::OutputTooSmall`] if `dst_hex` is not large enough.
16#[inline]
17pub fn encode_to_slice(src: &[u8], dst_hex: &mut [u8], lowercase: bool) -> Result<usize, Error> {
18    let out_len = encoded_len(src.len());
19    if dst_hex.len() < out_len {
20        return Err(Error::OutputTooSmall);
21    }
22
23    let alphabet = if lowercase {
24        b"0123456789abcdef"
25    } else {
26        b"0123456789ABCDEF"
27    };
28
29    // SAFETY-FREE hot loop:
30    // - dst_hex length already validated
31    // - we write exactly 2 bytes per input byte
32    for (byte, out_pair) in src
33        .iter()
34        .copied()
35        .zip(dst_hex[..out_len].chunks_exact_mut(2))
36    {
37        out_pair[0] = alphabet[(byte >> 4) as usize];
38        out_pair[1] = alphabet[(byte & 0x0f) as usize];
39    }
40
41    Ok(out_len)
42}
43
44/// Encode into a newly allocated `String`.
45///
46/// Available only with the `std` feature.
47#[cfg(feature = "std")]
48#[inline]
49pub fn encode_to_string(src: &[u8], lowercase: bool) -> String {
50    let mut out = vec![0u8; encoded_len(src.len())];
51    // infallible because buffer is pre-sized
52    let _ = encode_to_slice(src, &mut out, lowercase);
53
54    // Always valid UTF-8 because we only write ASCII hex characters.
55    String::from_utf8(out).expect("hex output is always valid UTF-8")
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_encode_empty() {
64        let mut out = [0u8; 0];
65        let written = encode_to_slice(&[], &mut out, true).unwrap();
66        assert_eq!(written, 0);
67    }
68
69    #[test]
70    fn test_encode_lowercase() {
71        let input = [0xde, 0xad, 0xbe, 0xef];
72        let mut out = [0u8; 8];
73        encode_to_slice(&input, &mut out, true).unwrap();
74        assert_eq!(&out, b"deadbeef");
75    }
76
77    #[test]
78    fn test_encode_uppercase() {
79        let input = [0xde, 0xad, 0xbe, 0xef];
80        let mut out = [0u8; 8];
81        encode_to_slice(&input, &mut out, false).unwrap();
82        assert_eq!(&out, b"DEADBEEF");
83    }
84
85    #[test]
86    fn test_encode_output_too_small() {
87        let input = [0xde, 0xad];
88        let mut out = [0u8; 2];
89        let err = encode_to_slice(&input, &mut out, true).unwrap_err();
90        assert_eq!(err, Error::OutputTooSmall);
91    }
92}