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#![cfg_attr(docsrs, feature(doc_auto_cfg))]
41// Coding conventions
42#![warn(missing_docs)]
43
44#[cfg(feature = "alloc")]
45extern crate alloc;
46
47#[doc(hidden)]
48pub mod _export {
49    /// A re-export of core::*
50    pub mod _core {
51        pub use core::*;
52    }
53}
54
55pub mod buf_encoder;
56pub mod display;
57pub mod error;
58mod iter;
59pub mod parse;
60#[cfg(feature = "serde")]
61pub mod serde;
62
63/// Re-exports of the common crate traits.
64pub mod prelude {
65    #[doc(inline)]
66    pub use crate::{display::DisplayHex, parse::FromHex};
67}
68
69pub(crate) use table::Table;
70
71#[rustfmt::skip]                // Keep public re-exports separate.
72#[doc(inline)]
73pub use self::{
74    display::DisplayHex,
75    error::{OddLengthStringError, HexToBytesError, HexToArrayError, InvalidCharError},
76    iter::{BytesToHexIter, HexToBytesIter, HexSliceToBytesIter},
77    parse::FromHex,
78};
79
80/// Possible case of hex.
81#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
82pub enum Case {
83    /// Produce lower-case chars (`[0-9a-f]`).
84    ///
85    /// This is the default.
86    Lower,
87
88    /// Produce upper-case chars (`[0-9A-F]`).
89    Upper,
90}
91
92impl Default for Case {
93    #[inline]
94    fn default() -> Self { Case::Lower }
95}
96
97impl Case {
98    /// Returns the encoding table.
99    ///
100    /// The returned table may only contain displayable ASCII chars.
101    #[inline]
102    #[rustfmt::skip]
103    pub(crate) fn table(self) -> &'static Table {
104        match self {
105            Case::Lower => &Table::LOWER,
106            Case::Upper => &Table::UPPER,
107        }
108    }
109}
110
111/// Correctness boundary for `Table`.
112mod table {
113    /// Table of hex chars.
114    //
115    // Correctness invariant: each byte in the table must be ASCII.
116    pub(crate) struct Table([u8; 16]);
117
118    impl Table {
119        pub(crate) const LOWER: Self = Table([
120            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',
121            b'e', b'f',
122        ]);
123        pub(crate) const UPPER: Self = Table([
124            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',
125            b'E', b'F',
126        ]);
127
128        /// Encodes single byte as two ASCII chars using the given table.
129        ///
130        /// The function guarantees only returning values from the provided table.
131        #[inline]
132        pub(crate) fn byte_to_chars(&self, byte: u8) -> [char; 2] {
133            let left = self.0[usize::from(byte >> 4)];
134            let right = self.0[usize::from(byte & 0x0F)];
135            [char::from(left), char::from(right)]
136        }
137
138        /// Writes the single byte as two ASCII chars in the provided buffer, and returns a `&str`
139        /// to that buffer.
140        ///
141        /// The function guarantees only returning values from the provided table.
142        #[inline]
143        pub(crate) fn byte_to_str<'a>(&self, dest: &'a mut [u8; 2], byte: u8) -> &'a str {
144            dest[0] = self.0[usize::from(byte >> 4)];
145            dest[1] = self.0[usize::from(byte & 0x0F)];
146            // SAFETY: Table inner array contains only valid ascii
147            let hex_str = unsafe { core::str::from_utf8_unchecked(dest) };
148            hex_str
149        }
150    }
151}
152
153/// Quick and dirty macro for parsing hex in tests.
154///
155/// For improved ergonomics import with: `use hex_conservative::test_hex_unwrap as hex;`
156#[macro_export]
157macro_rules! test_hex_unwrap (($hex:expr) => (<Vec<u8> as $crate::FromHex>::from_hex($hex).unwrap()));
158
159#[cfg(test)]
160mod tests {
161    use crate::test_hex_unwrap as hex;
162
163    #[test]
164    fn parse_hex_into_vector() {
165        let got = hex!("deadbeef");
166        let want = vec![0xde, 0xad, 0xbe, 0xef];
167        assert_eq!(got, want)
168    }
169}