1#![no_std]
2use core::fmt;
3
4const NON_ASCII: char = '.';
5type AddressWriter = dyn Fn(&mut dyn fmt::Write, usize) -> fmt::Result;
6
7pub struct Hex<'a, T: 'a + ?Sized>(&'a T, HexConfig);
9
10impl<'a, T: 'a + AsRef<[u8]>> fmt::Display for Hex<'a, T> {
11 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13 hex_write(f, self.0, self.1.to_simple())
14 }
15}
16
17impl<'a, T: 'a + AsRef<[u8]>> fmt::Debug for Hex<'a, T> {
18 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20 hex_write(f, self.0, self.1)
21 }
22}
23
24#[derive(Clone, Copy, Debug)]
26pub struct HexConfig {
27 pub title: bool,
29 pub ascii: bool,
31 pub width: usize,
33 pub group: usize,
35 pub chunk: usize,
37 pub max_bytes: usize,
39 pub display_offset: usize,
41}
42
43impl Default for HexConfig {
47 fn default() -> HexConfig {
48 HexConfig {
49 title: true,
50 ascii: true,
51 width: 16,
52 group: 4,
53 chunk: 1,
54 max_bytes: usize::MAX,
55 display_offset: 0,
56 }
57 }
58}
59
60impl HexConfig {
61 pub fn simple() -> Self {
64 HexConfig::default().to_simple()
65 }
66
67 fn delimiter(&self, i: usize) -> &'static str {
68 if i > 0 && self.chunk > 0 && i % self.chunk == 0 {
69 if self.group > 0 && i % (self.group * self.chunk) == 0 {
70 " "
71 } else {
72 " "
73 }
74 } else {
75 ""
76 }
77 }
78
79 fn to_simple(self) -> Self {
80 HexConfig {
81 title: false,
82 ascii: false,
83 width: 0,
84 ..self
85 }
86 }
87}
88
89pub trait HexPP {
91 fn hex_dump(&self) -> Hex<Self>;
94
95 fn hex_conf(&self, cfg: HexConfig) -> Hex<Self>;
98}
99
100impl<T> HexPP for T
101where
102 T: AsRef<[u8]> + ?Sized,
103{
104 fn hex_dump(&self) -> Hex<Self> {
105 Hex(&self, HexConfig::default())
106 }
107
108 fn hex_conf(&self, cfg: HexConfig) -> Hex<Self> {
109 Hex(self, cfg)
110 }
111}
112
113pub fn simple_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
116where
117 T: AsRef<[u8]> + ?Sized,
118 W: fmt::Write,
119{
120 hex_write(writer, source, HexConfig::simple())
121}
122
123pub fn pretty_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
126where
127 T: AsRef<[u8]>,
128 W: fmt::Write,
129{
130 hex_write(writer, source, HexConfig::default())
131}
132
133fn get_address_writer(max_addr: usize) -> &'static AddressWriter {
134 match max_addr {
135 0x0000..=0xFFFF => &|w, a| write!(w, "{:04x}: ", a),
136 0x010000..=0xFFFFFF => &|w, a| write!(w, "{:06x}: ", a),
137 0x01000000..=0xFFFFFFFF => &|w, a| write!(w, "{:08x}: ", a),
138 _ => &|w, a| write!(w, "{:16x}: ", a),
139 }
140}
141
142pub fn hex_write<T, W>(writer: &mut W, source: &T, cfg: HexConfig) -> fmt::Result
144where
145 T: AsRef<[u8]> + ?Sized,
146 W: fmt::Write,
147{
148 let mut source = source.as_ref();
149 if cfg.title {
150 writeln!(writer, "Length: {0} (0x{0:x})", source.len())?;
151 }
152
153 if source.is_empty() {
154 return Ok(());
155 }
156
157 let omitted = source.len().checked_sub(cfg.max_bytes);
158 if omitted.is_some() {
159 source = &source[..cfg.max_bytes]
160 }
161
162 let lines = source.chunks(if cfg.width > 0 {
163 cfg.width
164 } else {
165 source.len()
166 });
167
168 let lines_len = lines.len();
169
170 let max_address = if source.len() <= cfg.width {
171 source.len() + cfg.display_offset
172 } else {
173 source.len() - cfg.width + cfg.display_offset
174 };
175
176 let write_address = get_address_writer(max_address);
177
178 for (i, row) in lines.enumerate() {
179 if cfg.width > 0 {
180 write_address(writer, i * cfg.width + cfg.display_offset)?;
181 }
182
183 for (i, x) in row.as_ref().iter().enumerate() {
184 write!(writer, "{}{:02x}", cfg.delimiter(i), x)?;
185 }
186
187 if cfg.ascii {
188 for j in row.len() .. cfg.width {
189 write!(writer, "{} ", cfg.delimiter(j))?;
190 }
191
192 write!(writer, " ")?;
193 for x in row {
194 if x.is_ascii() && !x.is_ascii_control() {
195 writer.write_char((*x).into())?;
196 } else {
197 writer.write_char(NON_ASCII)?;
198 }
199 }
200 }
201
202 if i + 1 < lines_len {
203 writeln!(writer)?;
204 }
205 }
206
207 if let Some(o) = omitted {
208 write!(writer, "\n...{0} (0x{0:x}) bytes not shown...", o)?;
209 }
210
211 Ok(())
212}