hex_conservative/lib.rs
1// SPDX-License-Identifier: CC0-1.0
2
3//! Hex encoding and decoding.
4//!
5//! General purpose hex encoding/decoding library with a conservative MSRV and dependency policy.
6//!
7//! ## Const hex literals
8//!
9//! ```
10//! use hex_conservative::hex;
11//!
12//! const GENESIS: [u8; 32] = hex!("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
13//! ```
14//!
15//! ## Runtime hex parsing
16//!
17//! ```
18//! # #[cfg(feature = "alloc")] {
19//! // In your manifest use the `package` key to improve import ergonomics.
20//! // hex = { package = "hex-conservative", version = "*" }
21//! # use hex_conservative as hex; // No need for this if using `package` as above.
22//! use hex::prelude::*;
23//!
24//! // Decode an arbitrary length hex string into a vector.
25//! let v = Vec::from_hex("deadbeef").expect("valid hex digits");
26//! // Or a known length hex string into a fixed size array.
27//! let a = <[u8; 4]>::from_hex("deadbeef").expect("valid length and valid hex digits");
28//!
29//! // We support `LowerHex` and `UpperHex` out of the box for `[u8]` slices.
30//! println!("An array as lower hex: {:x}", a.as_hex());
31//! // And for vecs since `Vec` derefs to byte slice.
32//! println!("A vector as upper hex: {:X}", v.as_hex());
33//!
34//! // Allocate a new string (also `to_upper_hex_string`).
35//! let s = v.to_lower_hex_string();
36//!
37//! // Please note, mixed case strings will still parse successfully but we only
38//! // support displaying hex in a single case.
39//! assert_eq!(
40//! Vec::from_hex("dEaDbEeF").expect("valid mixed case hex digits"),
41//! Vec::from_hex("deadbeef").expect("valid hex digits"),
42//! );
43//! # }
44//! ```
45
46#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
47// Experimental features we need.
48#![cfg_attr(docsrs, feature(doc_cfg))]
49// Coding conventions
50#![warn(missing_docs)]
51
52#[cfg(feature = "alloc")]
53extern crate alloc;
54
55#[doc(hidden)]
56pub mod _export {
57 /// A re-export of core::*
58 pub mod _core {
59 pub use core::*;
60 }
61}
62
63pub mod buf_encoder;
64pub mod display;
65pub mod error;
66mod iter;
67pub mod parse;
68#[cfg(feature = "serde")]
69pub mod serde;
70
71/// Parses hex strings in const contexts.
72///
73/// Returns `[u8; N]` arrays. The string must have even length.
74#[macro_export]
75macro_rules! hex {
76 ($hex:expr) => {{
77 const _: () = assert!($hex.len() % 2 == 0, "hex string must have even length");
78
79 const fn decode_digit(digit: u8) -> u8 {
80 match digit {
81 b'0'..=b'9' => digit - b'0',
82 b'a'..=b'f' => digit - b'a' + 10,
83 b'A'..=b'F' => digit - b'A' + 10,
84 _ => panic!("invalid hex digit"),
85 }
86 }
87
88 let mut output = [0u8; $hex.len() / 2];
89 let bytes = $hex.as_bytes();
90
91 let mut i = 0;
92 while i < output.len() {
93 let high = decode_digit(bytes[i * 2]);
94 let low = decode_digit(bytes[i * 2 + 1]);
95 output[i] = (high << 4) | low;
96 i += 1;
97 }
98
99 output
100 }};
101}
102
103/// Re-exports of the common crate traits.
104pub mod prelude {
105 #[doc(inline)]
106 pub use crate::{display::DisplayHex, parse::FromHex};
107}
108
109pub(crate) use table::Table;
110
111#[rustfmt::skip] // Keep public re-exports separate.
112#[doc(inline)]
113pub use self::{
114 display::DisplayHex,
115 error::{OddLengthStringError, HexToBytesError, HexToArrayError, InvalidCharError},
116 iter::{BytesToHexIter, HexToBytesIter, HexSliceToBytesIter},
117 parse::FromHex,
118};
119
120/// Possible case of hex.
121#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
122pub enum Case {
123 /// Produce lower-case chars (`[0-9a-f]`).
124 ///
125 /// This is the default.
126 Lower,
127
128 /// Produce upper-case chars (`[0-9A-F]`).
129 Upper,
130}
131
132impl Default for Case {
133 #[inline]
134 fn default() -> Self { Case::Lower }
135}
136
137impl Case {
138 /// Returns the encoding table.
139 ///
140 /// The returned table may only contain displayable ASCII chars.
141 #[inline]
142 #[rustfmt::skip]
143 pub(crate) fn table(self) -> &'static Table {
144 match self {
145 Case::Lower => &Table::LOWER,
146 Case::Upper => &Table::UPPER,
147 }
148 }
149}
150
151/// Correctness boundary for `Table`.
152mod table {
153 /// Table of hex chars.
154 //
155 // Correctness invariant: each byte in the table must be ASCII.
156 pub(crate) struct Table([u8; 16]);
157
158 impl Table {
159 pub(crate) const LOWER: Self = Table([
160 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd',
161 b'e', b'f',
162 ]);
163 pub(crate) const UPPER: Self = Table([
164 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
165 b'E', b'F',
166 ]);
167
168 /// Encodes single byte as two ASCII chars using the given table.
169 ///
170 /// The function guarantees only returning values from the provided table.
171 #[inline]
172 pub(crate) fn byte_to_chars(&self, byte: u8) -> [char; 2] {
173 let left = self.0[usize::from(byte >> 4)];
174 let right = self.0[usize::from(byte & 0x0F)];
175 [char::from(left), char::from(right)]
176 }
177
178 /// Writes the single byte as two ASCII chars in the provided buffer, and returns a `&str`
179 /// to that buffer.
180 ///
181 /// The function guarantees only returning values from the provided table.
182 #[inline]
183 pub(crate) fn byte_to_str<'a>(&self, dest: &'a mut [u8; 2], byte: u8) -> &'a str {
184 dest[0] = self.0[usize::from(byte >> 4)];
185 dest[1] = self.0[usize::from(byte & 0x0F)];
186 // SAFETY: Table inner array contains only valid ascii
187 let hex_str = unsafe { core::str::from_utf8_unchecked(dest) };
188 hex_str
189 }
190 }
191}
192
193/// Quick and dirty macro for parsing hex in tests.
194///
195/// For improved ergonomics import with: `use hex_conservative::test_hex_unwrap as hex;`
196#[macro_export]
197macro_rules! test_hex_unwrap (($hex:expr) => (<Vec<u8> as $crate::FromHex>::from_hex($hex).unwrap()));
198
199#[cfg(test)]
200#[cfg(feature = "alloc")]
201mod tests {
202 use alloc::vec::Vec;
203
204 #[test]
205 fn parse_hex_into_vector() {
206 let got = crate::test_hex_unwrap!("deadbeef");
207 let want = vec![0xde, 0xad, 0xbe, 0xef];
208 assert_eq!(got, want)
209 }
210
211 #[test]
212 fn hex_macro() {
213 let data = hex!("deadbeef");
214 assert_eq!(data, [0xde, 0xad, 0xbe, 0xef]);
215 }
216
217 #[test]
218 fn hex_macro_case_insensitive() {
219 assert_eq!(hex!("DEADBEEF"), hex!("deadbeef"));
220 }
221
222 #[test]
223 fn hex_macro_const_context() {
224 const HASH: [u8; 32] =
225 hex!("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
226 assert_eq!(HASH[0], 0x00);
227 assert_eq!(HASH[31], 0x6f);
228 }
229}