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