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