1 use arrayvec::ArrayString;
2 use itertools::Itertools;
3 use std::fmt;
4 use std::fmt::Write;
5 use std::iter;
6 use std::ops;
7 use std::slice;
8
9 const SEGMENT_LENGTH: usize = 4;
10 // CHUNK_LENGTH should be a multiple of SEGMENT_LENGTH
11 const CHUNK_LENGTH: usize = 16;
12
13 const NUM_SEGMENTS_PER_CHUNK: usize = ((CHUNK_LENGTH + SEGMENT_LENGTH - 1) / SEGMENT_LENGTH);
14
15 const BUFFER_LENGTH: usize = 64;
16
17 type BufferImpl = ArrayString<[u8; BUFFER_LENGTH]>;
18
19 /// A single line of hexdump output.
20 ///
21 /// Can be printed using the `{}` (`std::fmt::Display`) formatter.
22 #[derive(Clone)]
23 pub struct Line {
24 inner: BufferImpl,
25 }
26
27 impl Line {
new(inner: BufferImpl) -> Line28 fn new(inner: BufferImpl) -> Line {
29 Line { inner: inner }
30 }
31 }
32
33 impl fmt::Display for Line {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 (**self).fmt(f)
36 }
37 }
38
39 impl fmt::Debug for Line {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 (**self).fmt(f)
42 }
43 }
44
45 impl ops::Deref for Line {
46 type Target = str;
deref(&self) -> &str47 fn deref(&self) -> &str {
48 &self.inner
49 }
50 }
51
52 /// Return type of `hexdump_iter`.
53 pub struct Hexdump<'a> {
54 len: usize,
55 chunks: iter::Enumerate<slice::Chunks<'a, u8>>,
56 summary_done: bool,
57 }
58
59 /// Sanitizes a byte for safe output.
60 ///
61 /// Any printable ASCII character is returned verbatim (including the space
62 /// character `' '`), for all other bytes, an ASCII dot `'.'` is returned.
sanitize_byte(byte: u8) -> char63 pub fn sanitize_byte(byte: u8) -> char {
64 if 0x20 <= byte && byte < 0x7f {
65 byte as char
66 } else {
67 '.'
68 }
69 }
70
71 /// Prints a hexdump of the given bytes to stdout.
hexdump(bytes: &[u8])72 pub fn hexdump(bytes: &[u8]) {
73 hexdump_iter(bytes).foreach(|s| println!("{}", s));
74 }
75
76 /// Creates a hexdump iterator that yields the individual lines.
hexdump_iter(bytes: &[u8]) -> Hexdump77 pub fn hexdump_iter(bytes: &[u8]) -> Hexdump {
78 Hexdump::new(bytes)
79 }
80
81 impl<'a> Hexdump<'a> {
new(bytes: &[u8]) -> Hexdump82 fn new(bytes: &[u8]) -> Hexdump {
83 Hexdump {
84 len: bytes.len(),
85 chunks: bytes.chunks(CHUNK_LENGTH).enumerate(),
86 summary_done: false,
87 }
88 }
89 }
90
once<T,F:FnOnce()->T>(once: &mut bool, f: F) -> Option<T>91 fn once<T,F:FnOnce()->T>(once: &mut bool, f: F) -> Option<T> {
92 if !*once {
93 *once = true;
94 Some(f())
95 } else {
96 None
97 }
98 }
99
100 impl<'a> Iterator for Hexdump<'a> {
101 type Item = Line;
next(&mut self) -> Option<Line>102 fn next(&mut self) -> Option<Line> {
103 let summary_done = &mut self.summary_done;
104 let len = self.len;
105 self.chunks.next().map(hexdump_chunk)
106 .or_else(|| once(summary_done, || hexdump_summary(len)))
107 }
size_hint(&self) -> (usize, Option<usize>)108 fn size_hint(&self) -> (usize, Option<usize>) {
109 (self.len(), Some(self.len()))
110 }
111 }
112
113 impl<'a> DoubleEndedIterator for Hexdump<'a> {
next_back(&mut self) -> Option<Line>114 fn next_back(&mut self) -> Option<Line> {
115 let chunks = &mut self.chunks;
116 let len = self.len;
117 once(&mut self.summary_done, || hexdump_summary(len))
118 .or_else(|| chunks.next_back().map(hexdump_chunk))
119 }
120 }
121
122 impl<'a> ExactSizeIterator for Hexdump<'a> {
len(&self) -> usize123 fn len(&self) -> usize {
124 self.chunks.len() + if !self.summary_done { 1 } else { 0 }
125 }
126 }
127
hexdump_summary(len: usize) -> Line128 fn hexdump_summary(len: usize) -> Line {
129 let mut buf = BufferImpl::new();
130 buf.write_str(" ").unwrap();
131 for _ in 0..CHUNK_LENGTH {
132 buf.write_str(" ").unwrap();
133 }
134 for _ in 1..NUM_SEGMENTS_PER_CHUNK {
135 buf.write_str(" ").unwrap();
136 }
137 write!(buf, "{:08x}", len).unwrap();
138
139 Line::new(buf)
140 }
141
hexdump_chunk((i, chunk): (usize, &[u8])) -> Line142 fn hexdump_chunk((i, chunk): (usize, &[u8])) -> Line {
143 let offset = i * CHUNK_LENGTH;
144
145 let mut buf = BufferImpl::new();
146 buf.write_str("|").unwrap();
147
148 let mut first = true;
149 let mut num_segments = 0;
150 let mut num_bytes = 0;
151 for segment in chunk.chunks(SEGMENT_LENGTH) {
152 if first {
153 first = false;
154 } else {
155 buf.write_str(" ").unwrap();
156 }
157
158 num_bytes = 0;
159 for &b in segment {
160 write!(buf, "{:02x}", b).unwrap();
161 num_bytes += 1;
162 }
163 num_segments += 1;
164 }
165
166 buf.write_str("| ").unwrap();
167 for _ in num_bytes..SEGMENT_LENGTH {
168 buf.write_str(" ").unwrap();
169 }
170 for _ in num_segments..NUM_SEGMENTS_PER_CHUNK {
171 for _ in 0..SEGMENT_LENGTH {
172 buf.write_str(" ").unwrap();
173 }
174 buf.write_str(" ").unwrap();
175 }
176
177 for &b in chunk {
178 write!(buf, "{}", sanitize_byte(b)).unwrap();
179 }
180
181 for _ in chunk.len()..CHUNK_LENGTH {
182 buf.write_str(" ").unwrap();
183 }
184
185 buf.write_str(" ").unwrap();
186 write!(buf, "{:08x}", offset).unwrap();
187
188 Line::new(buf)
189 }
190
191 #[cfg(all(test, feature="nightly-test"))]
192 mod test_nightly {
193 use super::CHUNK_LENGTH;
194 use super::hexdump_iter;
195
196 use itertools::Itertools;
197 use std::collections::HashSet;
198
199 #[quickcheck]
length(bytes: Vec<u8>) -> bool200 fn length(bytes: Vec<u8>) -> bool {
201 let len = hexdump_iter(b"").next().unwrap().len();
202 hexdump_iter(&bytes).all(|s| s.len() == len)
203 }
204
205 #[quickcheck]
ascii_only_no_cc(bytes: Vec<u8>) -> bool206 fn ascii_only_no_cc(bytes: Vec<u8>) -> bool {
207 hexdump_iter(&bytes).all(|s| s.bytes().all(|b| 0x20 <= b && b < 0x7f))
208 }
209
210 #[quickcheck]
summary(bytes: Vec<u8>) -> bool211 fn summary(bytes: Vec<u8>) -> bool {
212 usize::from_str_radix(hexdump_iter(&bytes).last().unwrap().trim(), 16).ok()
213 == Some(bytes.len())
214 }
215
216 #[quickcheck]
chars_existent(bytes: Vec<u8>) -> bool217 fn chars_existent(bytes: Vec<u8>) -> bool {
218 let printable_chars: HashSet<_> = bytes.iter()
219 .filter(|&&b| 0x20 <= b && b < 0x7f)
220 .map(|&b| b as char)
221 .collect();
222 let lines = hexdump_iter(&bytes).map(|l| l.to_owned()).collect_vec();
223 let printed_chars: HashSet<_> = lines.iter()
224 .flat_map(|l| l.chars())
225 .collect();
226
227 printable_chars.is_subset(&printed_chars)
228 }
229
230 #[quickcheck]
line_count(bytes: Vec<u8>) -> bool231 fn line_count(bytes: Vec<u8>) -> bool {
232 let expected = (bytes.len() + CHUNK_LENGTH - 1) / CHUNK_LENGTH + 1;
233 hexdump_iter(&bytes).len() == expected
234 && hexdump_iter(&bytes).count() == expected
235 }
236 }
237
238 #[cfg(test)]
239 mod test {
240 use super::sanitize_byte;
241
242 #[test]
test_sanitize_byte()243 fn test_sanitize_byte() {
244 use num::ToPrimitive;
245 for i in 0..256u16 {
246 let i = i.to_u8().unwrap();
247 assert!(sanitize_byte(i) == '.' || sanitize_byte(i) == i as char);
248 }
249 }
250 }
251