qdhex/
lib.rs

1//! This crate provides functions to create human readable hex
2//! dumps of binary data.
3//!
4#![no_std]
5const HEX_DIGIT: &[u8; 16] = b"0123456789abcdef";
6
7extern crate alloc;
8use alloc::{
9    string::String,
10    vec::Vec,
11};
12
13
14
15/// Write a simple hex dump of the given data to the given target.
16/// The dump contains pairs of hex digits, separated by spaces, no
17/// line breaks, decorations, etc.
18///
19/// # Examples
20/// ```
21/// let data = [0x00, 0x01, 0x02, 0x03];
22/// let mut target = Vec::new();
23/// qdhex::write_bare_dump_to_vec(&data, &mut target);
24/// assert_eq!(target, b"00 01 02 03");
25/// ```
26pub fn write_bare_dump_to_vec(data: &[u8], target: &mut Vec<u8>) {
27    for byte in data {
28        target.push(HEX_DIGIT[(byte >> 4) as usize]);
29        target.push(HEX_DIGIT[(byte & 0x0f) as usize]);
30        target.push(b' ');
31    }
32
33    target.pop();
34}
35
36/// Create a simple hex dump of the given data. The dump contains pairs of
37/// hex digits, separated by spaces, no line breaks, decorations, etc.
38///
39/// # Examples
40/// ```
41/// let data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
42/// let target = qdhex::bare_dump(&data);
43/// assert_eq!(target, b"00 01 02 03 04 05");
44/// ```
45pub fn bare_dump(data: &[u8]) -> Vec<u8> {
46    let mut target = Vec::with_capacity(data.len() * 3 + 1);
47    write_bare_dump_to_vec(data, &mut target);
48    target
49}
50
51/// Create a simple hex dump of the given data. The dump contains pairs of
52/// hex digits, separated by spaces, no line breaks, decorations, etc.
53///
54/// # Examples
55/// ```
56/// let data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
57/// let target = qdhex::bare_dump_string(&data);
58/// assert_eq!(target, "00 01 02 03 04 05");
59/// ```
60///
61pub fn bare_dump_string(data: &[u8]) -> String {
62    let vec = bare_dump(data);
63    // SAFETY: The dump is always valid UTF-8 since it only contains ASCII characters
64    unsafe { String::from_utf8_unchecked(vec) }
65}
66
67/// Write a formatted multi-line hex dump of the given data to the given target.
68/// Dump lines are prefixed with the given offset.  Each line contains up to 16
69/// bytes, separated by spaces, followed by a space and the ASCII representation.
70///
71/// # Examples
72/// ```
73/// let data = &b"baadfood\xba\xad\xf0\x0dASDFasdf;lkj."[..];
74/// let mut target = Vec::new();
75/// qdhex::write_formatted_dump_to_vec(0x1000, &data, &mut target);
76/// assert_eq!(target, br"1000 62 61 61 64 66 6f 6f 64 ba ad f0 0d 41 53 44 46 baadfood....ASDF
77/// 1010 61 73 64 66 3b 6c 6b 6a 2e                      asdf;lkj.
78/// ");
79/// ```
80pub fn write_formatted_dump_to_vec(offset: u32, data: &[u8], target: &mut Vec<u8>) {
81    let mut line_offset = offset;
82
83    for chunk in data.chunks(16) {
84        // Write the line offset
85        for i in 0..4 {
86            target.push(HEX_DIGIT[((line_offset >> (4 * (3 - i))) & 0x0f) as usize]);
87        }
88        target.push(b' ');
89
90        // Write the hex representation
91        write_bare_dump_to_vec(chunk, target);
92
93        // Pad the last line with spaces
94        for _ in chunk.len()..16 {
95            target.push(b' ');
96            target.push(b' ');
97            target.push(b' ');
98        }
99
100        // Write the ASCII representation
101        target.push(b' ');
102        for byte in chunk {
103            if *byte >= 0x20 && *byte <= 0x7e {
104                target.push(*byte);
105            } else {
106                target.push(b'.');
107            }
108        }
109
110        target.push(b'\n');
111
112        line_offset += 16;
113    }
114}
115
116/// Create a formatted multi-line hex dump of the given data.
117/// Dump lines are prefixed with the given offset.  Each line contains up to 16
118/// bytes, separated by spaces, followed by a space and the ASCII representation.
119///
120/// # Examples
121/// ```
122/// let data = &b"baadfood\xba\xad\xf0\x0dASDFasdf;lkj."[..];
123/// let target = qdhex::formatted_dump(0x1000, &data);
124/// assert_eq!(target, br"1000 62 61 61 64 66 6f 6f 64 ba ad f0 0d 41 53 44 46 baadfood....ASDF
125/// 1010 61 73 64 66 3b 6c 6b 6a 2e                      asdf;lkj.
126/// ");
127/// ```
128pub fn formatted_dump(offset: u32, data: &[u8]) -> Vec<u8> {
129    let lines = (data.len() + 15) / 16;
130    let size = lines * 70;
131    let mut target = Vec::with_capacity(size);
132    write_formatted_dump_to_vec(offset, data, &mut target);
133    target
134}
135
136/// Create a formatted multi-line hex dump of the given data.
137/// Dump lines are prefixed with the given offset.  Each line contains up to 16
138/// bytes, separated by spaces, followed by a space and the ASCII representation.
139///
140/// # Examples
141/// ```
142/// let data = &b"baadfood\xba\xad\xf0\x0dASDFasdf;lkj."[..];
143/// let target = qdhex::formatted_dump_string(0x1000, &data);
144/// assert_eq!(target, "1000 62 61 61 64 66 6f 6f 64 ba ad f0 0d 41 53 44 46 baadfood....ASDF\n1010 61 73 64 66 3b 6c 6b 6a 2e                      asdf;lkj.\n");
145/// ```
146pub fn formatted_dump_string(offset: u32, data: &[u8]) -> String {
147    let vec = formatted_dump(offset, data);
148    // SAFETY: The dump is always valid UTF-8 since it only contains ASCII characters
149    unsafe { String::from_utf8_unchecked(vec) }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_bare_dump() {
158        let data: [u8; 9] = [0x12, 0x34, 0x56, 0x78, 0xab, 0xcd, 0xef, 0xa0, 0x0b];
159        let mut target = Vec::new();
160        write_bare_dump_to_vec(&data, &mut target);
161        assert_eq!(target, b"12 34 56 78 ab cd ef a0 0b");
162
163        let data: &[u8] = &b"Hello, World!"[..];
164        let mut target = Vec::new();
165        write_bare_dump_to_vec(&data, &mut target);
166        assert_eq!(target, b"48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21");
167
168        // Test with empty data
169        let data: &[u8] = &b""[..];
170        let mut target = Vec::new();
171        write_bare_dump_to_vec(&data, &mut target);
172        assert_eq!(target, b"");
173
174        // Test with one byte
175        let data: &[u8] = &b"\xab"[..];
176        let mut target = Vec::new();
177        write_bare_dump_to_vec(&data, &mut target);
178        assert_eq!(target, b"ab");
179
180        // Verify that the target is not cleared
181        let data: &[u8] = &b"\xab\x30"[..];
182        let mut target = b"Hello, World!".to_vec();
183        write_bare_dump_to_vec(&data, &mut target);
184        assert_eq!(target, b"Hello, World!ab 30");
185    }
186}