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#[macro_export]
105macro_rules! fixed_bytes {
106 ($s:tt) => {
107 const { $crate::sequence::FixedBytes::new($crate::hex!($s)) }
108 };
109}
110
111#[cfg(test)]
112mod tests {
113 use crate::sequence::FixedBytes;
114
115 #[test]
116 fn single_literal() {
117 assert_eq!(hex!("ff e4"), [0xff, 0xe4]);
118 }
119
120 #[test]
121 fn empty() {
122 let nothing: [u8; 0] = hex!();
123 let empty_literals: [u8; 0] = hex!("" "" "");
124 let expected: [u8; 0] = [];
125 assert_eq!(nothing, expected);
126 assert_eq!(empty_literals, expected);
127 }
128
129 #[test]
130 fn upper_case() {
131 assert_eq!(hex!("AE DF 04 B2"), [0xae, 0xdf, 0x04, 0xb2]);
132 assert_eq!(hex!("FF BA 8C 00 01"), [0xff, 0xba, 0x8c, 0x00, 0x01]);
133 }
134
135 #[test]
136 fn mixed_case() {
137 assert_eq!(hex!("bF dd E4 Cd"), [0xbf, 0xdd, 0xe4, 0xcd]);
138 }
139
140 #[test]
141 fn can_strip_prefix() {
142 assert_eq!(hex!("0x1a2b3c"), [0x1a, 0x2b, 0x3c]);
143 assert_eq!(hex!("0xa1" "0xb2" "0xc3"), [0xa1, 0xb2, 0xc3]);
144 }
145
146 #[test]
147 fn multiple_literals() {
148 assert_eq!(
149 hex!(
150 "01 dd f7 7f"
151 "ee f0 d8"
152 ),
153 [0x01, 0xdd, 0xf7, 0x7f, 0xee, 0xf0, 0xd8]
154 );
155 assert_eq!(
156 hex!(
157 "ff"
158 "e8 d0"
159 ""
160 "01 1f"
161 "ab"
162 ),
163 [0xff, 0xe8, 0xd0, 0x01, 0x1f, 0xab]
164 );
165 }
166
167 #[test]
168 fn no_spacing() {
169 assert_eq!(hex!("abf0d8bb0f14"), [0xab, 0xf0, 0xd8, 0xbb, 0x0f, 0x14]);
170 assert_eq!(
171 hex!("09FFd890cbcCd1d08F"),
172 [0x09, 0xff, 0xd8, 0x90, 0xcb, 0xcc, 0xd1, 0xd0, 0x8f]
173 );
174 }
175
176 #[test]
177 fn allows_various_spacing() {
178 assert_eq!(
180 hex!(
181 "f
182 f
183 d
184 0
185 e
186
187 8
188 "
189 ),
190 [0xff, 0xd0, 0xe8]
191 );
192 assert_eq!(hex!("9f d 1 f07 3 01 "), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
194 assert_eq!(hex!(" e e d0 9 1 f f "), [0xee, 0xd0, 0x91, 0xff]);
196 }
197
198 #[test]
199 const fn can_use_const() {
200 const _: [u8; 4] = hex!("ff d3 01 7f");
201 }
202
203 #[test]
204 fn fixed_bytes() {
205 let bytes = fixed_bytes!("0x112233");
206 assert_eq!(bytes.as_ref(), &[0x11, 0x22, 0x33]);
207 assert_eq!(format!("{bytes}"), "112233");
208 }
209
210 #[test]
211 const fn const_fixed_bytes() {
212 const _: FixedBytes<4> = fixed_bytes!("0badc0de");
213 }
214}
215
216