opentitanlib/util/
hexdump.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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
18/// Print a hexdump of a buffer to `writer`.
19/// The hexdump includes the offset, hex bytes and printable ASCII characters.
20///
21///  00000000: 53 46 44 50 06 01 02 ff 00 06 01 10 30 00 00 ff  SFDP........0...
22///  00000010: c2 00 01 04 10 01 00 ff 84 00 01 02 c0 00 00 ff  ................
23///  00000020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................
24///  00000030: e5 20 fb ff ff ff ff 3f 44 eb 08 6b 08 3b 04 bb  . .....?D..k.;..
25///
26/// Note: This format can be consumed by `xxd -r` and converted back into binary.
27pub 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            // For printable ASCII chars, place them in the ascii buffer.
34            if byte == b' ' || byte.is_ascii_graphic() {
35                ascii[j] = byte;
36            }
37        }
38        // Align and print the ascii buffer.
39        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
48/// Print a hexdump of a buffer to a string.
49pub 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
55// Translate an ASCII byte into its hex numerical value.
56fn 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
65// Given a hex string, parse hex bytes and append them to `vec`.
66fn 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
78/// Parses a hexdump string in a variety of forms, returning the resulting bytes.
79pub fn hexdump_parse(text: &str) -> Result<Vec<u8>> {
80    // Detects `xxd -g<n>` formats.
81    let xxd = RegexBuilder::new(r"^[[:xdigit:]]{8}:\s+((?:[[:xdigit:]]{2,}\s)+)\s+.{1,16}$")
82        .multi_line(true)
83        .build()
84        .unwrap();
85    // Detects `hexdump -vC`
86    let hexdump =
87        RegexBuilder::new(r"^[[:xdigit:]]{8}\s+((?:[[:xdigit:]]{2}\s+?){1,16})\s+\|.*\|$")
88            .multi_line(true)
89            .build()
90            .unwrap();
91    // Detects a simple hex string with optional whitespace.
92    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    // Output from `hexdump -vC ...`
121    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    // Output from `xxd -g<n> ...` where n = {1,2,4,8}
127    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}