#[cfg(test)]
use std::io::Cursor;
use std::fmt::Write as _;
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use crate::Config;
pub fn run(config: Config) -> Result<(), String> {
match config.reverse {
true => {
reverse_hex_dump(config)?;
}
_ => hex_dump(config)?,
}
Ok(())
}
pub fn hex_dump(config: Config) -> Result<(), String> {
let mut reader = BufReader::new(config.input);
let mut writer = BufWriter::new(config.output);
let cols = config.cols;
let byte_groups = config.byte_groups;
let mut line = String::with_capacity(cols << 3);
let mut buf = vec![0u8; cols];
let mut offset: usize = 0;
loop {
let bytes_read = reader
.read(&mut buf)
.map_err(|err| format!("failed to read from input: {err}"))?;
if bytes_read == 0 {
break;
}
format_hex_dump_line(&mut line, &buf[..bytes_read], offset, cols, byte_groups)?;
writeln!(writer, "{line}").map_err(|err| format!("failed to write to output: {err}"))?;
offset += bytes_read;
line.clear();
}
Ok(())
}
fn format_hex_dump_line(
line: &mut String,
buffer: &[u8],
offset: usize,
cols: usize,
byte_groups: usize,
) -> Result<(), String> {
let bytes_read = buffer.len();
write!(line, "{:08x}: ", offset).map_err(|err| format!("failed to write to line: {err}"))?;
for (i, byte) in buffer.iter().enumerate() {
if i != 0 && i % byte_groups == 0 {
line.push(' ');
}
write!(line, "{:02x}", *byte).map_err(|err| format!("failed to write to line: {err}"))?;
}
if bytes_read < cols {
let padding = (cols - bytes_read) * 2 + ((cols - bytes_read) / byte_groups);
write!(line, "{:>padding$}", "")
.map_err(|err| format!("failed to write to line: {err}"))?;
}
line.push_str(" ");
line.extend(buffer.iter().map(|&b| match b {
0x20..=0x7e => b as char,
_ => '.',
}));
Ok(())
}
pub fn reverse_hex_dump(config: Config) -> Result<(), String> {
let mut reader = BufReader::new(config.input);
let mut writer = BufWriter::new(config.output);
let mut line = Vec::with_capacity(1024);
let mut buf = String::with_capacity(1024);
loop {
let bytes_read = reader
.read_line(&mut buf)
.map_err(|err| format!("failed to read from input: {err}"))?;
if bytes_read == 0 {
break;
}
format_reverse_hex_dump_line(&mut line, &buf[..bytes_read])?;
writer
.write_all(&line)
.map_err(|err| format!("failed to write to output: {err}"))?;
line.clear();
buf.clear();
}
Ok(())
}
fn format_reverse_hex_dump_line(line: &mut Vec<u8>, buffer: &str) -> Result<(), String> {
let colon_idx = buffer.find(':').ok_or("malformed line: missing ':'")?;
let start = colon_idx + 2;
let end = buffer[start..]
.find(" ")
.ok_or("malformed line: missing double space separator")?
+ start;
if end > buffer.len() {
return Err("malformed line: line too short".into());
}
let hex = &buffer[start..end];
let mut chars = hex.chars().filter(|c| !c.is_whitespace());
loop {
let high = match chars.next() {
Some(c) => c,
None => break,
};
let low = chars
.next()
.ok_or("malformed hex: odd number of hex digits")?;
let high_nibble = high
.to_digit(16)
.ok_or("malformed line: invalid hex char")? as u8;
let low_nibble = low.to_digit(16).ok_or("malformed line: invalid hex char")? as u8;
let byte: u8 = (high_nibble << 4) | low_nibble;
line.push(byte);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_missing_colon() {
let input = Cursor::new("00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64\n");
let output = Cursor::new(Vec::new());
let config = Config {
cols: 16,
byte_groups: 2,
reverse: true,
input: Box::new(input),
output: Box::new(output),
};
let result = reverse_hex_dump(config);
assert!(result.is_err());
assert!(result.unwrap_err().contains("missing ':'"));
}
#[test]
fn test_missing_double_space_separator() {
let input = Cursor::new("00000000: 48 65 6c 6c 6f 20 776f726c64\n");
let output = Cursor::new(Vec::new());
let config = Config {
cols: 16,
byte_groups: 2,
reverse: true,
input: Box::new(input),
output: Box::new(output),
};
let result = reverse_hex_dump(config);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("missing double space separator")
);
}
#[test]
fn test_line_too_short() {
let input = Cursor::new("00000000: 48\n");
let output = Cursor::new(Vec::new());
let config = Config {
cols: 16,
byte_groups: 2,
reverse: true,
input: Box::new(input),
output: Box::new(output),
};
let result = reverse_hex_dump(config);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("missing double space separator")
);
}
#[test]
fn test_odd_number_of_hex_digits() {
let input = Cursor::new("00000000: 4 8 6 5 6 c 6 c 6 f 2 0 7 7 6 f 7 2 6 c 6 \n");
let output = Cursor::new(Vec::new());
let config = Config {
cols: 16,
byte_groups: 2,
reverse: true,
input: Box::new(input),
output: Box::new(output),
};
let result = reverse_hex_dump(config);
assert!(result.is_err());
assert!(result.unwrap_err().contains("odd number of hex digits"));
}
#[test]
fn test_invalid_hex_character() {
let input = Cursor::new("00000000: 48 65 6c 6c 6f 2G 77 6f 72 6c 64 \n");
let output = Cursor::new(Vec::new());
let config = Config {
cols: 16,
byte_groups: 2,
reverse: true,
input: Box::new(input),
output: Box::new(output),
};
let result = reverse_hex_dump(config);
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid hex char"));
}
}