Skip to main content

stackforge_core/utils/
hex.rs

1//! Hexdump and hex string utilities.
2
3use std::fmt::Write;
4
5/// Generate a hexdump of bytes in the style of `xxd` or Scapy's hexdump.
6///
7/// # Example
8/// ```
9/// use stackforge_core::hexdump;
10/// let data = b"Hello, World!";
11/// println!("{}", hexdump(data));
12/// ```
13pub fn hexdump(data: &[u8]) -> String {
14    let mut output = String::new();
15    let mut offset = 0;
16
17    for chunk in data.chunks(16) {
18        // Offset
19        write!(output, "{:08x}  ", offset).unwrap();
20
21        // Hex bytes
22        for (i, byte) in chunk.iter().enumerate() {
23            if i == 8 {
24                output.push(' ');
25            }
26            write!(output, "{:02x} ", byte).unwrap();
27        }
28
29        // Padding for incomplete lines
30        if chunk.len() < 16 {
31            for i in chunk.len()..16 {
32                if i == 8 {
33                    output.push(' ');
34                }
35                output.push_str("   ");
36            }
37        }
38
39        // ASCII representation
40        output.push(' ');
41        output.push('|');
42        for byte in chunk {
43            if byte.is_ascii_graphic() || *byte == b' ' {
44                output.push(*byte as char);
45            } else {
46                output.push('.');
47            }
48        }
49        output.push('|');
50        output.push('\n');
51
52        offset += 16;
53    }
54
55    output
56}
57
58/// Generate a compact hex string representation.
59pub fn hexstr(data: &[u8]) -> String {
60    let mut output = String::with_capacity(data.len() * 2);
61    for byte in data {
62        write!(output, "{:02x}", byte).unwrap();
63    }
64    output
65}
66
67/// Generate hex string with separator.
68pub fn hexstr_sep(data: &[u8], sep: &str) -> String {
69    data.iter()
70        .map(|b| format!("{:02x}", b))
71        .collect::<Vec<_>>()
72        .join(sep)
73}
74
75/// Parse a hex string into bytes.
76pub fn parse_hex(s: &str) -> Result<Vec<u8>, String> {
77    let s = s.trim().replace(" ", "").replace(":", "").replace("-", "");
78
79    if s.len() % 2 != 0 {
80        return Err("hex string must have even length".to_string());
81    }
82
83    (0..s.len())
84        .step_by(2)
85        .map(|i| {
86            u8::from_str_radix(&s[i..i + 2], 16)
87                .map_err(|e| format!("invalid hex at position {}: {}", i, e))
88        })
89        .collect()
90}
91
92/// Convert bytes to a pretty-printed representation (like Scapy's show()).
93pub fn pretty_bytes(data: &[u8], indent: usize) -> String {
94    let indent_str = " ".repeat(indent);
95    let mut output = String::new();
96
97    for (i, chunk) in data.chunks(16).enumerate() {
98        if i > 0 {
99            output.push('\n');
100        }
101        output.push_str(&indent_str);
102
103        for byte in chunk {
104            write!(output, "{:02x} ", byte).unwrap();
105        }
106    }
107
108    output
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_hexdump() {
117        let data = b"Hello, World!";
118        let dump = hexdump(data);
119        assert!(dump.contains("48 65 6c 6c")); // "Hell"
120        assert!(dump.contains("|Hello, World!|"));
121    }
122
123    #[test]
124    fn test_hexstr() {
125        let data = [0xde, 0xad, 0xbe, 0xef];
126        assert_eq!(hexstr(&data), "deadbeef");
127        assert_eq!(hexstr_sep(&data, ":"), "de:ad:be:ef");
128    }
129
130    #[test]
131    fn test_parse_hex() {
132        assert_eq!(parse_hex("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
133        assert_eq!(
134            parse_hex("de:ad:be:ef").unwrap(),
135            vec![0xde, 0xad, 0xbe, 0xef]
136        );
137        assert_eq!(
138            parse_hex("de ad be ef").unwrap(),
139            vec![0xde, 0xad, 0xbe, 0xef]
140        );
141        assert!(parse_hex("dea").is_err()); // Odd length
142    }
143}