1use std::error::Error;
4use std::fmt;
5
6const LOWER_HEX: &[u8; 16] = b"0123456789abcdef";
7const UPPER_HEX: &[u8; 16] = b"0123456789ABCDEF";
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum HexDecodeError {
13 OddLength {
15 len: usize,
17 },
18 InvalidCharacter {
20 index: usize,
22 byte: u8,
24 },
25}
26
27impl fmt::Display for HexDecodeError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::OddLength { len } => {
31 write!(f, "hex input must have even byte length, got {len}")
32 }
33 Self::InvalidCharacter { index, byte } => {
34 write!(
35 f,
36 "hex input contains invalid byte 0x{byte:02x} at position {index}"
37 )
38 }
39 }
40 }
41}
42
43impl Error for HexDecodeError {}
44
45#[must_use]
55pub fn encode_hex_lower(bytes: impl AsRef<[u8]>) -> String {
56 encode_hex_with_alphabet(bytes.as_ref(), LOWER_HEX)
57}
58
59#[must_use]
69pub fn encode_hex_upper(bytes: impl AsRef<[u8]>) -> String {
70 encode_hex_with_alphabet(bytes.as_ref(), UPPER_HEX)
71}
72
73pub fn decode_hex(encoded: impl AsRef<str>) -> Result<Vec<u8>, HexDecodeError> {
94 let encoded = encoded.as_ref();
95 let bytes = encoded.as_bytes();
96 if bytes.len() % 2 != 0 {
97 return Err(HexDecodeError::OddLength { len: bytes.len() });
98 }
99
100 let mut decoded = Vec::with_capacity(bytes.len() / 2);
101 for (pair_index, chunk) in bytes.chunks_exact(2).enumerate() {
102 let high_index = pair_index * 2;
103 let high = decode_nibble(chunk[0], high_index)?;
104 let low = decode_nibble(chunk[1], high_index + 1)?;
105 decoded.push((high << 4) | low);
106 }
107
108 Ok(decoded)
109}
110
111fn encode_hex_with_alphabet(bytes: &[u8], alphabet: &[u8; 16]) -> String {
112 let mut encoded = String::with_capacity(bytes.len() * 2);
113 for byte in bytes {
114 encoded.push(alphabet[(byte >> 4) as usize] as char);
115 encoded.push(alphabet[(byte & 0x0f) as usize] as char);
116 }
117 encoded
118}
119
120fn decode_nibble(byte: u8, index: usize) -> Result<u8, HexDecodeError> {
121 match byte {
122 b'0'..=b'9' => Ok(byte - b'0'),
123 b'a'..=b'f' => Ok(byte - b'a' + 10),
124 b'A'..=b'F' => Ok(byte - b'A' + 10),
125 _ => Err(HexDecodeError::InvalidCharacter { index, byte }),
126 }
127}