hex_pp/
lib.rs

1#![no_std]
2use core::fmt;
3
4const NON_ASCII: char = '.';
5type AddressWriter = dyn Fn(&mut dyn fmt::Write, usize) -> fmt::Result;
6
7/// Reference wrapper for use in arguments formatting.
8pub struct Hex<'a, T: 'a + ?Sized>(&'a T, HexConfig);
9
10impl<'a, T: 'a + AsRef<[u8]>> fmt::Display for Hex<'a, T> {
11    /// Formats the value by `simple_hex_write` using the given formatter.
12    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    /// Formats the value by `pretty_hex_write` using the given formatter.
19    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        hex_write(f, self.0, self.1)
21    }
22}
23
24/// Configuration parameters for hex dump
25#[derive(Clone, Copy, Debug)]
26pub struct HexConfig {
27    /// Write first line header with data length.
28    pub title: bool,
29    /// Append ASCII representation column.
30    pub ascii: bool,
31    /// Source bytes per row. 0 for single row without address prefix.
32    pub width: usize,
33    /// Chunks count per group. 0 for single group (column).
34    pub group: usize,
35    /// Source bytes per chunk (word). 0 for single word.
36    pub chunk: usize,
37    /// Maximum bytes to print.
38    pub max_bytes: usize,
39    /// Offset added to displayed address prefix.
40    pub display_offset: usize,
41}
42
43/// Default configuration with `title`, `ascii`, 16 source bytes `width` grouped to 4
44/// separate hex bytes. Using `pretty_hex`, `pretty_hex_write` and `fmt::Debug`
45/// implementation.
46impl 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    /// Returns configuration for `simple_hex`, `simple_hex_writer` and `fmt::Display`
62    /// implementation.
63    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
89/// Allows generates hex dumps to a formatter.
90pub trait HexPP {
91    /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug`
92    /// formatting as hex dumps.
93    fn hex_dump(&self) -> Hex<Self>;
94
95    /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug`
96    /// formatting as hex dumps in _specified format_.
97    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
113/// Dump `source` as hex octets in default format wihtout header and ASCII column to the
114/// `writer`.
115pub 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
123/// Write multi-line hexdump in default format complete with addressing, hex digits, and
124/// ASCII representation to the `writer`.
125pub 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
142/// Write hex dump in specified format.
143pub 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}