diff --git a/src/bin/hexyl.rs b/src/bin/hexyl.rs index 872df04..f59640d 100644 --- a/src/bin/hexyl.rs +++ b/src/bin/hexyl.rs @@ -17,7 +17,7 @@ use thiserror::Error as ThisError; use terminal_size::terminal_size; -use hexyl::{Base, BorderStyle, Input, PrinterBuilder}; +use hexyl::{Base, BorderStyle, Endianness, Input, PrinterBuilder}; const DEFAULT_BLOCK_SIZE: i64 = 512; @@ -174,10 +174,34 @@ fn run() -> Result<()> { .value_name("N") .help( "Number of bytes/octets that should be grouped together. \ - Possible group sizes are 1, 2, 4, 8. The default is 1. \ - '--groupsize can be used as an alias (xxd-compatibility).", + Possible group sizes are 1, 2, 4, 8. The default is 1. You \ + can use the '--endianness' option to control the ordering of \ + the bytes within a group. '--groupsize' can be used as an \ + alias (xxd-compatibility).", ), ) + .arg( + Arg::new("endianness") + .long("endianness") + .num_args(1) + .value_name("FORMAT") + .value_parser(["big", "little"]) + .default_value("big") + .help( + "Whether to print out groups in little-endian or big-endian \ + format. This option only has an effect if the '--group-size' \ + is larger than 1. '-e' can be used as an alias for \ + '--endianness=little'.", + ), + ) + .arg( + Arg::new("little_endian_format") + .short('e') + .action(ArgAction::SetTrue) + .overrides_with("endianness") + .hide(true) + .help("An alias for '--endianness=little'."), + ) .arg( Arg::new("base") .short('b') @@ -425,6 +449,16 @@ fn run() -> Result<()> { ) }; + let little_endian_format = *matches.get_one::("little_endian_format").unwrap(); + let endianness = matches.get_one::("endianness"); + let endianness = match ( + endianness.map(|s| s.as_ref()).unwrap(), + little_endian_format, + ) { + (_, true) | ("little", _) => Endianness::Little, + ("big", _) => Endianness::Big, + _ => unreachable!(), + }; let stdout = io::stdout(); let mut stdout_lock = BufWriter::new(stdout.lock()); @@ -437,6 +471,7 @@ fn run() -> Result<()> { .num_panels(panels) .group_size(group_size) .with_base(base) + .endianness(endianness) .build(); printer.display_offset(skip_offset + display_offset); printer.print_all(&mut reader).map_err(|e| anyhow!(e))?; diff --git a/src/lib.rs b/src/lib.rs index 62d01ce..410a897 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,11 @@ pub enum ByteCategory { NonAscii, } +pub enum Endianness { + Little, + Big, +} + #[derive(PartialEq)] enum Squeezer { Print, @@ -159,6 +164,7 @@ pub struct PrinterBuilder<'a, Writer: Write> { panels: u64, group_size: u8, base: Base, + endianness: Endianness, } impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { @@ -173,6 +179,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { panels: 2, group_size: 1, base: Base::Hexadecimal, + endianness: Endianness::Big, } } @@ -216,6 +223,11 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { self } + pub fn endianness(mut self, endianness: Endianness) -> Self { + self.endianness = endianness; + self + } + pub fn build(self) -> Printer<'a, Writer> { Printer::new( self.writer, @@ -227,6 +239,7 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { self.panels, self.group_size, self.base, + self.endianness, ) } } @@ -254,6 +267,8 @@ pub struct Printer<'a, Writer: Write> { group_size: u8, /// The number of digits used to write the base. base_digits: u8, + /// Whether to show groups in little or big endian format. + endianness: Endianness, } impl<'a, Writer: Write> Printer<'a, Writer> { @@ -267,6 +282,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> { panels: u64, group_size: u8, base: Base, + endianness: Endianness, ) -> Printer<'a, Writer> { Printer { idx: 0, @@ -304,6 +320,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> { Base::Decimal => 3, Base::Hexadecimal => 2, }, + endianness, } } @@ -523,8 +540,26 @@ impl<'a, Writer: Write> Printer<'a, Writer> { Ok(()) } + fn reorder_buffer_to_little_endian(&self, buf: &mut Vec) { + let n = buf.len(); + let group_sz = self.group_size as usize; + + for idx in (0..n).step_by(group_sz) { + let remaining = n - idx; + let total = remaining.min(group_sz); + + buf[idx..idx + total].reverse(); + } + } + pub fn print_bytes(&mut self) -> io::Result<()> { - for (i, &b) in self.line_buf.clone().iter().enumerate() { + let mut buf = self.line_buf.clone(); + + if matches!(self.endianness, Endianness::Little) { + self.reorder_buffer_to_little_endian(&mut buf); + }; + + for (i, &b) in buf.iter().enumerate() { self.print_byte(i, b)?; } Ok(()) @@ -682,6 +717,7 @@ mod tests { 2, 1, Base::Hexadecimal, + Endianness::Big, ); printer.print_all(input).unwrap(); @@ -736,6 +772,7 @@ mod tests { 2, 1, Base::Hexadecimal, + Endianness::Big, ); printer.display_offset(0xdeadbeef); @@ -769,6 +806,7 @@ mod tests { 4, 1, Base::Hexadecimal, + Endianness::Big, ); printer.print_all(input).unwrap(); @@ -828,6 +866,7 @@ mod tests { 3, 1, Base::Hexadecimal, + Endianness::Big, ); printer.print_all(input).unwrap(); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index fda9daf..fc10dfa 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -292,12 +292,12 @@ mod display_settings { } } -mod group { +mod group_and_endianness { use super::hexyl; use super::PrettyAssert; #[test] - fn group_2_bytes() { + fn group_2_bytes_be() { hexyl() .arg("ascii") .arg("--color=never") @@ -312,7 +312,23 @@ mod group { } #[test] - fn group_4_bytes() { + fn group_2_bytes_le() { + hexyl() + .arg("ascii") + .arg("--color=never") + .arg("--group-size=2") + .arg("--endianness=little") + .assert() + .success() + .stdout( + "┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\n\ + │00000000│ 3130 3332 3534 3736 ┊ 3938 6261 6463 0a65 │01234567┊89abcde_│\n\ + └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\n", + ); + } + + #[test] + fn group_4_bytes_be() { hexyl() .arg("ascii") .arg("--color=never") @@ -327,7 +343,23 @@ mod group { } #[test] - fn group_8_bytes() { + fn group_4_bytes_le() { + hexyl() + .arg("ascii") + .arg("--color=never") + .arg("--group-size=4") + .arg("--endianness=little") + .assert() + .success() + .stdout( + "┌────────┬───────────────────┬───────────────────┬────────┬────────┐\n\ + │00000000│ 33323130 37363534 ┊ 62613938 0a656463 │01234567┊89abcde_│\n\ + └────────┴───────────────────┴───────────────────┴────────┴────────┘\n", + ); + } + + #[test] + fn group_8_bytes_be() { hexyl() .arg("ascii") .arg("--color=never") @@ -341,6 +373,22 @@ mod group { ); } + #[test] + fn group_8_bytes_le() { + hexyl() + .arg("ascii") + .arg("--color=never") + .arg("--group-size=8") + .arg("--endianness=little") + .assert() + .success() + .stdout( + "┌────────┬──────────────────┬──────────────────┬────────┬────────┐\n\ + │00000000│ 3736353433323130 ┊ 0a65646362613938 │01234567┊89abcde_│\n\ + └────────┴──────────────────┴──────────────────┴────────┴────────┘\n", + ); + } + #[test] fn group_size_plain() { hexyl()