mesh_loader/utils/
hex.rs

1// Based on https://github.com/KokaKiwi/rust-hex/pull/62, but with several additional optimizations.
2
3use std::{io, mem};
4
5// Lookup table for ascii to hex decoding.
6#[rustfmt::skip]
7static DECODE_TABLE: [u8; 256] = {
8    const __: u8 = u8::MAX;
9    [
10        //  _1  _2  _3  _4  _5  _6  _7  _8  _9  _A  _B  _C  _D  _E  _F
11        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0_
12        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1_
13        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2_
14         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, __, __, __, __, __, __, // 3_
15        __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 4_
16        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 5_
17        __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 6_
18        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7_
19        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8_
20        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9_
21        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A_
22        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B_
23        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C_
24        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D_
25        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E_
26        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F_
27    ]
28};
29
30#[inline]
31pub(crate) fn decode(bytes: &[u8]) -> io::Result<Vec<u8>> {
32    if bytes.len() % 2 != 0 {
33        bail!("invalid length {}", bytes.len());
34    }
35    let mut out = vec![0; bytes.len() / 2];
36    // Using hex2byte16 instead of hex2byte here increases throughput by 1.5x,
37    // but it also increases binary size.
38    // let hex2byte = hex2byte16;
39    let hex2byte = hex2byte;
40    decode_to_slice(bytes, &mut out, hex2byte)?;
41    Ok(out)
42}
43
44#[inline]
45fn decode_to_slice(
46    bytes: &[u8],
47    out: &mut [u8],
48    hex2byte: fn(&[u8], &mut u8) -> io::Result<()>,
49) -> io::Result<()> {
50    const CHUNK_SIZE: usize = mem::size_of::<usize>();
51    // First, process the data in usize units. This improves performance by
52    // reducing the number of writes to memory.
53    let mut bytes = bytes.chunks_exact(CHUNK_SIZE);
54    let mut out = out.chunks_exact_mut(CHUNK_SIZE / 2);
55    for (bytes, out) in bytes.by_ref().zip(out.by_ref()) {
56        let mut num = [0; CHUNK_SIZE / 2];
57        for (bytes, num) in bytes.chunks_exact(2).zip(&mut num) {
58            hex2byte(bytes, num)?;
59        }
60        out.copy_from_slice(&num);
61    }
62    // Then process the remaining data.
63    let bytes = bytes.remainder();
64    let out = out.into_remainder();
65    for (bytes, out) in bytes.chunks_exact(2).zip(out) {
66        hex2byte(bytes, out)?;
67    }
68    Ok(())
69}
70
71#[inline]
72fn hex2byte(bytes: &[u8], out: &mut u8) -> io::Result<()> {
73    let upper = DECODE_TABLE[bytes[0] as usize];
74    let lower = DECODE_TABLE[bytes[1] as usize];
75    if upper == u8::MAX {
76        bail!("invalid hex character {}", bytes[0] as char);
77    }
78    if lower == u8::MAX {
79        bail!("invalid hex character {}", bytes[1] as char);
80    }
81    *out = (upper << 4) | lower;
82    Ok(())
83}
84
85#[cfg(test)]
86static ENCODE_LOWER_TABLE: &[u8; 16] = b"0123456789abcdef";
87#[cfg(test)]
88static ENCODE_UPPER_TABLE: &[u8; 16] = b"0123456789ABCDEF";
89#[cfg(test)]
90#[inline]
91const fn byte2hex(byte: u8, table: &[u8; 16]) -> [u8; 2] {
92    let upper = table[((byte & 0xF0) >> 4) as usize];
93    let lower = table[(byte & 0x0F) as usize];
94    [upper, lower]
95}
96
97#[cfg(test)]
98#[inline]
99fn hex2byte16(bytes: &[u8], out: &mut u8) -> io::Result<()> {
100    static DECODE_TABLE: [u16; 65536] = {
101        let mut table = [u16::MAX; 65536];
102        let mut i = 0;
103        loop {
104            let lower = u16::from_ne_bytes(byte2hex(i, ENCODE_LOWER_TABLE));
105            let upper = u16::from_ne_bytes(byte2hex(i, ENCODE_UPPER_TABLE));
106            table[lower as usize] = i as u16;
107            table[upper as usize] = i as u16;
108            if i == u8::MAX {
109                break;
110            }
111            i += 1;
112        }
113        table
114    };
115    let n = u16::from_ne_bytes(bytes.try_into().unwrap());
116    let num = DECODE_TABLE[n as usize];
117    if num == u16::MAX {
118        bail!(
119            "invalid hex character {}{}",
120            bytes[0] as char,
121            bytes[1] as char
122        );
123    }
124    #[allow(clippy::cast_possible_truncation)]
125    {
126        *out = num as u8;
127    }
128    Ok(())
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    fn encode_naive(bytes: &[u8], table: &[u8; 16]) -> Vec<u8> {
136        let mut out = vec![0; bytes.len() * 2];
137        for (&byte, out) in bytes.iter().zip(out.chunks_exact_mut(2)) {
138            out.copy_from_slice(&byte2hex(byte, table));
139        }
140        out
141    }
142    fn decode_naive(
143        bytes: &[u8],
144        hex2byte: fn(&[u8], &mut u8) -> io::Result<()>,
145    ) -> io::Result<Vec<u8>> {
146        if bytes.len() % 2 != 0 {
147            bail!("invalid length {}", bytes.len());
148        }
149        let mut out = vec![0; bytes.len() / 2];
150        for (bytes, out) in bytes.chunks_exact(2).zip(&mut out) {
151            hex2byte(bytes, out)?;
152        }
153        Ok(out)
154    }
155    #[inline]
156    fn decode16(bytes: &[u8]) -> io::Result<Vec<u8>> {
157        if bytes.len() % 2 != 0 {
158            bail!("invalid length {}", bytes.len());
159        }
160        let mut out = vec![0; bytes.len() / 2];
161        decode_to_slice(bytes, &mut out, hex2byte16)?;
162        Ok(out)
163    }
164
165    #[test]
166    fn decode_max() {
167        let x = &[!0];
168        let hex_lower = encode_naive(x, ENCODE_LOWER_TABLE);
169        assert_eq!(decode(&hex_lower).unwrap(), x);
170        assert_eq!(decode16(&hex_lower).unwrap(), x);
171        assert_eq!(decode_naive(&hex_lower, hex2byte).unwrap(), x);
172        assert_eq!(decode_naive(&hex_lower, hex2byte16).unwrap(), x);
173    }
174    ::quickcheck::quickcheck! {
175        fn decode_valid(x: String) -> bool {
176            if x.is_empty() {
177                return true;
178            }
179            let x = x.as_bytes();
180            let hex_lower = encode_naive(x, ENCODE_LOWER_TABLE);
181            assert_eq!(decode(&hex_lower).unwrap(), x);
182            assert_eq!(decode16(&hex_lower).unwrap(), x);
183            assert_eq!(decode_naive(&hex_lower, hex2byte).unwrap(), x);
184            assert_eq!(decode_naive(&hex_lower, hex2byte16).unwrap(), x);
185            let hex_upper = encode_naive(x, ENCODE_UPPER_TABLE);
186            assert_eq!(decode(&hex_upper).unwrap(), x);
187            assert_eq!(decode16(&hex_lower).unwrap(), x);
188            assert_eq!(decode_naive(&hex_upper, hex2byte).unwrap(), x);
189            assert_eq!(decode_naive(&hex_upper, hex2byte16).unwrap(), x);
190            true
191        }
192        fn decode_invalid(x: String) -> bool {
193            if x.is_empty() {
194                return true;
195            }
196            let mut x = x.as_bytes();
197            if x.len() < 2 {
198                return true;
199            }
200            if x.len() % 2 != 0 {
201                x = &x[..x.len() - 2];
202            }
203            let res = decode(x).ok();
204            assert_eq!(res, decode16(x).ok());
205            assert_eq!(res, decode_naive(x, hex2byte).ok());
206            assert_eq!(res, decode_naive(x, hex2byte16).ok());
207            true
208        }
209    }
210}