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 { rest } else { string }
41}
42
43#[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#[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_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)] pub 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 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 assert_eq!(hex!("9f d 1 f07 3 01 "), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
177 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}