Skip to main content

rustolio_utils/bytes/
hex.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use bytes::Bytes;
12
13pub fn encode(bytes: impl AsRef<[u8]>) -> String {
14    const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
15
16    let bytes = bytes.as_ref();
17    let mut hex_string = String::with_capacity(bytes.len() * 2);
18
19    for &byte in bytes {
20        hex_string.push(HEX_CHARS[(byte >> 4) as usize] as char);
21        hex_string.push(HEX_CHARS[(byte & 0xF) as usize] as char);
22    }
23    hex_string
24}
25
26pub fn decode(hex: &str) -> Result<Bytes, &'static str> {
27    fn hex_char_to_value(c: u8) -> Result<u8, &'static str> {
28        match c {
29            b'0'..=b'9' => Ok(c - b'0'),
30            b'a'..=b'f' => Ok(c - b'a' + 10),
31            b'A'..=b'F' => Ok(c - b'A' + 10),
32            _ => Err("Invalid hex character"),
33        }
34    }
35
36    let hex = hex.as_bytes();
37
38    // Check if string length is even
39    if !hex.len().is_multiple_of(2) {
40        return Err("Hex string must have even length");
41    }
42
43    let mut bytes = Vec::with_capacity(hex.len() * 2);
44
45    for i in (0..hex.len()).step_by(2) {
46        let high = hex_char_to_value(hex[i])?;
47        let low = hex_char_to_value(hex[i + 1])?;
48        bytes.push((high << 4) | low);
49    }
50
51    Ok(Bytes::from_owner(bytes))
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_to_hex_empty() {
60        let empty = vec![];
61        assert_eq!(encode(empty), "");
62
63        let empty_array: [u8; 0] = [];
64        assert_eq!(encode(empty_array), "");
65    }
66
67    #[test]
68    fn test_to_hex_single_byte() {
69        let single = vec![0x00];
70        assert_eq!(encode(single), "00");
71
72        let single2 = vec![0xFF];
73        assert_eq!(encode(single2), "ff");
74
75        let single3 = vec![0x0A];
76        assert_eq!(encode(single3), "0a");
77
78        let single4 = vec![0xA0];
79        assert_eq!(encode(single4), "a0");
80    }
81
82    #[test]
83    fn test_to_hex_multiple_bytes() {
84        let bytes = vec![0x12, 0x34, 0x56, 0x78];
85        assert_eq!(encode(bytes), "12345678");
86
87        let bytes2 = vec![0xDE, 0xAD, 0xBE, 0xEF];
88        assert_eq!(encode(bytes2), "deadbeef");
89
90        let bytes3 = vec![0x00, 0xFF, 0x80, 0x7F];
91        assert_eq!(encode(bytes3), "00ff807f");
92    }
93
94    #[test]
95    fn test_to_hex_with_arrays() {
96        let array: [u8; 4] = [0x12, 0x34, 0x56, 0x78];
97        assert_eq!(encode(array), "12345678");
98
99        let slice: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
100        assert_eq!(encode(slice), "deadbeef");
101    }
102
103    #[test]
104    fn test_from_hex_empty() {
105        let empty = decode("").unwrap();
106        assert_eq!(empty, vec![]);
107    }
108
109    #[test]
110    fn test_from_hex_single_byte() {
111        let single = decode("00").unwrap();
112        assert_eq!(single, vec![0x00]);
113
114        let single2 = decode("ff").unwrap();
115        assert_eq!(single2, vec![0xFF]);
116
117        let single3 = decode("0a").unwrap();
118        assert_eq!(single3, vec![0x0A]);
119
120        let single4 = decode("A0").unwrap();
121        assert_eq!(single4, vec![0xA0]);
122    }
123
124    #[test]
125    fn test_from_hex_multiple_bytes() {
126        let bytes = decode("12345678").unwrap();
127        assert_eq!(bytes, vec![0x12, 0x34, 0x56, 0x78]);
128
129        let bytes2 = decode("deadbeef").unwrap();
130        assert_eq!(bytes2, vec![0xDE, 0xAD, 0xBE, 0xEF]);
131
132        let bytes3 = decode("00FF807F").unwrap();
133        assert_eq!(bytes3, vec![0x00, 0xFF, 0x80, 0x7F]);
134
135        // Mixed case
136        let bytes4 = decode("DeAdBeEf").unwrap();
137        assert_eq!(bytes4, vec![0xDE, 0xAD, 0xBE, 0xEF]);
138    }
139
140    #[test]
141    fn test_from_hex_odd_length() {
142        let result = decode("123");
143        assert!(result.is_err());
144        assert_eq!(result.err().unwrap(), "Hex string must have even length");
145
146        let result2 = decode("1");
147        assert!(result2.is_err());
148        assert_eq!(result2.err().unwrap(), "Hex string must have even length");
149
150        let result3 = decode("12345");
151        assert!(result3.is_err());
152        assert_eq!(result3.err().unwrap(), "Hex string must have even length");
153    }
154
155    #[test]
156    fn test_from_hex_invalid_characters() {
157        // Invalid character 'g'
158        let result = decode("1g");
159        assert!(result.is_err());
160        assert_eq!(result.err().unwrap(), "Invalid hex character");
161
162        // Invalid character 'z'
163        let result2 = decode("az");
164        assert!(result2.is_err());
165        assert_eq!(result2.err().unwrap(), "Invalid hex character");
166
167        // Invalid character 'G'
168        let result3 = decode("AG");
169        assert!(result3.is_err());
170        assert_eq!(result3.err().unwrap(), "Invalid hex character");
171
172        // Non-hex character
173        let result4 = decode("!@");
174        assert!(result4.is_err());
175        assert_eq!(result4.err().unwrap(), "Invalid hex character");
176
177        // Space character
178        let result5 = decode("12  34");
179        assert!(result5.is_err());
180        assert_eq!(result5.err().unwrap(), "Invalid hex character");
181    }
182
183    #[test]
184    fn test_round_trip() {
185        // Test round trip conversion
186        let original = Bytes::from_static(&[
187            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
188            0xEE, 0xFF,
189        ]);
190        let hex = encode(original.clone());
191        let decoded = decode(&hex).unwrap();
192        assert_eq!(original, decoded);
193
194        // Test with uppercase hex string
195        let uppercase_hex = hex.to_uppercase();
196        let decoded_upper = decode(&uppercase_hex).unwrap();
197        assert_eq!(original, decoded_upper);
198    }
199
200    // #[test]
201    // fn test_round_trip_random() {
202    //     use rand::Rng;
203
204    //     let mut rng = rand::thread_rng();
205
206    //     for _ in 0..100 {
207    //         let length = rng.gen_range(0..100);
208    //         let original = (0..length).map(|_| rng.gen()).collect();
209
210    //         let hex = encode(original);
211    //         let decoded = decode(&hex).unwrap();
212
213    //         assert_eq!(original, decoded, "Failed round trip for length {}", length);
214    //     }
215    // }
216
217    #[test]
218    fn test_edge_cases() {
219        // All zeros
220        let zeros = vec![0u8; 100];
221        let hex = encode(zeros.clone());
222        assert_eq!(hex.len(), 200);
223        assert!(hex.chars().all(|c| c == '0'));
224        let decoded = decode(&hex).unwrap();
225        assert_eq!(zeros, decoded);
226
227        // All Fs
228        let all_fs = vec![0xFFu8; 50];
229        let hex = encode(all_fs.clone());
230        assert_eq!(hex.len(), 100);
231        assert!(hex.chars().all(|c| c == 'f'));
232        let decoded = decode(&hex).unwrap();
233        assert_eq!(all_fs, decoded);
234
235        // Alternating pattern
236        let pattern: Vec<u8> = (0..=255).collect();
237        let hex = encode(pattern.clone());
238        let decoded = decode(&hex).unwrap();
239        assert_eq!(pattern, decoded);
240    }
241}