use crate::options::HhhArgs;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::stdin;
use std::io::BufWriter;
use std::io::Read;
use std::io::Result;
use std::io::Write;
use std::rc::Rc;
pub fn write_hexdump<W: Write + 'static>(
args: HhhArgs,
map: BTreeMap<String, String>,
sink: W,
) -> Result<()> {
let mut sink = sink;
for (key, value) in map.into_iter() {
if !value.is_empty() {
let _ = writeln!(sink, "// {}: {}", key, value);
}
}
let mut generator = Generator::new(sink);
generator.config(&args);
generator.set_offset(args.start as usize);
let mut offset = 0;
let start = args.start;
let stop = if args.count == 0 {
u64::MAX
} else {
args.start + args.count
};
let mut push = |bytes: Vec<u8>| {
let len = bytes.len();
let begin = (start.saturating_sub(offset) as usize).min(len);
let end = (stop.saturating_sub(offset) as usize).min(len);
generator.add(&bytes[begin..end]);
offset += len as u64;
};
if args.files.is_empty() {
let mut bytes = vec![];
stdin().read_to_end(&mut bytes)?;
push(bytes);
}
for path in args.files {
let mut file = File::open(path)?;
let mut bytes = vec![];
file.read_to_end(&mut bytes)?;
push(bytes);
}
generator.finish();
Ok(())
}
#[derive(Clone)]
pub struct Generator {
offset: usize,
bytes: Vec<u8>,
total: usize,
ascii_adjust: u8,
pub bytes_per_group: u16,
pub groups_per_line: u16,
pub bias: i64,
pub little_endian: bool,
pub uppercase: bool,
pub separator: String,
pub show_offset: bool,
pub offset_width: u8,
pub show_ascii: bool,
pub skip_zero: bool,
pub radix_prefixes: bool,
pub target: Rc<RefCell<dyn Write>>,
}
impl Generator {
pub fn new<W: Write + 'static>(target: W) -> Self {
let mut instance = Self::default();
let buf = BufWriter::new(target);
instance.target = Rc::new(RefCell::new(buf));
instance
}
pub fn config(&mut self, options: &HhhArgs) {
self.bytes_per_group = options.bytes_per_group;
self.groups_per_line = options.groups_per_line;
self.bias = options.bias;
self.little_endian = options.little_endian;
self.separator = options.group_separator.to_owned();
self.show_ascii = !options.no_ascii;
self.show_offset = !options.no_offset;
self.skip_zero = options.skip_zeros;
self.radix_prefixes = options.radix_prefixes;
self.offset_width = options.offset_width;
self.uppercase = options.uppercase;
self.ascii_adjust = if self.uppercase { b'A' } else { b'a' };
self.total = self.bytes_per_group as usize * self.groups_per_line as usize;
}
pub fn set_offset(&mut self, offset: usize) {
let value = match (offset as i64).checked_add(self.bias) {
Some(value) => {
if value < 0 {
0
} else {
value as usize
}
}
None => 0,
};
self.offset = value;
}
pub fn add(&mut self, bytes: &[u8]) {
self.bytes.extend_from_slice(bytes);
}
#[inline]
fn u8_to_hex(&self, byte: u8) -> [u8; 2] {
let low = byte & 0xf;
let high = byte >> 4;
[
if high > 9 {
high + self.ascii_adjust - 10
} else {
high + b'0'
},
if low > 9 {
low + self.ascii_adjust - 10
} else {
low + b'0'
},
]
}
#[inline]
fn usize_to_padded_hex(&self, value: u64, padding: u8) -> Vec<u8> {
let mut top = 0;
let mut bytes = [b'0'; 16];
bytes[0] = (value & 0xf) as u8;
bytes[1] = ((value >> 4) & 0xf) as u8;
if bytes[1] != b'\0' {
top = 1
}
bytes[2] = ((value >> 8) & 0xf) as u8;
if bytes[2] != b'\0' {
top = 2
}
bytes[3] = ((value >> 12) & 0xf) as u8;
if bytes[3] != b'\0' {
top = 3
}
bytes[4] = ((value >> 16) & 0xf) as u8;
if bytes[4] != b'\0' {
top = 4
}
bytes[5] = ((value >> 20) & 0xf) as u8;
if bytes[5] != b'\0' {
top = 5
}
bytes[6] = ((value >> 24) & 0xf) as u8;
if bytes[6] != b'\0' {
top = 6
}
bytes[7] = ((value >> 28) & 0xf) as u8;
if bytes[7] != b'\0' {
top = 7
}
bytes[8] = ((value >> 32) & 0xf) as u8;
if bytes[8] != b'\0' {
top = 8
}
bytes[9] = ((value >> 36) & 0xf) as u8;
if bytes[9] != b'\0' {
top = 9
}
bytes[10] = ((value >> 40) & 0xf) as u8;
if bytes[10] != b'\0' {
top = 10
}
bytes[11] = ((value >> 44) & 0xf) as u8;
if bytes[11] != b'\0' {
top = 11
}
bytes[12] = ((value >> 48) & 0xf) as u8;
if bytes[12] != b'\0' {
top = 12
}
bytes[13] = ((value >> 52) & 0xf) as u8;
if bytes[13] != b'\0' {
top = 13
}
bytes[14] = ((value >> 56) & 0xf) as u8;
if bytes[14] != b'\0' {
top = 14
}
bytes[15] = ((value >> 60) & 0xf) as u8;
if bytes[15] != b'\0' {
top = 15
}
let mut ret = vec![];
for _ in top + 1..padding {
ret.push(b'0')
}
for index in (0..=top).rev() {
let b0 = bytes[index as usize];
ret.push(if b0 > 9 {
b0 + self.ascii_adjust - 10
} else {
b0 + b'0'
});
}
ret
}
fn write(&mut self) {
if self.bytes.is_empty() {
return;
}
let little_endian = self.little_endian && (!self.radix_prefixes);
let mut target = self.target.borrow_mut();
let sep_value = self.separator.as_bytes();
let sep_space = (0..self.separator.len()).map(|_| b' ').collect::<Vec<_>>();
let mut offset = 0;
let limit = self.bytes.len();
while offset < limit {
let start = offset;
let end = limit.min(start + self.total);
offset += self.total;
if self.skip_zero {
let mut nonzero = false;
for index in start..end {
nonzero |= self.bytes[index] != b'\0';
}
if !nonzero {
continue;
}
}
if self.show_offset {
if self.radix_prefixes {
let _ = target.write(b"0x");
}
let _ = target.write(
&self.usize_to_padded_hex((self.offset + start) as u64, self.offset_width),
);
let _ = target.write(b": ");
}
for group_index in 0..self.groups_per_line {
let group_start = start + group_index as usize * self.bytes_per_group as usize;
if group_index > 0 {
if group_start >= self.bytes.len() {
let _ = target.write(&sep_space);
} else {
let _ = target.write(sep_value);
}
}
for byte_index in 0..self.bytes_per_group {
let position = group_start
+ if little_endian {
self.bytes_per_group - byte_index - 1
} else {
byte_index
} as usize;
let _ = if position >= self.bytes.len() {
if byte_index == 0 && self.radix_prefixes {
let _ = target.write(b" ");
}
target.write(b" ")
} else {
if byte_index == 0 && self.radix_prefixes {
let _ = target.write(b"0x");
}
target.write(&self.u8_to_hex(self.bytes[position]))
};
}
}
if self.show_ascii {
let preview = (start..end)
.map(|index| {
if self.bytes[index].is_ascii_graphic() {
self.bytes[index]
} else {
b'.'
}
})
.collect::<Vec<_>>();
let _ = target.write(b" // ");
let _ = target.write(&preview);
}
let _ = target.write(b"\n");
}
self.bytes.clear();
let _ = target.flush();
}
pub fn finish(&mut self) {
if !self.bytes.is_empty() {
self.write()
}
}
}
impl Default for Generator {
fn default() -> Self {
Self {
offset: 0usize,
bytes: vec![],
total: 16,
bytes_per_group: 1,
groups_per_line: 16,
bias: 0,
little_endian: false,
uppercase: false,
separator: " ".to_string(),
show_offset: true,
offset_width: 8,
show_ascii: true,
skip_zero: false,
radix_prefixes: false,
ascii_adjust: b'a',
target: Rc::new(RefCell::new(std::io::stdout())),
}
}
}
impl Drop for Generator {
fn drop(&mut self) {
self.finish();
}
}
#[cfg(test)]
mod test {
use crate::options::HhhArgs;
use crate::write_hexdump;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::io::{Result, Write};
use std::path::PathBuf;
use super::Generator;
struct Holder {
data: std::rc::Rc<RefCell<Vec<u8>>>,
}
impl Write for Holder {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let size = buf.len();
self.data.borrow_mut().extend_from_slice(buf);
Ok(size)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
#[test]
fn write_hexdump_test_1() -> Result<()> {
let mut config = HhhArgs::default();
config.count = 1000;
config.skip_zeros = true;
config.files.push(PathBuf::from("test_files/simple.bin"));
let map = BTreeMap::from([
("Project".to_string(), "Orion".to_string()),
("Place".to_string(), "Here".to_string()),
]);
let output = std::rc::Rc::new(RefCell::new(vec![]));
let holder = Holder {
data: output.clone(),
};
write_hexdump(config, map, holder)?;
let result = output.take();
assert_eq!(
b"\
// Place: Here\n\
// Project: Orion\n\
00000000: a4 21 41 3b fe cf 08 7c de af 67 ea // .!A;...|..g.\n\
"
.to_vec(),
result
);
Ok(())
}
#[test]
fn write_hexdump_test_2() -> Result<()> {
let mut config = HhhArgs::default();
config.count = 1000;
config.radix_prefixes = true;
config.no_ascii = true;
config.files.push(PathBuf::from("test_files/simple.bin"));
let map = BTreeMap::from([
("Project".to_string(), "Orion".to_string()),
("Place".to_string(), "Here".to_string()),
]);
let output = std::rc::Rc::new(RefCell::new(vec![]));
let holder = Holder {
data: output.clone(),
};
write_hexdump(config, map, holder)?;
let result = output.take();
assert_eq!(b"\
// Place: Here\n\
// Project: Orion\n\
0x00000000: 0xa4 0x21 0x41 0x3b 0xfe 0xcf 0x08 0x7c 0xde 0xaf 0x67 0xea \n".to_vec(), result);
Ok(())
}
#[test]
fn write_hexdump_test_3() -> Result<()> {
let mut config = HhhArgs::default();
config.count = 1000;
config.skip_zeros = true;
config
.files
.push(PathBuf::from("test_files/lots_of_zeros.bin"));
let map = BTreeMap::new();
let output = std::rc::Rc::new(RefCell::new(vec![]));
let holder = Holder {
data: output.clone(),
};
write_hexdump(config, map, holder)?;
let result = output.take();
dbg!(std::str::from_utf8(&result).unwrap());
assert_eq!(
b"\
00000000: ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // ................\n\
00000100: ff // .\n"
.to_vec(),
result
);
Ok(())
}
#[test]
fn set_offset_test() {
let output = vec![];
let mut generator = Generator::new(output);
generator.set_offset(0);
assert_eq!(generator.offset, 0);
generator.bias = -1000;
generator.set_offset(100);
assert_eq!(generator.offset, 0);
generator.bias = 0x7fffffff_ffffffff;
generator.set_offset(0x7fffffff_ffffffff);
assert_eq!(generator.offset, 0);
}
#[test]
fn usize_to_padded_hex_test() {
let output = vec![];
let generator = Generator::new(output);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00000000, 16),
b"0000000000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00000001, 16),
b"0000000000000001"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00000010, 16),
b"0000000000000010"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00000100, 16),
b"0000000000000100"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00001000, 16),
b"0000000000001000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00010000, 16),
b"0000000000010000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_00100000, 16),
b"0000000000100000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_01000000, 16),
b"0000000001000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000000_10000000, 16),
b"0000000010000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000001_00000000, 16),
b"0000000100000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000010_00000000, 16),
b"0000001000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00000100_00000000, 16),
b"0000010000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00001000_00000000, 16),
b"0000100000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00010000_00000000, 16),
b"0001000000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x00100000_00000000, 16),
b"0010000000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x01000000_00000000, 16),
b"0100000000000000"
);
assert_eq!(
generator.usize_to_padded_hex(0x10000000_00000000, 16),
b"1000000000000000"
);
}
}