1#![no_std]
18
19#[doc(hidden)]
20#[macro_export]
21macro_rules! require_even_number_digits {
22 ($e:expr) => {
23 let _: $crate::internals::Even<[(); $e % 2]>;
24 };
25}
26
27#[macro_export]
28macro_rules! hex {
29 (@string $arg:expr) => {{
30 const DATA: &[u8] = $arg.as_bytes();
31 const RAW_LENGTH: usize = $arg.len() - $crate::internals::count_skipped(&DATA);
32 $crate::require_even_number_digits!(RAW_LENGTH);
33 $crate::internals::convert::<{RAW_LENGTH / 2}, {$arg.len()}>(&DATA)
34 }};
35 ($($tt:tt)*) => {
36 hex!(@string stringify!($($tt)*))
37 };
38}
39
40#[doc(hidden)]
41pub mod internals {
42
43 pub type Even<T> =
44 <<T as HexStringLength>::Marker as LengthIsEvenNumberOfHexDigits>::Check;
45
46 pub enum IsEvenNumberofDigits {}
47 pub enum IsOddNumberofDigits {}
48
49 pub trait HexStringLength {
50 type Marker;
51 }
52
53 impl HexStringLength for [(); 0] {
54 type Marker = IsEvenNumberofDigits;
55 }
56
57 impl HexStringLength for [(); 1] {
58 type Marker = IsOddNumberofDigits;
59 }
60
61 pub trait LengthIsEvenNumberOfHexDigits {
62 type Check;
63 }
64
65 impl LengthIsEvenNumberOfHexDigits for IsEvenNumberofDigits {
66 type Check = ();
67 }
68
69 pub const fn count_skipped(data: &[u8]) -> usize {
71 let mut char_count: usize = 0;
72 let mut char_index: usize = 0;
73
74 while char_index < data.len() {
75 if char_index + 1 < data.len() && !is_valid_delimiter(data[char_index]) {
76 let mut next_index = char_index + 1;
77 while next_index < data.len() && is_valid_delimiter(data[next_index]) {
78 char_count += 1;
79 next_index += 1;
80 }
81
82 if data[char_index] == b'0' && data[next_index] == b'x' {
83 char_count += 2;
84 }
85
86 char_index = next_index + 1;
87 } else {
88 char_index += 1;
89 char_count += 1;
90 }
91 }
92
93 char_count
94 }
95
96 pub const fn is_valid_delimiter(c: u8) -> bool {
98 matches!(c, b' ' | b'"' | b'_' | b'|' | b'-' | b'\n')
99 }
100
101 #[allow(clippy::unnecessary_operation)]
104 pub const fn to_ordinal(input: u8) -> u8 {
105 match input {
106 b'0'..=b'9' => input - b'0',
107 b'A'..=b'F' => input - b'A' + 10,
108 b'a'..=b'f' => input - b'a' + 10,
109 _ => {
110 #[allow(unconditional_panic)]
111 ["Invalid hex digit."][({ true } as usize)];
112 loop {} }
114 }
115 }
116
117 pub const fn convert<const RESULT_SIZE: usize, const STRING_SIZE: usize>(
119 input: &[u8],
120 ) -> [u8; RESULT_SIZE] {
121 let mut data = [0_u8; RESULT_SIZE];
122 let mut data_index: usize = 0;
123 let mut char_index: usize = 0;
124
125 while data_index < STRING_SIZE && char_index + 1 < STRING_SIZE {
126 if !is_valid_delimiter(input[char_index]) {
127 let mut next_index = char_index + 1;
128 while next_index < STRING_SIZE && is_valid_delimiter(input[next_index]) {
129 next_index += 1;
130 }
131
132 if !(input[char_index] == b'0' && input[next_index] == b'x') {
133 data[data_index] = to_ordinal(input[char_index]) * 16
134 + to_ordinal(input[next_index]);
135 data_index += 1;
136 }
137 char_index = next_index + 1;
138 } else {
139 char_index += 1;
140 }
141 }
142 data
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::hex;
149
150 #[test]
151 fn test_leading_zeros() {
152 assert_eq!(hex!("01020304"), [1, 2, 3, 4]);
153 }
154
155 #[test]
156 fn test_alphanumeric_lower() {
157 assert_eq!(hex!("a1b2c3d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
158 }
159
160 #[test]
161 fn test_alphanumeric_upper() {
162 assert_eq!(hex!("E5E69092"), [0xE5, 0xE6, 0x90, 0x92]);
163 }
164
165 #[test]
166 fn test_alphanumeric_mixed() {
167 assert_eq!(hex!("0a0B0C0d"), [10, 11, 12, 13]);
168 }
169
170 #[test]
171 fn test_leading_zeros_space() {
172 assert_eq!(hex!("01 02 03 04"), [1, 2, 3, 4]);
173 }
174
175 #[test]
176 fn test_alphanumeric_lower_space() {
177 assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
178 }
179
180 #[test]
181 fn test_alphanumeric_upper_space() {
182 assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
183 }
184
185 #[test]
186 fn test_alphanumeric_mixed_space() {
187 assert_eq!(hex!("0a 0B 0C 0d"), [10, 11, 12, 13]);
188 }
189
190 #[test]
191 fn test_no_quotes() {
192 assert_eq!(hex!(a0 0B 0C 0d), [0xa0, 11, 12, 13]);
193 }
194
195 #[test]
196 fn test_weird_quotes() {
197 assert_eq!(hex!(a0 "0b" 0C 0d), [0xa0, 11, 12, 13]);
198 }
199
200 #[test]
201 fn test_no_quotes_start_with_zero() {
202 assert_eq!(hex!(0A 0B 0C0d), [10, 11, 12, 13]);
203 }
204
205 #[test]
206 fn test_underscores() {
207 assert_eq!(hex!(0A_0B_0C 0d), [10, 11, 12, 13]);
208 }
209
210 #[test]
211 fn test_pipes() {
212 assert_eq!(hex!(0A|0B|0C|0d), [10, 11, 12, 13]);
213 assert_eq!(hex!(0F 03|0B|0C|0d), [15, 3, 11, 12, 13]);
214 }
215
216 #[test]
217 fn test_dashes() {
218 assert_eq!(hex!(0A-0B-0C-0d), [10, 11, 12, 13]);
219 assert_eq!(hex!("0F 03-0B 0C-0d 0E"), [15, 3, 11, 12, 13, 14]);
220 }
221
222 #[test]
223 fn test_mixed_no_quotes() {
224 assert_eq!(hex!(1a 0b_0C 0d), [0x1a, 11, 12, 13]);
225 assert_eq!(hex!(1a 0_b 0C 0d), [0x1a, 11, 12, 13]);
226 }
227
228 #[test]
229 fn test_new_lines() {
230 let a = hex!(
231 00000000 00000000 00000000
232 00000000
233 00000000 00000000 00000000 00000000 00000000
234 );
235 let b = hex!(
236 00000000 00000000 00000000 00000000
237 00000000 00000000 00000000 00000000 00000000
238 );
239 let c = hex!(
240 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
241 );
242 let d = hex!(00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000);
243
244 assert_eq!(a, b);
245 assert_eq!(b, c);
246 assert_eq!(c, d);
247 }
248
249 #[test]
250 fn test_hex_prefix() {
251 assert_eq!(
252 hex!("0xe9e7cea3dedca5984780bafc599bd69add087d56"),
253 [
254 0xE9, 0xE7, 0xCE, 0xA3, 0xDE, 0xDC, 0xA5, 0x98, 0x47, 0x80, 0xBA, 0xFC,
255 0x59, 0x9B, 0xD6, 0x9A, 0xDD, 0x08, 0x7D, 0x56
256 ]
257 );
258 }
259}