Skip to main content

idb/util/
hex.rs

1//! Hex dump formatting utilities.
2//!
3//! Helpers for formatting byte offsets, hex values, and producing traditional
4//! hex dump output with offset columns and ASCII sidebars.
5
6/// Format a byte offset as "decimal (0xhex)".
7pub fn format_offset(offset: u64) -> String {
8    format!("{} (0x{:x})", offset, offset)
9}
10
11/// Format a u32 value as hex with 0x prefix.
12pub fn format_hex32(value: u32) -> String {
13    format!("0x{:08x}", value)
14}
15
16/// Format a u64 value as hex with 0x prefix.
17pub fn format_hex64(value: u64) -> String {
18    format!("0x{:016x}", value)
19}
20
21/// Format bytes as a compact hex string (e.g., "4a2f00ff").
22pub fn format_bytes(data: &[u8]) -> String {
23    data.iter().map(|b| format!("{:02x}", b)).collect()
24}
25
26/// Produce a standard hex dump of `data` with the given `base_offset`.
27///
28/// Output format (16 bytes per line):
29/// ```text
30/// 00000000  xx xx xx xx xx xx xx xx  xx xx xx xx xx xx xx xx  |................|
31/// ```
32pub fn hex_dump(data: &[u8], base_offset: u64) -> String {
33    let mut lines = Vec::new();
34
35    for (i, chunk) in data.chunks(16).enumerate() {
36        let offset = base_offset + (i * 16) as u64;
37
38        // Offset column
39        let mut line = format!("{:08x}  ", offset);
40
41        // Hex columns (two groups of 8 bytes separated by extra space)
42        for (j, byte) in chunk.iter().enumerate() {
43            if j == 8 {
44                line.push(' ');
45            }
46            line.push_str(&format!("{:02x} ", byte));
47        }
48
49        // Pad short last line
50        if chunk.len() < 16 {
51            let missing = 16 - chunk.len();
52            for j in 0..missing {
53                if chunk.len() + j == 8 {
54                    line.push(' ');
55                }
56                line.push_str("   ");
57            }
58        }
59
60        // ASCII column
61        line.push(' ');
62        line.push('|');
63        for byte in chunk {
64            if byte.is_ascii_graphic() || *byte == b' ' {
65                line.push(*byte as char);
66            } else {
67                line.push('.');
68            }
69        }
70        // Pad ASCII column for short last line
71        for _ in chunk.len()..16 {
72            line.push(' ');
73        }
74        line.push('|');
75
76        lines.push(line);
77    }
78
79    lines.join("\n")
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_format_bytes() {
88        assert_eq!(format_bytes(&[0x4a, 0x2f, 0x00, 0xff]), "4a2f00ff");
89        assert_eq!(format_bytes(&[]), "");
90        assert_eq!(format_bytes(&[0x00]), "00");
91    }
92
93    #[test]
94    fn test_hex_dump_full_line() {
95        let data: Vec<u8> = (0..16).collect();
96        let output = hex_dump(&data, 0);
97        assert!(output.starts_with("00000000  "));
98        assert!(output.contains("00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f"));
99        assert!(output.contains('|'));
100    }
101
102    #[test]
103    fn test_hex_dump_partial_line() {
104        let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello"
105        let output = hex_dump(&data, 0x100);
106        assert!(output.starts_with("00000100  "));
107        assert!(output.contains("48 65 6c 6c 6f"));
108        assert!(output.contains("|Hello"));
109    }
110
111    #[test]
112    fn test_hex_dump_nonprintable() {
113        let data = vec![0x00, 0x01, 0x7f, 0x80, 0xff];
114        let output = hex_dump(&data, 0);
115        assert!(output.contains("|....."));
116    }
117}