Skip to content

Instantly share code, notes, and snippets.

@mcastorina
Created October 16, 2022 22:00
Show Gist options
  • Save mcastorina/21ba9d3504cca562d8a056a12dd6caa7 to your computer and use it in GitHub Desktop.
Save mcastorina/21ba9d3504cca562d8a056a12dd6caa7 to your computer and use it in GitHub Desktop.
Hexdump trait
use std::{fmt, io, result};
/// Result of a hexdump.
pub type Result<T> = result::Result<T, io::Error>;
/// Hexdump provides functionality to output a hexdump to a provided io::Write object.
/// The default Write is io::stdout().
///
/// Examples:
/// ```rust
/// "hello world foo bar baz\n".as_bytes().hexdump();
///
/// File::open("/dev/urandom")
/// .expect("it opens")
/// .take(1024)
/// .hexdump();
/// ```
pub trait Hexdump {
fn hexdump_to(&mut self, w: impl io::Write) -> Result<()>;
/// Write the hexdump to stdout and panic on any IO errors.
fn hexdump(&mut self) {
self.hexdump_to(io::stdout()).unwrap()
}
}
/// Implementation of Hexdump for any type that implements io::Read.
/// This implementation (ab)uses the Debug and Display trait to format the output (see HexChunk).
impl<R: io::Read> Hexdump for R {
fn hexdump_to(&mut self, mut w: impl io::Write) -> Result<()> {
// We'll only ever have 0x10 bytes of data at a time.
let mut buffer = [0; 0x10];
let mut total_read = 0;
loop {
let num_read = self.read(&mut buffer)?;
let buffer = &buffer[..num_read];
match num_read {
0 => break,
_ if num_read <= 8 => writeln!(
w,
"{total_read:08x} {chunk:<49} |{chunk:?}|",
chunk = HexChunk(buffer)
)?,
_ if num_read <= 16 => writeln!(
w,
"{total_read:08x} {chunk1} {chunk2:<24} |{chunk1:?}{chunk2:?}|",
chunk1 = HexChunk(&buffer[..8]),
chunk2 = HexChunk(&buffer[8..])
)?,
_ => unreachable!(),
}
total_read += num_read;
}
writeln!(w, "{total_read:08x}")?;
Ok(())
}
}
/// Helper struct for formatting the byte slice.
struct HexChunk<'a>(&'a [u8]);
/// Display prints the bytes in the slice as space separated hex values.
impl<'a> fmt::Display for HexChunk<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
// Generate a String and call it's Display fmt implementation to preserve formatting.
self.0
.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<_>>()
.join(" ")
.fmt(f)
}
}
/// Debug prints the bytes in the slice as characters or '.' if unprintable.
impl<'a> fmt::Debug for HexChunk<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
// Generate a String and call it's Display fmt implementation to preserve formatting.
let s = self
.0
.iter()
.cloned()
.map(|b| if b >= 32 && b <= 126 { b as char } else { '.' })
.collect();
// Explicitly use the Display trait's fmt, otherwise Rust defaults to Debug here.
<String as fmt::Display>::fmt(&s, f)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment