Skip to main content

commonware_formatting/
hex_literal.rs

1//! Hex literal macro implementation.
2//!
3//! Modified from the [`hex-literal`](https://github.com/RustCrypto/utils/tree/master/hex-literal)
4//! crate to allow `0x` prefixes.
5//!
6//! Vendored from [`alloy-primitives`](https://github.com/alloy-rs/core/tree/main/crates/primitives).
7
8const 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/// Strips the `0x` prefix from a hex string.
38///
39/// This function is an implementation detail and SHOULD NOT be called directly!
40#[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/// Compute length of a byte array which will be decoded from the strings.
50///
51/// This function is an implementation detail and SHOULD NOT be called directly!
52#[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/// Decode hex strings into a byte array of pre-computed length.
68///
69/// This function is an implementation detail and SHOULD NOT be called directly!
70#[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 for converting sequence of string literals containing hex-encoded data
91/// into an array of bytes.
92#[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#[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        // newlines
166        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        // tabs
180        assert_eq!(hex!("9f	d		1		f07	3		01	"), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
181        // spaces
182        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}
190
191// https://github.com/alloy-rs/core/blob/main/LICENSE-MIT
192//
193// Permission is hereby granted, free of charge, to any
194// person obtaining a copy of this software and associated
195// documentation files (the "Software"), to deal in the
196// Software without restriction, including without
197// limitation the rights to use, copy, modify, merge,
198// publish, distribute, sublicense, and/or sell copies of
199// the Software, and to permit persons to whom the Software
200// is furnished to do so, subject to the following
201// conditions:
202//
203// The above copyright notice and this permission notice
204// shall be included in all copies or substantial portions
205// of the Software.
206//
207// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
208// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
209// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
210// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
211// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
212// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
213// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
214// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
215// DEALINGS IN THE SOFTWARE.