hexlit/
lib.rs

1//! This crate provides the `hex!` macro for converting
2//! hexadecimal string literals to a byte array at compile
3//! time.
4//!
5//! # Examples
6//! ```
7//! use hexlit::hex;
8//!
9//! const DATA: [u8; 4] = hex!("01020304");
10//! assert_eq!(DATA, [1, 2, 3, 4]);
11//! assert_eq!(hex!("0xDEADBEEF"), [0xDE, 0xAD, 0xBE, 0xEF]);
12//! assert_eq!(hex!("a1b2c3d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
13//! assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
14//! assert_eq!(hex!("0a0B0C0d"), [10, 11, 12, 13]);
15//! assert_eq!(hex!(0a "01" 0C 02), [10, 1, 12, 2]);
16//! ```
17#![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    // Count the number of occurrences of a char.
70    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    // Checks if part of set of valid delimiters.
97    pub const fn is_valid_delimiter(c: u8) -> bool {
98        matches!(c, b' ' | b'"' | b'_' | b'|' | b'-' | b'\n')
99    }
100
101    // Converts a individual byte into its correct integer
102    // counter-part.
103    #[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 {} // Unreachable
113            }
114        }
115    }
116
117    // Converts a hex-string to its byte array representation.
118    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}