diff --git a/src/bin/bytediff.rs b/src/bin/bytediff.rs index e63c600..c865f98 100644 --- a/src/bin/bytediff.rs +++ b/src/bin/bytediff.rs @@ -1,7 +1,53 @@ use std::fs::File; use std::io::{prelude::*, BufReader}; +use std::path::PathBuf; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(author, version)] +/// diff binary files +/// +/// bytediff checks whether binary files match, and prints out +/// a list of differences at the byte level if they don't. +struct Cli { + /// Print out a full hexdump of differing bytes, not just a summary of differences + #[arg(short, long)] + detailed: bool, + + /// First file to diff + #[arg(required = true)] + file1: PathBuf, + + /// Second file to diff + #[arg(required = true)] + file2: PathBuf, +} + +#[derive(PartialEq, Eq)] +enum DiffState { + Same, + DifferNonZero, + DifferAZero, + DifferBZero, +} + +fn is_byte_vec_zero(v: &[u8]) -> bool { + v.iter().all(|c| *c == 0u8) +} + +fn compare_byte_vecs(a: &[u8], b: &[u8]) -> DiffState { + if a == b { + DiffState::Same + } else if is_byte_vec_zero(a) { + DiffState::DifferAZero + } else if is_byte_vec_zero(b) { + DiffState::DifferBZero + } else { + DiffState::DifferNonZero + } +} struct ZipLonger { a: A, @@ -83,53 +129,89 @@ fn color_byte_display(this: &[u8], that: &[u8], color: u8) -> String { } fn main() -> Result<()> { - let mut args = std::env::args(); - let name = args - .next() - .ok_or_else(|| anyhow!("Missing executable name in args"))?; - let (a, b) = match (args.next(), args.next()) { - (Some(a), Some(b)) => (a, b), - _ => return Err(anyhow!("Usage: {} file file", name)), - }; + let cli = Cli::parse(); + + let a = cli.file1; + let b = cli.file2; let a_bytes = BufReader::new( - File::open(a.clone()).with_context(|| format!("Failed to open input file {}", a))?, + File::open(a.clone()) + .with_context(|| format!("Failed to open input file {}", a.display()))?, ) .bytes(); let b_bytes = BufReader::new( - File::open(b.clone()).with_context(|| format!("Failed to open input file {}", b))?, + File::open(b.clone()) + .with_context(|| format!("Failed to open input file {}", b.display()))?, ) .bytes(); - let mut in_differing_region = false; + let mut diff_state = DiffState::Same; + let mut region_start: usize = 0; for (i, (rva, rvb)) in ZipLonger::new(Chunks::new(a_bytes, 16), Chunks::new(b_bytes, 16)).enumerate() { let va: Vec = rva .transpose() - .with_context(|| format!("Error reading from input file {}", a))? + .with_context(|| format!("Error reading from input file {}", a.display()))? .unwrap_or_default(); let vb: Vec = rvb .transpose() - .with_context(|| format!("Error reading from input file {}", b))? + .with_context(|| format!("Error reading from input file {}", b.display()))? .unwrap_or_default(); - if va == vb { - if in_differing_region { - in_differing_region = false; - println!("---"); - } - continue; - } - in_differing_region = true; + let new_diff_state = compare_byte_vecs(&va, &vb); - println!( - "{0:08x}: {1}\n{0:08x}: {2}", - i * 16, - color_byte_display(&va, &vb, 31), - color_byte_display(&vb, &va, 32), - ); + if cli.detailed { + if new_diff_state == DiffState::Same { + if diff_state != DiffState::Same { + println!("---"); + } + } else { + println!( + "{0:08x}: {1}\n{0:08x}: {2}", + i * 16, + color_byte_display(&va, &vb, 31), + color_byte_display(&vb, &va, 32), + ); + } + } else { + if new_diff_state != diff_state { + if diff_state != DiffState::Same { + print!("{0:08x} to {1:08x}: ", region_start, i * 16); + } + match diff_state { + DiffState::Same => (), + DiffState::DifferAZero => { + println!("First file is zero bytes"); + } + DiffState::DifferBZero => { + println!("Second file is zero bytes"); + } + DiffState::DifferNonZero => { + println!("Files differ"); + } + } + region_start = i * 16; + } + } + diff_state = new_diff_state; + } + + match diff_state { + DiffState::Same => (), + DiffState::DifferAZero => { + println!("First file is zero bytes from {0:08x} to end", region_start); + } + DiffState::DifferBZero => { + println!( + "Second file is zero bytes from {0:08x} to end", + region_start + ); + } + DiffState::DifferNonZero => { + println!("Files differ from {0:08x} to end", region_start); + } } Ok(())