1const FORWARD_HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
18const REVERSE_HEX_CHARS: &[u8; 16] = b"zyxwvutsrqponmlk";
19
20fn forward_hex_value(b: u8) -> Option<u8> {
21 match b {
22 b'0'..=b'9' => Some(b - b'0'),
23 b'a'..=b'f' => Some(b - b'a' + 10),
24 b'A'..=b'F' => Some(b - b'A' + 10),
25 _ => None,
26 }
27}
28
29fn reverse_hex_value(b: u8) -> Option<u8> {
30 match b {
31 b'k'..=b'z' => Some(b'z' - b),
32 b'K'..=b'Z' => Some(b'Z' - b),
33 _ => None,
34 }
35}
36
37pub fn decode_hex(hex: impl AsRef<[u8]>) -> Option<Vec<u8>> {
39 decode_hex_inner(hex.as_ref(), forward_hex_value)
40}
41
42pub fn decode_hex_prefix(hex: impl AsRef<[u8]>) -> Option<(Vec<u8>, bool)> {
45 decode_hex_prefix_inner(hex.as_ref(), forward_hex_value)
46}
47
48pub fn decode_reverse_hex(reverse_hex: impl AsRef<[u8]>) -> Option<Vec<u8>> {
50 decode_hex_inner(reverse_hex.as_ref(), reverse_hex_value)
51}
52
53pub fn decode_reverse_hex_prefix(reverse_hex: impl AsRef<[u8]>) -> Option<(Vec<u8>, bool)> {
56 decode_hex_prefix_inner(reverse_hex.as_ref(), reverse_hex_value)
57}
58
59fn decode_hex_inner(reverse_hex: &[u8], hex_value: impl Fn(u8) -> Option<u8>) -> Option<Vec<u8>> {
60 if !reverse_hex.len().is_multiple_of(2) {
61 return None;
62 }
63 let (decoded, _) = decode_hex_prefix_inner(reverse_hex, hex_value)?;
64 Some(decoded)
65}
66
67fn decode_hex_prefix_inner(
68 reverse_hex: &[u8],
69 hex_value: impl Fn(u8) -> Option<u8>,
70) -> Option<(Vec<u8>, bool)> {
71 let mut decoded = Vec::with_capacity(usize::div_ceil(reverse_hex.len(), 2));
72 let (chunks, remainder) = reverse_hex.as_chunks();
73 for &[hi, lo] in chunks {
74 decoded.push(hex_value(hi)? << 4 | hex_value(lo)?);
75 }
76 if let &[hi] = remainder {
77 decoded.push(hex_value(hi)? << 4);
78 Some((decoded, true))
79 } else {
80 Some((decoded, false))
81 }
82}
83
84pub fn encode_hex(data: &[u8]) -> String {
86 encode_hex_inner(data, FORWARD_HEX_CHARS)
87}
88
89pub fn encode_reverse_hex(data: &[u8]) -> String {
91 encode_hex_inner(data, REVERSE_HEX_CHARS)
92}
93
94fn encode_hex_inner(data: &[u8], chars: &[u8; 16]) -> String {
95 let encoded = data
96 .iter()
97 .flat_map(|b| [chars[usize::from(b >> 4)], chars[usize::from(b & 0xf)]])
98 .collect();
99 String::from_utf8(encoded).unwrap()
100}
101
102pub fn common_hex_len(bytes_a: &[u8], bytes_b: &[u8]) -> usize {
105 std::iter::zip(bytes_a, bytes_b)
106 .enumerate()
107 .find_map(|(i, (a, b))| match a ^ b {
108 0 => None,
109 d if d & 0xf0 == 0 => Some(i * 2 + 1),
110 _ => Some(i * 2),
111 })
112 .unwrap_or_else(|| bytes_a.len().min(bytes_b.len()) * 2)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_forward_hex() {
121 assert_eq!(decode_hex(""), Some(vec![]));
123 assert_eq!(decode_hex_prefix(""), Some((vec![], false)));
124 assert_eq!(encode_hex(b""), "".to_string());
125
126 assert_eq!(decode_hex("0"), None);
128 assert_eq!(decode_hex_prefix("f"), Some((vec![0xf0], true)));
129
130 assert_eq!(
132 decode_hex("0123456789abcDEF"),
133 Some(b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec())
134 );
135 assert_eq!(
136 decode_hex_prefix("0123456789ABCdef"),
137 Some((b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec(), false))
138 );
139 assert_eq!(
140 encode_hex(b"\x01\x23\x45\x67\x89\xab\xcd\xef"),
141 "0123456789abcdef".to_string()
142 );
143
144 assert_eq!(decode_hex("gg"), None);
146 assert_eq!(decode_hex_prefix("gg"), None);
147 }
148
149 #[test]
150 fn test_reverse_hex() {
151 assert_eq!(decode_reverse_hex(""), Some(vec![]));
153 assert_eq!(decode_reverse_hex_prefix(""), Some((vec![], false)));
154 assert_eq!(encode_reverse_hex(b""), "".to_string());
155
156 assert_eq!(decode_reverse_hex("z"), None);
158 assert_eq!(decode_reverse_hex_prefix("k"), Some((vec![0xf0], true)));
159
160 assert_eq!(
162 decode_reverse_hex("zyxwvutsRQPONMLK"),
163 Some(b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec())
164 );
165 assert_eq!(
166 decode_reverse_hex_prefix("ZYXWVUTSrqponmlk"),
167 Some((b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec(), false))
168 );
169 assert_eq!(
170 encode_reverse_hex(b"\x01\x23\x45\x67\x89\xab\xcd\xef"),
171 "zyxwvutsrqponmlk".to_string()
172 );
173
174 assert_eq!(decode_reverse_hex("jj"), None);
176 assert_eq!(decode_reverse_hex_prefix("jj"), None);
177 }
178}