1 | #[cfg (feature = "alloc" )] |
2 | use alloc::string::String; |
3 | use core::{default::Default, fmt}; |
4 | |
5 | /// Returns a one-line hexdump of `source` grouped in default format without header |
6 | /// and ASCII column. |
7 | #[cfg (feature = "alloc" )] |
8 | pub fn simple_hex<T: AsRef<[u8]>>(source: &T) -> String { |
9 | let mut writer: String = String::new(); |
10 | hex_write(&mut writer, source, HexConfig::simple()).unwrap_or(()); |
11 | writer |
12 | } |
13 | |
14 | /// Dump `source` as hex octets in default format without header and ASCII column to the `writer`. |
15 | pub fn simple_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result |
16 | where |
17 | T: AsRef<[u8]>, |
18 | W: fmt::Write, |
19 | { |
20 | hex_write(writer, source, cfg:HexConfig::simple()) |
21 | } |
22 | |
23 | /// Return a multi-line hexdump in default format complete with addressing, hex digits, |
24 | /// and ASCII representation. |
25 | #[cfg (feature = "alloc" )] |
26 | pub fn pretty_hex<T: AsRef<[u8]>>(source: &T) -> String { |
27 | let mut writer: String = String::new(); |
28 | hex_write(&mut writer, source, HexConfig::default()).unwrap_or(()); |
29 | writer |
30 | } |
31 | |
32 | /// Write multi-line hexdump in default format complete with addressing, hex digits, |
33 | /// and ASCII representation to the writer. |
34 | pub fn pretty_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result |
35 | where |
36 | T: AsRef<[u8]>, |
37 | W: fmt::Write, |
38 | { |
39 | hex_write(writer, source, cfg:HexConfig::default()) |
40 | } |
41 | |
42 | /// Return a hexdump of `source` in specified format. |
43 | #[cfg (feature = "alloc" )] |
44 | pub fn config_hex<T: AsRef<[u8]>>(source: &T, cfg: HexConfig) -> String { |
45 | let mut writer: String = String::new(); |
46 | hex_write(&mut writer, source, cfg).unwrap_or(()); |
47 | writer |
48 | } |
49 | |
50 | /// Configuration parameters for hexdump. |
51 | #[derive (Clone, Copy, Debug)] |
52 | pub struct HexConfig { |
53 | /// Write first line header with data length. |
54 | pub title: bool, |
55 | /// Append ASCII representation column. |
56 | pub ascii: bool, |
57 | /// Source bytes per row. 0 for single row without address prefix. |
58 | pub width: usize, |
59 | /// Chunks count per group. 0 for single group (column). |
60 | pub group: usize, |
61 | /// Source bytes per chunk (word). 0 for single word. |
62 | pub chunk: usize, |
63 | /// Maximum bytes to print. |
64 | pub max_bytes: usize, |
65 | /// Offset added to displayed address prefix |
66 | pub display_offset: usize, |
67 | } |
68 | |
69 | /// Default configuration with `title`, `ascii`, 16 source bytes `width` grouped to 4 separate |
70 | /// hex bytes. Using in `pretty_hex`, `pretty_hex_write` and `fmt::Debug` implementation. |
71 | impl Default for HexConfig { |
72 | fn default() -> HexConfig { |
73 | HexConfig { |
74 | title: true, |
75 | ascii: true, |
76 | width: 16, |
77 | group: 4, |
78 | chunk: 1, |
79 | max_bytes: usize::MAX, |
80 | display_offset: 0, |
81 | } |
82 | } |
83 | } |
84 | |
85 | impl HexConfig { |
86 | /// Returns configuration for `simple_hex`, `simple_hex_write` and `fmt::Display` implementation. |
87 | pub fn simple() -> Self { |
88 | HexConfig::default().to_simple() |
89 | } |
90 | |
91 | fn delimiter(&self, i: usize) -> &'static str { |
92 | if i > 0 && self.chunk > 0 && i % self.chunk == 0 { |
93 | if self.group > 0 && i % (self.group * self.chunk) == 0 { |
94 | " " |
95 | } else { |
96 | " " |
97 | } |
98 | } else { |
99 | "" |
100 | } |
101 | } |
102 | |
103 | fn to_simple(self) -> Self { |
104 | HexConfig { |
105 | title: false, |
106 | ascii: false, |
107 | width: 0, |
108 | ..self |
109 | } |
110 | } |
111 | } |
112 | |
113 | const NON_ASCII: char = '.' ; |
114 | |
115 | |
116 | type AddressWriter = dyn Fn(&mut dyn fmt::Write, usize) -> fmt::Result; |
117 | |
118 | fn get_address_writer(max_addr: usize) -> &'static AddressWriter{ |
119 | match max_addr { |
120 | 0x0000..=0xffff => &|w: &mut dyn fmt::Write, a: usize| write!(w, " {:04x}: " , a), |
121 | 0x010000..=0xffffff => &|w: &mut dyn fmt::Write, a: usize| write!(w, " {:06x}: " , a), |
122 | 0x01000000..=0xffffffff => &|w: &mut dyn fmt::Write, a: usize| write!(w, " {:08x}: " , a), |
123 | _ => &|w: &mut dyn fmt::Write, a: usize| write!(w, " {:016x}: " , a) |
124 | } |
125 | } |
126 | |
127 | /// Write hex dump in specified format. |
128 | pub fn hex_write<T, W>(writer: &mut W, source: &T, cfg: HexConfig) -> fmt::Result |
129 | where |
130 | T: AsRef<[u8]> + ?Sized, |
131 | W: fmt::Write, |
132 | { |
133 | let mut source = source.as_ref(); |
134 | if cfg.title { |
135 | writeln!(writer, "Length: {0} (0x {0:x}) bytes" , source.len())?; |
136 | } |
137 | |
138 | if source.is_empty() { |
139 | return Ok(()); |
140 | } |
141 | |
142 | let omitted = source.len().checked_sub(cfg.max_bytes); |
143 | if omitted.is_some() { |
144 | source = &source[..cfg.max_bytes]; |
145 | } |
146 | let lines = source.chunks(if cfg.width > 0 { |
147 | cfg.width |
148 | } else { |
149 | source.len() |
150 | }); |
151 | |
152 | let lines_len = lines.len(); |
153 | |
154 | let max_address = if source.len() <= cfg.width { |
155 | source.len() + cfg.display_offset |
156 | } else { |
157 | source.len() - cfg.width + cfg.display_offset |
158 | }; |
159 | let write_address = get_address_writer(max_address); |
160 | |
161 | for (i, row) in lines.enumerate() { |
162 | if cfg.width > 0 { |
163 | write_address(writer, i * cfg.width + cfg.display_offset)?; |
164 | } |
165 | for (i, x) in row.as_ref().iter().enumerate() { |
166 | write!(writer, " {}{:02x}" , cfg.delimiter(i), x)?; |
167 | } |
168 | if cfg.ascii { |
169 | for j in row.len()..cfg.width { |
170 | write!(writer, " {} " , cfg.delimiter(j))?; |
171 | } |
172 | write!(writer, " " )?; |
173 | for x in row { |
174 | if x.is_ascii() && !x.is_ascii_control() { |
175 | writer.write_char((*x).into())?; |
176 | } else { |
177 | writer.write_char(NON_ASCII)?; |
178 | } |
179 | } |
180 | } |
181 | if i + 1 < lines_len { |
182 | writeln!(writer)?; |
183 | } |
184 | } |
185 | if let Some(o) = omitted { |
186 | write!(writer, " \n... {0} (0x {0:x}) bytes not shown..." , o)?; |
187 | } |
188 | Ok(()) |
189 | } |
190 | |
191 | /// Reference wrapper for use in arguments formatting. |
192 | pub struct Hex<'a, T: 'a + ?Sized>(&'a T, HexConfig); |
193 | |
194 | impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Display for Hex<'a, T> { |
195 | /// Formats the value by `simple_hex_write` using the given formatter. |
196 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
197 | hex_write(writer:f, self.0, self.1.to_simple()) |
198 | } |
199 | } |
200 | |
201 | impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Debug for Hex<'a, T> { |
202 | /// Formats the value by `pretty_hex_write` using the given formatter. |
203 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
204 | hex_write(writer:f, self.0, self.1) |
205 | } |
206 | } |
207 | |
208 | /// Allows generates hex dumps to a formatter. |
209 | pub trait PrettyHex { |
210 | /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug` |
211 | /// formatting as hex dumps. |
212 | fn hex_dump(&self) -> Hex<Self>; |
213 | |
214 | /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug` |
215 | /// formatting as hex dumps in specified format. |
216 | fn hex_conf(&self, cfg: HexConfig) -> Hex<Self>; |
217 | } |
218 | |
219 | impl<T> PrettyHex for T |
220 | where |
221 | T: AsRef<[u8]> + ?Sized, |
222 | { |
223 | fn hex_dump(&self) -> Hex<Self> { |
224 | Hex(self, HexConfig::default()) |
225 | } |
226 | fn hex_conf(&self, cfg: HexConfig) -> Hex<Self> { |
227 | Hex(self, cfg) |
228 | } |
229 | } |
230 | |