alloy_primitives/
hex_literal.rs

1//! Hex literal macro implementation.
2//!
3//! Modified from the [`hex-literal`](https://github.com/RustCrypto/utils/tree/master/hex-literal)
4//! crate to allow `0x` prefixes.
5
6const fn next_hex_char(string: &[u8], mut pos: usize) -> Option<(u8, usize)> {
7    while pos < string.len() {
8        let raw_val = string[pos];
9        pos += 1;
10        let val = match raw_val {
11            b'0'..=b'9' => raw_val - 48,
12            b'A'..=b'F' => raw_val - 55,
13            b'a'..=b'f' => raw_val - 87,
14            b' ' | b'\r' | b'\n' | b'\t' => continue,
15            0..=127 => panic!("Encountered invalid ASCII character"),
16            _ => panic!("Encountered non-ASCII character"),
17        };
18        return Some((val, pos));
19    }
20    None
21}
22
23const fn next_byte(string: &[u8], pos: usize) -> Option<(u8, usize)> {
24    let (half1, pos) = match next_hex_char(string, pos) {
25        Some(v) => v,
26        None => return None,
27    };
28    let (half2, pos) = match next_hex_char(string, pos) {
29        Some(v) => v,
30        None => panic!("Odd number of hex characters"),
31    };
32    Some(((half1 << 4) + half2, pos))
33}
34
35/// Strips the `0x` prefix from a hex string.
36///
37/// This function is an implementation detail and SHOULD NOT be called directly!
38#[doc(hidden)]
39pub const fn strip_hex_prefix(string: &[u8]) -> &[u8] {
40    if let [b'0', b'x' | b'X', rest @ ..] = string {
41        rest
42    } else {
43        string
44    }
45}
46
47/// Compute length of a byte array which will be decoded from the strings.
48///
49/// This function is an implementation detail and SHOULD NOT be called directly!
50#[doc(hidden)]
51pub const fn len(strings: &[&[u8]]) -> usize {
52    let mut i = 0;
53    let mut len = 0;
54    while i < strings.len() {
55        let mut pos = 0;
56        while let Some((_, new_pos)) = next_byte(strings[i], pos) {
57            len += 1;
58            pos = new_pos;
59        }
60        i += 1;
61    }
62    len
63}
64
65/// Decode hex strings into a byte array of pre-computed length.
66///
67/// This function is an implementation detail and SHOULD NOT be called directly!
68#[doc(hidden)]
69pub const fn decode<const LEN: usize>(strings: &[&[u8]]) -> [u8; LEN] {
70    let mut i = 0;
71    let mut buf = [0u8; LEN];
72    let mut buf_pos = 0;
73    while i < strings.len() {
74        let mut pos = 0;
75        while let Some((byte, new_pos)) = next_byte(strings[i], pos) {
76            buf[buf_pos] = byte;
77            buf_pos += 1;
78            pos = new_pos;
79        }
80        i += 1;
81    }
82    if LEN != buf_pos {
83        panic!("Length mismatch. Please report this bug.");
84    }
85    buf
86}
87
88/// Macro for converting sequence of string literals containing hex-encoded data
89/// into an array of bytes.
90#[macro_export]
91macro_rules! hex {
92    ($($s:literal)*) => {const {
93        const STRINGS: &[&[u8]] = &[$( $crate::hex_literal::strip_hex_prefix($s.as_bytes()), )*];
94        $crate::hex_literal::decode::<{ $crate::hex_literal::len(STRINGS) }>(STRINGS)
95    }};
96}
97#[doc(hidden)] // Use `crate::hex` directly instead!
98pub use crate::hex;
99
100#[cfg(test)]
101mod tests {
102    #[test]
103    fn single_literal() {
104        assert_eq!(hex!("ff e4"), [0xff, 0xe4]);
105    }
106
107    #[test]
108    fn empty() {
109        let nothing: [u8; 0] = hex!();
110        let empty_literals: [u8; 0] = hex!("" "" "");
111        let expected: [u8; 0] = [];
112        assert_eq!(nothing, expected);
113        assert_eq!(empty_literals, expected);
114    }
115
116    #[test]
117    fn upper_case() {
118        assert_eq!(hex!("AE DF 04 B2"), [0xae, 0xdf, 0x04, 0xb2]);
119        assert_eq!(hex!("FF BA 8C 00 01"), [0xff, 0xba, 0x8c, 0x00, 0x01]);
120    }
121
122    #[test]
123    fn mixed_case() {
124        assert_eq!(hex!("bF dd E4 Cd"), [0xbf, 0xdd, 0xe4, 0xcd]);
125    }
126
127    #[test]
128    fn can_strip_prefix() {
129        assert_eq!(hex!("0x1a2b3c"), [0x1a, 0x2b, 0x3c]);
130        assert_eq!(hex!("0xa1" "0xb2" "0xc3"), [0xa1, 0xb2, 0xc3]);
131    }
132
133    #[test]
134    fn multiple_literals() {
135        assert_eq!(
136            hex!(
137                "01 dd f7 7f"
138                "ee f0 d8"
139            ),
140            [0x01, 0xdd, 0xf7, 0x7f, 0xee, 0xf0, 0xd8]
141        );
142        assert_eq!(
143            hex!(
144                "ff"
145                "e8 d0"
146                ""
147                "01 1f"
148                "ab"
149            ),
150            [0xff, 0xe8, 0xd0, 0x01, 0x1f, 0xab]
151        );
152    }
153
154    #[test]
155    fn no_spacing() {
156        assert_eq!(hex!("abf0d8bb0f14"), [0xab, 0xf0, 0xd8, 0xbb, 0x0f, 0x14]);
157        assert_eq!(
158            hex!("09FFd890cbcCd1d08F"),
159            [0x09, 0xff, 0xd8, 0x90, 0xcb, 0xcc, 0xd1, 0xd0, 0x8f]
160        );
161    }
162
163    #[test]
164    fn allows_various_spacing() {
165        // newlines
166        assert_eq!(
167            hex!(
168                "f
169                f
170                d
171                0
172                e
173                
174                8
175                "
176            ),
177            [0xff, 0xd0, 0xe8]
178        );
179        // tabs
180        assert_eq!(hex!("9f	d		1		f07	3		01	"), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
181        // spaces
182        assert_eq!(hex!(" e    e d0  9 1   f  f  "), [0xee, 0xd0, 0x91, 0xff]);
183    }
184
185    #[test]
186    const fn can_use_const() {
187        const _: [u8; 4] = hex!("ff d3 01 7f");
188    }
189}