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}