1const fn next_hex_char(string: &[u8], mut pos: usize) -> Option<(u8, usize)> {
9 while pos < string.len() {
10 let raw_val = string[pos];
11 pos += 1;
12 let val = match raw_val {
13 b'0'..=b'9' => raw_val - 48,
14 b'A'..=b'F' => raw_val - 55,
15 b'a'..=b'f' => raw_val - 87,
16 b' ' | b'\r' | b'\n' | b'\t' => continue,
17 0..=127 => panic!("Encountered invalid ASCII character"),
18 _ => panic!("Encountered non-ASCII character"),
19 };
20 return Some((val, pos));
21 }
22 None
23}
24
25const fn next_byte(string: &[u8], pos: usize) -> Option<(u8, usize)> {
26 let (half1, pos) = match next_hex_char(string, pos) {
27 Some(v) => v,
28 None => return None,
29 };
30 let (half2, pos) = match next_hex_char(string, pos) {
31 Some(v) => v,
32 None => panic!("Odd number of hex characters"),
33 };
34 Some(((half1 << 4) + half2, pos))
35}
36
37#[doc(hidden)]
41pub const fn strip_hex_prefix(string: &[u8]) -> &[u8] {
42 if let [b'0', b'x' | b'X', rest @ ..] = string {
43 rest
44 } else {
45 string
46 }
47}
48
49#[doc(hidden)]
53pub const fn len(strings: &[&[u8]]) -> usize {
54 let mut i = 0;
55 let mut len = 0;
56 while i < strings.len() {
57 let mut pos = 0;
58 while let Some((_, new_pos)) = next_byte(strings[i], pos) {
59 len += 1;
60 pos = new_pos;
61 }
62 i += 1;
63 }
64 len
65}
66
67#[doc(hidden)]
71pub const fn decode<const LEN: usize>(strings: &[&[u8]]) -> [u8; LEN] {
72 let mut i = 0;
73 let mut buf = [0u8; LEN];
74 let mut buf_pos = 0;
75 while i < strings.len() {
76 let mut pos = 0;
77 while let Some((byte, new_pos)) = next_byte(strings[i], pos) {
78 buf[buf_pos] = byte;
79 buf_pos += 1;
80 pos = new_pos;
81 }
82 i += 1;
83 }
84 if LEN != buf_pos {
85 panic!("Length mismatch. Please report this bug.");
86 }
87 buf
88}
89
90#[macro_export]
93macro_rules! hex {
94 ($($s:literal)*) => {const {
95 const STRINGS: &[&[u8]] = &[$( $crate::hex_literal::strip_hex_prefix($s.as_bytes()), )*];
96 $crate::hex_literal::decode::<{ $crate::hex_literal::len(STRINGS) }>(STRINGS)
97 }};
98}
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 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 assert_eq!(hex!("9f d 1 f07 3 01 "), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
181 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}
190
191