opentitanlib/util/
hexdump.rs1use anyhow::Result;
6use regex::RegexBuilder;
7use std::io::Write;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum HexdumpError {
12 #[error("odd number of input characters")]
13 BadLength,
14 #[error("unrecognized hexdump format")]
15 UnrecognizedFormat,
16}
17
18pub fn hexdump(mut writer: impl Write, buf: &[u8]) -> Result<()> {
28 for (i, chunk) in buf.chunks(16).enumerate() {
29 let mut ascii = [b'.'; 16];
30 write!(writer, "{:08x}:", i * 16)?;
31 for (j, byte) in chunk.iter().copied().enumerate() {
32 write!(writer, " {:02x}", byte)?;
33 if byte == b' ' || byte.is_ascii_graphic() {
35 ascii[j] = byte;
36 }
37 }
38 let j = chunk.len();
40 for _ in 0..(16 - j) {
41 write!(writer, " ")?;
42 }
43 writeln!(writer, " {}", std::str::from_utf8(&ascii[0..j]).unwrap())?;
44 }
45 Ok(())
46}
47
48pub fn hexdump_string(buf: &[u8]) -> Result<String> {
50 let mut s = Vec::new();
51 hexdump(&mut s, buf)?;
52 Ok(String::from_utf8(s)?)
53}
54
55fn unhex(byte: u8) -> Option<u8> {
57 match byte {
58 b'0'..=b'9' => Some(byte - b'0'),
59 b'A'..=b'F' => Some(byte - b'A' + 10),
60 b'a'..=b'f' => Some(byte - b'a' + 10),
61 _ => None,
62 }
63}
64
65fn from_hex(text: &str, vec: &mut Vec<u8>) -> Result<()> {
67 let mut it = text.bytes().filter_map(unhex);
68 while let Some(a) = it.next() {
69 if let Some(b) = it.next() {
70 vec.push(a << 4 | b);
71 } else {
72 return Err(HexdumpError::BadLength.into());
73 }
74 }
75 Ok(())
76}
77
78pub fn hexdump_parse(text: &str) -> Result<Vec<u8>> {
80 let xxd = RegexBuilder::new(r"^[[:xdigit:]]{8}:\s+((?:[[:xdigit:]]{2,}\s)+)\s+.{1,16}$")
82 .multi_line(true)
83 .build()
84 .unwrap();
85 let hexdump =
87 RegexBuilder::new(r"^[[:xdigit:]]{8}\s+((?:[[:xdigit:]]{2}\s+?){1,16})\s+\|.*\|$")
88 .multi_line(true)
89 .build()
90 .unwrap();
91 let hexstr = RegexBuilder::new(r"(?:0[xX])?((?:[[:xdigit:]]{2}\s*)+)")
93 .multi_line(false)
94 .build()
95 .unwrap();
96
97 let mut res = Vec::new();
98 let captures = if xxd.is_match(text) {
99 xxd.captures_iter(text)
100 } else if hexdump.is_match(text) {
101 hexdump.captures_iter(text)
102 } else if hexstr.is_match(text) {
103 hexstr.captures_iter(text)
104 } else {
105 return Err(HexdumpError::UnrecognizedFormat.into());
106 };
107 for c in captures {
108 from_hex(c.get(1).unwrap().as_str(), &mut res)?;
109 }
110 Ok(res)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use anyhow::Result;
117
118 const TEST_STR: &str = "The quick brown fox jumped over the lazy dog!";
119
120 const HEXDUMP_C: &str = "\
12200000000 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 |The quick brown |\n\
12300000010 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 |fox jumped over |\n\
12400000020 74 68 65 20 6c 61 7a 79 20 64 6f 67 21 |the lazy dog!|\n";
125
126 const XXD_G1: &str = "\
12800000000: 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 The quick brown \n\
12900000010: 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 fox jumped over \n\
13000000020: 74 68 65 20 6c 61 7a 79 20 64 6f 67 21 the lazy dog!\n";
131
132 const XXD_G2: &str = "\
13300000000: 5468 6520 7175 6963 6b20 6272 6f77 6e20 The quick brown \n\
13400000010: 666f 7820 6a75 6d70 6564 206f 7665 7220 fox jumped over \n\
13500000020: 7468 6520 6c61 7a79 2064 6f67 21 the lazy dog!\n";
136
137 const XXD_G4: &str = "\
13800000000: 54686520 71756963 6b206272 6f776e20 The quick brown \n\
13900000010: 666f7820 6a756d70 6564206f 76657220 fox jumped over \n\
14000000020: 74686520 6c617a79 20646f67 21 the lazy dog!\n";
141
142 const XXD_G8: &str = "\
14300000000: 5468652071756963 6b2062726f776e20 The quick brown \n\
14400000010: 666f78206a756d70 6564206f76657220 fox jumped over \n\
14500000020: 746865206c617a79 20646f6721 the lazy dog!\n";
146
147 const XXD: [&str; 4] = [XXD_G1, XXD_G2, XXD_G4, XXD_G8];
148
149 #[test]
150 fn test_hexdump() -> Result<()> {
151 let buf = TEST_STR;
152 let res = hexdump_string(buf.as_bytes())?;
153 assert_eq!(res, XXD_G1);
154 Ok(())
155 }
156
157 #[test]
158 fn test_from_hexstr() -> Result<()> {
159 let buf = "5468652071756963\n6b2062726f776e20";
160 let res = hexdump_parse(buf)?;
161 let s = std::str::from_utf8(&res)?;
162 assert_eq!(s, "The quick brown ");
163 Ok(())
164 }
165
166 #[test]
167 fn test_from_hexdump() -> Result<()> {
168 let res = hexdump_parse(HEXDUMP_C)?;
169 let s = std::str::from_utf8(&res)?;
170 assert_eq!(s, TEST_STR);
171 Ok(())
172 }
173
174 #[test]
175 fn test_from_xxd() -> Result<()> {
176 for xxd in &XXD {
177 let res = hexdump_parse(xxd)?;
178 let s = std::str::from_utf8(&res)?;
179 assert_eq!(s, TEST_STR);
180 }
181 Ok(())
182 }
183}