1const 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#[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#[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#[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_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)] pub 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 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}