1use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq, Error)]
7pub enum HexDecodeError {
8 #[error("odd hex length {length}")]
10 OddLength {
11 length: usize,
13 },
14 #[error("invalid hex byte {byte:#04x}")]
16 InvalidByte {
17 byte: u8,
19 },
20}
21
22pub fn hex_encode_bytes(bytes: &[u8]) -> String {
24 const HEX: &[u8; 16] = b"0123456789abcdef";
25 let mut encoded = String::with_capacity(bytes.len() * 2);
26 for byte in bytes {
27 encoded.push(char::from(HEX[(byte >> 4) as usize]));
28 encoded.push(char::from(HEX[(byte & 0x0f) as usize]));
29 }
30 encoded
31}
32
33fn nibble(byte: u8) -> Result<u8, HexDecodeError> {
34 match byte {
35 b'0'..=b'9' => Ok(byte - b'0'),
36 b'a'..=b'f' => Ok(byte - b'a' + 10),
37 _ => Err(HexDecodeError::InvalidByte { byte }),
38 }
39}
40
41pub(crate) fn is_lower_hex_byte(byte: u8) -> bool {
42 nibble(byte).is_ok()
43}
44
45pub fn hex_decode_bytes(encoded: &str) -> Result<Vec<u8>, HexDecodeError> {
48 let bytes = encoded.as_bytes();
49 if bytes.len() % 2 != 0 {
50 return Err(HexDecodeError::OddLength {
51 length: bytes.len(),
52 });
53 }
54 let mut decoded = Vec::with_capacity(bytes.len() / 2);
55 for pair in bytes.chunks_exact(2) {
56 decoded.push((nibble(pair[0])? << 4) | nibble(pair[1])?);
57 }
58 Ok(decoded)
59}