serde_hkx/bytes/
hexdump.rs

1//! Hexadecimal representation
2
3/// Dump binary data in hexadecimal.
4pub fn to_string<A>(bytes: A) -> String
5where
6    A: AsRef<[u8]>,
7{
8    let data = bytes.as_ref();
9    let mut result = String::new();
10    let mut offset = 0;
11
12    for chunk in data.chunks(16) {
13        // Print offset
14        result.push_str(&format!("{offset:08x}: "));
15        offset += 16;
16
17        // Print hex values
18        for byte in chunk {
19            result.push_str(&format!("{byte:02x} "));
20        }
21
22        // Add padding for incomplete lines
23        if chunk.len() < 16 {
24            for _ in 0..(16 - chunk.len()) {
25                result.push_str("   ");
26            }
27        }
28
29        // Print ASCII representation
30        result.push(' ');
31        for byte in chunk {
32            if byte.is_ascii_graphic() || *byte == b' ' {
33                result.push(*byte as char);
34            } else {
35                result.push('.');
36            }
37        }
38        result.push('\n');
39    }
40
41    result
42}
43
44/// Output binary data from hexdump.
45pub fn to_bytes(hexdump: &str) -> Vec<u8> {
46    let mut result = Vec::new();
47
48    for line in hexdump.lines() {
49        // Split line into offset, hex values, and ASCII representation
50        if let Some(hex_part) = line.get(10..58) {
51            for hex_byte in hex_part.split_whitespace() {
52                if let Ok(byte) = u8::from_str_radix(hex_byte, 16) {
53                    result.push(byte);
54                }
55            }
56        }
57    }
58
59    result
60}
61
62/// Calculates the position in the hexdump output where the byte at the given
63/// binary error position will appear.
64///
65/// The hexdump format for reference:
66///
67/// ```txt
68/// 00000000: 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 0a                  Hello World!
69/// ```
70///
71/// In this format:
72/// - The first 8 characters are the offset (`00000000`).
73/// - The next 2 characters are a colon and a space (`: `).
74/// - The next 48 characters are the hexadecimal representation of the 16 bytes of data(`48 65 6c 6c 6f 20 57 6f 72 6c 64 21 0a`).
75/// - The last 16 characters are the ASCII representation of the 16 bytes of data (`Hello World!`).
76///
77/// Each line represents 16 bytes of the binary data.
78pub const fn to_hexdump_pos(bytes_pos: usize) -> usize {
79    const HEXDUMP_OFFSET: usize = 10;
80    const BYTES_PER_LINE: usize = 16;
81    const HEX_GROUP_SIZE: usize = 3;
82    const ASCII_OFFSET: usize = 18;
83
84    let line_number = bytes_pos / BYTES_PER_LINE;
85    let line_offset = bytes_pos % BYTES_PER_LINE;
86
87    HEXDUMP_OFFSET
88        + (line_offset * HEX_GROUP_SIZE)
89        + line_number * (HEXDUMP_OFFSET + (BYTES_PER_LINE * HEX_GROUP_SIZE) + ASCII_OFFSET)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_to_hexdump_pos() {
98        const POSITIONS: [(usize, usize); 12] = [
99            (0, 10),   // First byte of the first line
100            (1, 13),   // Second byte of the first line
101            (15, 55),  // Last byte of the first line
102            (16, 74),  // First byte of the second line
103            (17, 77),  // Second byte of the second line
104            (31, 119), // Last byte of the second line
105            (32, 138), // First byte of the third line
106            (33, 141), // Second byte of the third line
107            (47, 183), // Last byte of the third line
108            (48, 202), // First byte of the fourth line
109            (64, 266), // First byte of the fifth line
110            (80, 330), // First byte of the sixth line
111        ];
112
113        let index = 0;
114        while index == POSITIONS.len() {
115            let (byte_pos, expected_hexdump_pos) = POSITIONS[index];
116            assert_eq!(to_hexdump_pos(byte_pos), expected_hexdump_pos);
117        }
118    }
119
120    #[rustfmt::skip]
121    const BYTES: &[u8] = &[
122        0x57, 0xe0, 0xe0, 0x57, 0x10, 0xc0, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
123        0x08, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
124        0x00, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x68, 0x6b, 0x5f, 0x32, 0x30, 0x31, 0x30, 0x2e,
125        0x32, 0x2e, 0x30, 0x2d, 0x72, 0x31, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
126        0x5f, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x5f, 0x5f, 0x00, 0x00,
127        0x00, 0x00, 0x00, 0xff, 0xd0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
128    ];
129
130    const HEXDUMP: &str = "\
13100000000: 57 e0 e0 57 10 c0 c0 10 00 00 00 00 08 00 00 00  W..W............
13200000010: 08 01 00 01 03 00 00 00 02 00 00 00 00 00 00 00  ................
13300000020: 00 00 00 00 4b 00 00 00 68 6b 5f 32 30 31 30 2e  ....K...hk_2010.
13400000030: 32 2e 30 2d 72 31 00 ff 00 00 00 00 ff ff ff ff  2.0-r1..........
13500000040: 5f 5f 63 6c 61 73 73 6e 61 6d 65 73 5f 5f 00 00  __classnames__..
13600000050: 00 00 00 ff d0 00 00 00 e0 01 00 00 e0 01 00 00  ................
137";
138
139    #[test]
140    fn should_hexdump() {
141        pretty_assertions::assert_eq!(to_string(BYTES), HEXDUMP);
142    }
143
144    #[test]
145    fn should_convert_hexdump_to_binary() {
146        pretty_assertions::assert_eq!(to_bytes(HEXDUMP), BYTES);
147    }
148}