Skip to main content

pretty_hex/
pretty_hex.rs

1#[cfg(feature = "alloc")]
2use alloc::string::String;
3use 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")]
8pub fn simple_hex<T: AsRef<[u8]>>(source: &T) -> String {
9    let mut writer = 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`.
15pub fn simple_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
16where
17    T: AsRef<[u8]>,
18    W: fmt::Write,
19{
20    hex_write(writer, source, 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")]
26pub fn pretty_hex<T: AsRef<[u8]>>(source: &T) -> String {
27    let mut writer = 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.
34pub fn pretty_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
35where
36    T: AsRef<[u8]>,
37    W: fmt::Write,
38{
39    hex_write(writer, source, HexConfig::default())
40}
41
42/// Return a hexdump of `source` in specified format.
43#[cfg(feature = "alloc")]
44pub fn config_hex<T: AsRef<[u8]>>(source: &T, cfg: HexConfig) -> String {
45    let mut writer = 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)]
52pub 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.
71impl 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
85impl 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
113const NON_ASCII: char = '.';
114
115type AddressWriter = dyn Fn(&mut dyn fmt::Write, usize) -> fmt::Result;
116
117fn get_address_writer(max_addr: usize) -> &'static AddressWriter {
118    match max_addr {
119        0x0000..=0xffff => &|w: &mut dyn fmt::Write, a| write!(w, "{:04x}:   ", a),
120        0x010000..=0xffffff => &|w: &mut dyn fmt::Write, a| write!(w, "{:06x}:   ", a),
121        0x01000000..=0xffffffff => &|w: &mut dyn fmt::Write, a| write!(w, "{:08x}:   ", a),
122        _ => &|w: &mut dyn fmt::Write, a| write!(w, "{:016x}:   ", a),
123    }
124}
125
126/// Write hex dump in specified format.
127pub fn hex_write<T, W>(writer: &mut W, source: &T, cfg: HexConfig) -> fmt::Result
128where
129    T: AsRef<[u8]> + ?Sized,
130    W: fmt::Write,
131{
132    let mut source = source.as_ref();
133    if cfg.title {
134        writeln!(writer, "Length: {0} (0x{0:x}) bytes", source.len())?;
135    }
136
137    if source.is_empty() {
138        return Ok(());
139    }
140
141    let omitted = source
142        .len()
143        .checked_sub(cfg.max_bytes)
144        .and_then(|o| (o > 0).then_some(o));
145    if omitted.is_some() {
146        source = &source[..cfg.max_bytes];
147    }
148    let lines = source.chunks(if cfg.width > 0 {
149        cfg.width
150    } else {
151        source.len()
152    });
153
154    let lines_len = lines.len();
155
156    let max_address = if source.len() <= cfg.width {
157        source.len() + cfg.display_offset
158    } else {
159        source.len() - cfg.width + cfg.display_offset
160    };
161    let write_address = get_address_writer(max_address);
162
163    for (i, row) in lines.enumerate() {
164        if cfg.width > 0 {
165            write_address(writer, i * cfg.width + cfg.display_offset)?;
166        }
167        for (i, x) in row.as_ref().iter().enumerate() {
168            write!(writer, "{}{:02x}", cfg.delimiter(i), x)?;
169        }
170        if cfg.ascii {
171            for j in row.len()..cfg.width {
172                write!(writer, "{}  ", cfg.delimiter(j))?;
173            }
174            write!(writer, "   ")?;
175            for x in row {
176                if x.is_ascii() && !x.is_ascii_control() {
177                    writer.write_char((*x).into())?;
178                } else {
179                    writer.write_char(NON_ASCII)?;
180                }
181            }
182        }
183        if i + 1 < lines_len {
184            writeln!(writer)?;
185        }
186    }
187    if let Some(o) = omitted {
188        write!(writer, "\n... {0} (0x{0:x}) bytes not shown ...", o)?;
189    }
190    Ok(())
191}
192
193/// Reference wrapper for use in arguments formatting.
194pub struct Hex<'a, T: 'a + ?Sized>(&'a T, HexConfig);
195
196impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Display for Hex<'a, T> {
197    /// Formats the value by `simple_hex_write` using the given formatter.
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        hex_write(f, self.0, self.1.to_simple())
200    }
201}
202
203impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Debug for Hex<'a, T> {
204    /// Formats the value by `pretty_hex_write` using the given formatter.
205    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206        hex_write(f, self.0, self.1)
207    }
208}
209
210/// Allows generates hex dumps to a formatter.
211pub trait PrettyHex {
212    /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug`
213    /// formatting as hex dumps.
214    fn hex_dump(&self) -> Hex<Self>;
215
216    /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug`
217    /// formatting as hex dumps in specified format.
218    fn hex_conf(&self, cfg: HexConfig) -> Hex<Self>;
219}
220
221impl<T> PrettyHex for T
222where
223    T: AsRef<[u8]> + ?Sized,
224{
225    fn hex_dump(&self) -> Hex<Self> {
226        Hex(self, HexConfig::default())
227    }
228    fn hex_conf(&self, cfg: HexConfig) -> Hex<Self> {
229        Hex(self, cfg)
230    }
231}