Created
October 16, 2022 22:00
-
-
Save mcastorina/21ba9d3504cca562d8a056a12dd6caa7 to your computer and use it in GitHub Desktop.
Hexdump trait
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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