1use anyhow::{ensure, Context, Result};
2
3pub fn parse_hexdump(s: &str) -> Result<Vec<u8>> {
7 let mut result = Vec::new();
8
9 for (line_number, line) in s.lines().enumerate() {
10 let data_part = line.split('#').next().unwrap_or("");
11 let cleaned: String = data_part.chars().filter(|c| !c.is_whitespace()).collect();
12
13 ensure!(
14 cleaned.len() % 2 == 0,
15 "Non-even number of hex chars detected on line {}.",
16 line_number + 1
17 );
18
19 for i in (0..cleaned.len()).step_by(2) {
20 let byte_str = &cleaned[i..i + 2];
21 let byte = u8::from_str_radix(byte_str, 16)
22 .with_context(|| format!("Invalid hex data on line {}.", line_number + 1))?;
23
24 result.push(byte);
25 }
26 }
27
28 Ok(result)
29}
30
31pub fn print_hexdump(bytes: impl AsRef<[u8]>, line_lengths: impl AsRef<[usize]>) -> String {
33 let line_lengths = line_lengths.as_ref();
34 let mut bytes_iter = bytes.as_ref().iter();
35 let default_line_length = line_lengths
36 .last()
37 .filter(|x| **x != 0)
38 .copied()
39 .unwrap_or(16);
40 let mut line_lengths_iter = line_lengths.iter();
41 let mut output = String::new();
42
43 loop {
44 let line_length = line_lengths_iter
45 .next()
46 .copied()
47 .unwrap_or(default_line_length);
48 if line_length == 0 {
49 output.push('\n');
50 } else {
51 let line: Vec<_> = bytes_iter.by_ref().take(line_length).collect();
52
53 if line.is_empty() {
54 break;
55 }
56
57 for byte in &line {
58 output.push_str(&format!("{:02x} ", byte));
59 }
60 output.pop(); output.push('\n');
62 }
63 }
64
65 output
66}
67
68#[macro_export]
74macro_rules! assert_eq_hex {
75 ($a:expr, $b:expr) => {
76 assert_eq_hex!($a, $b, [])
77 };
78 ($a:expr, $b:expr, $hint:expr) => {
79 let a = $a;
80 let b = $b;
81 let hint = $hint;
82 let ar: &[u8] = a.as_ref();
83 let br: &[u8] = b.as_ref();
84 let hintr: &[usize] = hint.as_ref();
85 if ar != br {
86 panic!(
87 "assertion failed: `(left == right)`\nleft:\n{}\nright:\n{}\n",
88 ::iroh_test::hexdump::print_hexdump(ar, hintr),
89 ::iroh_test::hexdump::print_hexdump(br, hintr),
90 )
91 }
92 };
93}
94
95#[cfg(test)]
96mod tests {
97 use super::{parse_hexdump, print_hexdump};
98
99 #[test]
100 fn test_basic() {
101 let input = r"
102 a1b2 # comment
103 3c4d
104 ";
105 let result = parse_hexdump(input).unwrap();
106 assert_eq!(result, vec![0xa1, 0xb2, 0x3c, 0x4d]);
107 }
108
109 #[test]
110 fn test_upper_case() {
111 let input = r"
112 A1B2 # comment
113 3C4D
114 ";
115 let result = parse_hexdump(input).unwrap();
116 assert_eq!(result, vec![0xa1, 0xb2, 0x3c, 0x4d]);
117 }
118
119 #[test]
120 fn test_mixed_case() {
121 let input = r"
122 a1B2 # comment
123 3C4d
124 ";
125 let result = parse_hexdump(input).unwrap();
126 assert_eq!(result, vec![0xa1, 0xb2, 0x3c, 0x4d]);
127 }
128
129 #[test]
130 fn test_odd_characters() {
131 let input = r"
132 a1b
133 ";
134 let result = parse_hexdump(input);
135 assert!(result.is_err());
136 }
137
138 #[test]
139 fn test_invalid_characters() {
140 let input = r"
141 a1g2 # 'g' is not valid in hex
142 ";
143 let result = parse_hexdump(input);
144 assert!(result.is_err());
145 }
146 #[test]
147 fn test_basic_hexdump() {
148 let data: &[u8] = &[0x1, 0x2, 0x3, 0x4, 0x5];
149 let output = print_hexdump(data, [1, 2]);
150 assert_eq!(output, "01\n02 03\n04 05\n");
151 }
152
153 #[test]
154 fn test_newline_insertion() {
155 let data: &[u8] = &[0x1, 0x2, 0x3, 0x4];
156 let output = print_hexdump(data, [1, 0, 2]);
157 assert_eq!(output, "01\n\n02 03\n04\n");
158 }
159
160 #[test]
161 fn test_indefinite_line_length() {
162 let data: &[u8] = &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];
163 let output = print_hexdump(data, [2, 4]);
164 assert_eq!(output, "01 02\n03 04 05 06\n07 08\n");
165 }
166
167 #[test]
168 fn test_empty_data() {
169 let data: &[u8] = &[];
170 let output = print_hexdump(data, [1, 2]);
171 assert_eq!(output, "");
172 }
173
174 #[test]
175 fn test_zeros_then_default() {
176 let data: &[u8] = &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];
177 let output = print_hexdump(data, [1, 0, 0, 2]);
178 assert_eq!(output, "01\n\n\n02 03\n04 05\n06 07\n08\n");
179 }
180}