hex_conservative/
parse.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Hex encoding and decoding.
4
5use core::{fmt, str};
6
7#[cfg(all(feature = "alloc", not(feature = "std")))]
8use crate::alloc::vec::Vec;
9use crate::iter::HexToBytesIter;
10
11/// Trait for objects that can be deserialized from hex strings.
12pub trait FromHex: Sized {
13    /// Error type returned while parsing hex string.
14    type Err: From<HexToBytesError> + Sized + fmt::Debug + fmt::Display;
15
16    /// Produces an object from a byte iterator.
17    fn from_byte_iter<I>(iter: I) -> Result<Self, Self::Err>
18    where
19        I: Iterator<Item = Result<u8, HexToBytesError>> + ExactSizeIterator + DoubleEndedIterator;
20
21    /// Produces an object from a hex string.
22    fn from_hex(s: &str) -> Result<Self, Self::Err> {
23        Self::from_byte_iter(HexToBytesIter::new(s)?)
24    }
25}
26
27#[cfg(any(test, feature = "std", feature = "alloc"))]
28impl FromHex for Vec<u8> {
29    type Err = HexToBytesError;
30
31    fn from_byte_iter<I>(iter: I) -> Result<Self, Self::Err>
32    where
33        I: Iterator<Item = Result<u8, HexToBytesError>> + ExactSizeIterator + DoubleEndedIterator,
34    {
35        iter.collect()
36    }
37}
38
39/// Hex decoding error.
40#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum HexToBytesError {
42    /// Non-hexadecimal character.
43    InvalidChar(u8),
44    /// Purported hex string had odd length.
45    OddLengthString(usize),
46}
47
48impl fmt::Display for HexToBytesError {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        use self::HexToBytesError::*;
51
52        match *self {
53            InvalidChar(ch) => write!(f, "invalid hex character {}", ch),
54            OddLengthString(ell) => write!(f, "odd hex string length {}", ell),
55        }
56    }
57}
58
59#[cfg(feature = "std")]
60impl std::error::Error for HexToBytesError {
61    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62        use self::HexToBytesError::*;
63
64        match self {
65            InvalidChar(_) | OddLengthString(_) => None,
66        }
67    }
68}
69
70macro_rules! impl_fromhex_array {
71    ($len:expr) => {
72        impl FromHex for [u8; $len] {
73            type Err = HexToArrayError;
74
75            fn from_byte_iter<I>(iter: I) -> Result<Self, Self::Err>
76            where
77                I: Iterator<Item = Result<u8, HexToBytesError>>
78                    + ExactSizeIterator
79                    + DoubleEndedIterator,
80            {
81                if iter.len() == $len {
82                    let mut ret = [0; $len];
83                    for (n, byte) in iter.enumerate() {
84                        ret[n] = byte?;
85                    }
86                    Ok(ret)
87                } else {
88                    let got = 2 * iter.len();
89                    let want = 2 * $len;
90                    Err(HexToArrayError::InvalidLength(got, want))
91                }
92            }
93        }
94    };
95}
96
97impl_fromhex_array!(2);
98impl_fromhex_array!(4);
99impl_fromhex_array!(6);
100impl_fromhex_array!(8);
101impl_fromhex_array!(10);
102impl_fromhex_array!(12);
103impl_fromhex_array!(14);
104impl_fromhex_array!(16);
105impl_fromhex_array!(20);
106impl_fromhex_array!(24);
107impl_fromhex_array!(28);
108impl_fromhex_array!(32);
109impl_fromhex_array!(33);
110impl_fromhex_array!(64);
111impl_fromhex_array!(65);
112impl_fromhex_array!(128);
113impl_fromhex_array!(256);
114impl_fromhex_array!(384);
115impl_fromhex_array!(512);
116
117/// Hex decoding error.
118#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
119pub enum HexToArrayError {
120    /// Conversion error while parsing hex string.
121    Conversion(HexToBytesError),
122    /// Tried to parse fixed-length hash from a string with the wrong length (got, want).
123    InvalidLength(usize, usize),
124}
125
126impl fmt::Display for HexToArrayError {
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        use HexToArrayError::*;
129
130        match *self {
131            Conversion(ref e) => crate::write_err!(f, "conversion error"; e),
132            InvalidLength(got, want) =>
133                write!(f, "bad hex string length {} (expected {})", got, want),
134        }
135    }
136}
137
138#[cfg(feature = "std")]
139impl std::error::Error for HexToArrayError {
140    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
141        use HexToArrayError::*;
142
143        match *self {
144            Conversion(ref e) => Some(e),
145            InvalidLength(_, _) => None,
146        }
147    }
148}
149
150impl From<HexToBytesError> for HexToArrayError {
151    fn from(e: HexToBytesError) -> Self { Self::Conversion(e) }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::display::DisplayHex;
158
159    #[test]
160    #[cfg(feature = "alloc")]
161    fn hex_error() {
162        use HexToBytesError::*;
163
164        let oddlen = "0123456789abcdef0";
165        let badchar1 = "Z123456789abcdef";
166        let badchar2 = "012Y456789abcdeb";
167        let badchar3 = "«23456789abcdef";
168
169        assert_eq!(Vec::<u8>::from_hex(oddlen), Err(OddLengthString(17)));
170        assert_eq!(
171            <[u8; 4]>::from_hex(oddlen),
172            Err(HexToArrayError::Conversion(OddLengthString(17)))
173        );
174        assert_eq!(Vec::<u8>::from_hex(badchar1), Err(InvalidChar(b'Z')));
175        assert_eq!(Vec::<u8>::from_hex(badchar2), Err(InvalidChar(b'Y')));
176        assert_eq!(Vec::<u8>::from_hex(badchar3), Err(InvalidChar(194)));
177    }
178
179    #[test]
180    fn hex_to_array() {
181        let len_sixteen = "0123456789abcdef";
182        assert!(<[u8; 8]>::from_hex(len_sixteen).is_ok());
183    }
184    #[test]
185    fn hex_to_array_error() {
186        use HexToArrayError::*;
187        let len_sixteen = "0123456789abcdef";
188        let result = <[u8; 4]>::from_hex(len_sixteen);
189        assert_eq!(result, Err(InvalidLength(16, 8)));
190        assert_eq!(&result.unwrap_err().to_string(), "bad hex string length 16 (expected 8)");
191    }
192
193    #[test]
194    fn mixed_case() {
195        let s = "DEADbeef0123";
196        let want_lower = "deadbeef0123";
197        let want_upper = "DEADBEEF0123";
198
199        let v = Vec::<u8>::from_hex(s).expect("valid hex");
200        assert_eq!(format!("{:x}", v.as_hex()), want_lower);
201        assert_eq!(format!("{:X}", v.as_hex()), want_upper);
202    }
203}