1pub fn encode(data: &[u8]) -> String {
8 let mut result = String::with_capacity(data.len() * 2);
9 for byte in data {
10 result.push_str(&format!("{:02x}", byte));
11 }
12 result
13}
14
15pub fn decode(hex: &str) -> Result<Vec<u8>, DecodeError> {
17 let hex = hex.trim();
18
19 if hex.is_empty() {
20 return Ok(Vec::new());
21 }
22
23 if hex.len() % 2 != 0 {
24 return Err(DecodeError::OddLength);
25 }
26
27 let mut result = Vec::with_capacity(hex.len() / 2);
28
29 for chunk in hex.as_bytes().chunks(2) {
30 let high = decode_hex_digit(chunk[0])?;
31 let low = decode_hex_digit(chunk[1])?;
32 result.push((high << 4) | low);
33 }
34
35 Ok(result)
36}
37
38fn decode_hex_digit(byte: u8) -> Result<u8, DecodeError> {
39 match byte {
40 b'0'..=b'9' => Ok(byte - b'0'),
41 b'a'..=b'f' => Ok(byte - b'a' + 10),
42 b'A'..=b'F' => Ok(byte - b'A' + 10),
43 _ => Err(DecodeError::InvalidCharacter(byte as char)),
44 }
45}
46
47#[derive(Debug)]
48pub enum DecodeError {
49 InvalidCharacter(char),
50 OddLength,
51}
52
53impl std::fmt::Display for DecodeError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 DecodeError::InvalidCharacter(ch) => {
57 write!(f, "Invalid hex character: '{}'", ch)
58 }
59 DecodeError::OddLength => {
60 write!(f, "Hex string has odd length")
61 }
62 }
63 }
64}
65
66impl std::error::Error for DecodeError {}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_encode() {
74 assert_eq!(encode(b"Hello"), "48656c6c6f");
75 assert_eq!(encode(b""), "");
76 assert_eq!(encode(&[0x00, 0xFF, 0x42]), "00ff42");
77 }
78
79 #[test]
80 fn test_decode() {
81 assert_eq!(decode("48656c6c6f").unwrap(), b"Hello");
82 assert_eq!(decode("48656C6C6F").unwrap(), b"Hello");
83 assert_eq!(decode("").unwrap(), b"");
84 assert_eq!(decode("00ff42").unwrap(), vec![0x00, 0xFF, 0x42]);
85 }
86
87 #[test]
88 fn test_decode_errors() {
89 assert!(decode("xyz").is_err());
90 assert!(decode("48656c6c6").is_err()); }
92
93 #[test]
94 fn test_roundtrip() {
95 let data = b"Hello, World! \x00\x01\x02\xFF";
96 let encoded = encode(data);
97 let decoded = decode(&encoded).unwrap();
98 assert_eq!(decoded, data);
99 }
100}