commonware_utils/
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/// Macro for converting sequence of string literals containing hex-encoded data
101/// into a [FixedBytes] type.
102///
103/// [FixedBytes]: crate::sequence::FixedBytes
104#[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        // newlines
179        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        // tabs
193        assert_eq!(hex!("9f	d		1		f07	3		01	"), [0x9f, 0xd1, 0xf0, 0x73, 0x01]);
194        // spaces
195        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// https://github.com/alloy-rs/core/blob/main/LICENSE-MIT
217//
218// Permission is hereby granted, free of charge, to any
219// person obtaining a copy of this software and associated
220// documentation files (the "Software"), to deal in the
221// Software without restriction, including without
222// limitation the rights to use, copy, modify, merge,
223// publish, distribute, sublicense, and/or sell copies of
224// the Software, and to permit persons to whom the Software
225// is furnished to do so, subject to the following
226// conditions:
227//
228// The above copyright notice and this permission notice
229// shall be included in all copies or substantial portions
230// of the Software.
231//
232// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
233// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
234// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
235// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
236// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
237// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
238// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
239// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
240// DEALINGS IN THE SOFTWARE.