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//! ## Basic Usage
8//! ```
9//! # #[cfg(feature = "alloc")] {
10//! // In your manifest use the `package` key to improve import ergonomics.
11//! // hex = { package = "hex-conservative", version = "*" }
12//! # use hex_conservative as hex; // No need for this if using `package` as above.
13//! use hex::prelude::*;
14//!
15//! // Decode an arbitrary length hex string into a vector.
16//! let v = Vec::from_hex("deadbeef").expect("valid hex digits");
17//! // Or a known length hex string into a fixed size array.
18//! let a = <[u8; 4]>::from_hex("deadbeef").expect("valid length and valid hex digits");
19//!
20//! // We support `LowerHex` and `UpperHex` out of the box for `[u8]` slices.
21//! println!("An array as lower hex: {:x}", a.as_hex());
22//! // And for vecs since `Vec` derefs to byte slice.
23//! println!("A vector as upper hex: {:X}", v.as_hex());
24//!
25//! // Allocate a new string (also `to_upper_hex_string`).
26//! let s = v.to_lower_hex_string();
27//!
28//! // Please note, mixed case strings will still parse successfully but we only
29//! // support displaying hex in a single case.
30//! assert_eq!(
31//! Vec::from_hex("dEaDbEeF").expect("valid mixed case hex digits"),
32//! Vec::from_hex("deadbeef").expect("valid hex digits"),
33//! );
34//! # }
35//! ```
36
37#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
38// Experimental features we need.
39#![cfg_attr(docsrs, feature(doc_cfg))]
40// Coding conventions
41#![warn(missing_docs)]
42
43#[cfg(feature = "alloc")]
44extern crate alloc;
45
46#[doc(hidden)]
47pub mod _export {
48 /// A re-export of core::*
49 pub mod _core {
50 pub use core::*;
51 }
52}
53
54pub mod buf_encoder;
55pub mod display;
56pub mod error;
57mod iter;
58pub mod parse;
59#[cfg(feature = "serde")]
60pub mod serde;
61
62/// Re-exports of the common crate traits.
63pub mod prelude {
64 #[doc(inline)]
65 pub use crate::{display::DisplayHex, parse::FromHex};
66}
67
68pub(crate) use table::Table;
69
70#[rustfmt::skip] // Keep public re-exports separate.
71#[doc(inline)]
72pub use self::{
73 display::DisplayHex,
74 error::{OddLengthStringError, HexToBytesError, HexToArrayError, InvalidCharError},
75 iter::{BytesToHexIter, HexToBytesIter, HexSliceToBytesIter},
76 parse::FromHex,
77};
78
79/// Possible case of hex.
80#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
81pub enum Case {
82 /// Produce lower-case chars (`[0-9a-f]`).
83 ///
84 /// This is the default.
85 Lower,
86
87 /// Produce upper-case chars (`[0-9A-F]`).
88 Upper,
89}
90
91impl Default for Case {
92 #[inline]
93 fn default() -> Self { Case::Lower }
94}
95
96impl Case {
97 /// Returns the encoding table.
98 ///
99 /// The returned table may only contain displayable ASCII chars.
100 #[inline]
101 #[rustfmt::skip]
102 pub(crate) fn table(self) -> &'static Table {
103 match self {
104 Case::Lower => &Table::LOWER,
105 Case::Upper => &Table::UPPER,
106 }
107 }
108}
109
110/// Correctness boundary for `Table`.
111mod table {
112 use arrayvec::ArrayString;
113
114 /// Table of hex chars.
115 //
116 // Correctness invariant: each byte in the table must be ASCII.
117 pub(crate) struct Table([u8; 16]);
118
119 impl Table {
120 pub(crate) const LOWER: Self = Table([
121 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',
122 b'e', b'f',
123 ]);
124 pub(crate) const UPPER: Self = Table([
125 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',
126 b'E', b'F',
127 ]);
128
129 /// Encodes single byte as two ASCII chars using the given table.
130 ///
131 /// The function guarantees only returning values from the provided table.
132 #[inline]
133 pub(crate) fn byte_to_hex(&self, byte: u8) -> ArrayString<2> {
134 let left = self.0[usize::from(byte.wrapping_shr(4))];
135 let right = self.0[usize::from(byte & 0x0F)];
136
137 ArrayString::from_byte_string(&[left, right]).expect("Table only contains valid ASCII")
138 }
139 }
140}
141
142/// Quick and dirty macro for parsing hex in tests.
143///
144/// For improved ergonomics import with: `use hex_conservative::test_hex_unwrap as hex;`
145#[macro_export]
146macro_rules! test_hex_unwrap (($hex:expr) => (<Vec<u8> as $crate::FromHex>::from_hex($hex).unwrap()));
147
148#[cfg(test)]
149mod tests {
150 use crate::test_hex_unwrap as hex;
151
152 #[test]
153 fn parse_hex_into_vector() {
154 let got = hex!("deadbeef");
155 let want = vec![0xde, 0xad, 0xbe, 0xef];
156 assert_eq!(got, want)
157 }
158}