#![deny(
dead_code,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]
extern crate ansi_term;
extern crate clap;
use clap::ArgMatches;
use no_color::is_no_color;
use std::env;
use std::error::Error;
use std::f64;
use std::fs;
use std::io::BufReader;
use std::io::IsTerminal;
use std::io::{self, BufRead, Read, Write};
pub const ARG_COL: &str = "cols";
pub const ARG_LEN: &str = "len";
pub const ARG_FMT: &str = "format";
pub const ARG_INP: &str = "INPUTFILE";
pub const ARG_CLR: &str = "color";
pub const ARG_ARR: &str = "array";
pub const ARG_FNC: &str = "func";
pub const ARG_PLC: &str = "places";
pub const ARG_PFX: &str = "prefix";
const ARGS: [&str; 9] = [
ARG_COL, ARG_LEN, ARG_FMT, ARG_INP, ARG_CLR, ARG_ARR, ARG_FNC, ARG_PLC, ARG_PFX,
];
const DBG: u8 = 0x0;
#[derive(Copy, Clone, Debug)]
pub enum Format {
Octal,
LowerHex,
UpperHex,
Pointer,
Binary,
LowerExp,
UpperExp,
Unknown,
}
impl Format {
fn format(&self, data: u8, prefix: bool) -> String {
if prefix {
match &self {
Self::Octal => format!("{:#06o}", data),
Self::LowerHex => format!("{:#04x}", data),
Self::UpperHex => format!("{:#04X}", data),
Self::Binary => format!("{:#010b}", data),
_ => panic!("format is not implemented for this Format"),
}
.to_string()
} else {
match &self {
Self::Octal => format!("{:04o}", data),
Self::LowerHex => format!("{:02x}", data),
Self::UpperHex => format!("{:02X}", data),
Self::Binary => format!("{:08b}", data),
_ => panic!("format is not implemented for this Format"),
}
.to_string()
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Line {
pub offset: u64,
pub hex_body: Vec<u8>,
pub ascii: Vec<u8>,
pub bytes: u64,
}
impl Line {
pub fn new() -> Line {
Line {
offset: 0x0,
hex_body: Vec::new(),
ascii: Vec::new(),
bytes: 0x0,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Page {
pub offset: u64,
pub body: Vec<Line>,
pub bytes: u64,
}
impl Page {
pub fn new() -> Page {
Page {
offset: 0x0,
body: Vec::new(),
bytes: 0x0,
}
}
}
pub fn offset(b: u64) -> String {
format!("{:#08x}", b)
}
pub fn print_offset(w: &mut impl Write, b: u64) -> io::Result<()> {
write!(w, "{}: ", offset(b))
}
pub fn print_byte(
w: &mut impl Write,
b: u8,
format: Format,
colorize: bool,
prefix: bool,
) -> io::Result<()> {
let fmt_string = format.format(b, prefix);
if colorize {
let color = byte_to_color(b);
write!(
w,
"{} ",
ansi_term::Style::new()
.fg(ansi_term::Color::Fixed(color))
.paint(fmt_string)
)
} else {
write!(w, "{} ", fmt_string)
}
}
pub fn byte_to_color(b: u8) -> u8 {
let mut color: u8 = b;
if color < 1 {
color = 0x16;
}
color
}
pub fn append_ascii(target: &mut Vec<u8>, b: u8, colorize: bool) {
let char = match b > 31 && b < 127 {
true => b as char,
false => '.',
};
if colorize {
let string = ansi_term::Style::new()
.fg(ansi_term::Color::Fixed(byte_to_color(b)))
.paint(char.to_string());
target.extend(format!("{}", string).as_bytes());
} else {
target.extend(format!("{}", char).as_bytes());
}
}
pub fn run(matches: ArgMatches) -> Result<(), Box<dyn Error>> {
let mut column_width: u64 = 10;
let mut truncate_len: u64 = 0x0;
if let Some(len) = matches.get_one::<String>("func") {
let mut p: usize = 4;
if let Some(places) = matches.get_one::<String>("places") {
p = match places.parse::<usize>() {
Ok(p) => p,
Err(e) => {
eprintln!("-p, --places <integer> expected. {:?}", e);
return Err(Box::new(e));
}
}
}
output_function(len.parse::<u64>().unwrap(), p);
} else {
let is_stdin = is_stdin(matches.clone());
let mut buf: Box<dyn BufRead> = if is_stdin.unwrap() {
Box::new(BufReader::new(io::stdin()))
} else {
Box::new(BufReader::new(fs::File::open(
matches.get_one::<String>(ARG_INP).unwrap(),
)?))
};
let mut format_out = Format::LowerHex;
let mut colorize = true;
let mut prefix = true;
if let Some(columns) = matches.get_one::<String>(ARG_COL) {
column_width = match columns.parse::<u64>() {
Ok(column_width) => column_width,
Err(e) => {
eprintln!("-c, --cols <integer> expected. {:?}", e);
return Err(Box::new(e));
}
}
}
if let Some(length) = matches.get_one::<String>(ARG_LEN) {
truncate_len = match length.parse::<u64>() {
Ok(truncate_len) => truncate_len,
Err(e) => {
eprintln!("-l, --len <integer> expected. {:?}", e);
return Err(Box::new(e));
}
}
}
if let Some(format) = matches.get_one::<String>(ARG_FMT) {
match format.as_str() {
"o" => format_out = Format::Octal,
"x" => format_out = Format::LowerHex,
"X" => format_out = Format::UpperHex,
"p" => format_out = Format::Pointer,
"b" => format_out = Format::Binary,
"e" => format_out = Format::LowerExp,
"E" => format_out = Format::UpperExp,
_ => format_out = Format::Unknown,
}
}
if is_no_color() {
colorize = false;
}
if !io::stdout().is_terminal() {
colorize = false;
}
if let Some(color) = matches.get_one::<String>(ARG_CLR) {
colorize = color.parse::<u8>().unwrap() == 1;
}
if let Some(prefix_flag) = matches.get_one::<String>(ARG_PFX) {
prefix = prefix_flag.parse::<u8>().unwrap() == 1;
}
if let Some(array) = matches.get_one::<String>(ARG_ARR) {
output_array(array, buf, truncate_len, column_width)?;
} else {
let mut ascii_line: Line = Line::new();
let mut offset_counter: u64 = 0x0;
let mut byte_column: u64 = 0x0;
let page = buf_to_array(&mut buf, truncate_len, column_width)?;
let stdout = io::stdout();
let mut locked = stdout.lock();
for line in page.body.iter() {
print_offset(&mut locked, offset_counter)?;
for hex in line.hex_body.iter() {
offset_counter += 1;
byte_column += 1;
print_byte(&mut locked, *hex, format_out, colorize, prefix)?;
append_ascii(&mut ascii_line.ascii, *hex, colorize);
}
if byte_column < column_width {
write!(
locked,
"{:<1$}",
"",
5 * (column_width - byte_column) as usize
)?;
}
locked.write_all(ascii_line.ascii.as_slice())?;
writeln!(locked)?;
byte_column = 0x0;
ascii_line = Line::new();
}
if true {
writeln!(locked, " bytes: {}", page.bytes)?;
}
}
}
Ok(())
}
#[allow(clippy::absurd_extreme_comparisons)]
pub fn is_stdin(matches: ArgMatches) -> Result<bool, Box<dyn Error>> {
let mut is_stdin = false;
if let Some(file) = matches.get_one::<String>(ARG_INP) {
if DBG > 0 {
dbg!(file);
}
is_stdin = false;
} else if let Some(nth1) = env::args().nth(1) {
if DBG > 0 {
dbg!(nth1);
}
is_stdin = ARGS.iter().any(|arg| matches.index_of(arg) == Some(2));
} else if !matches.args_present() {
is_stdin = true;
}
if DBG > 0 {
dbg!(is_stdin);
}
Ok(is_stdin)
}
pub fn output_array(
array_format: &str,
mut buf: Box<dyn BufRead>,
truncate_len: u64,
column_width: u64,
) -> io::Result<()> {
let stdout = io::stdout();
let mut locked = stdout.lock();
let page = buf_to_array(&mut buf, truncate_len, column_width).unwrap();
match array_format {
"r" => writeln!(locked, "let ARRAY: [u8; {}] = [", page.bytes)?,
"c" => writeln!(locked, "unsigned char ARRAY[{}] = {{", page.bytes)?,
"g" => writeln!(locked, "a := [{}]byte{{", page.bytes)?,
"p" => writeln!(locked, "a = [")?,
"k" => writeln!(locked, "val a = byteArrayOf(")?,
"j" => writeln!(locked, "byte[] a = new byte[]{{")?,
"s" => writeln!(locked, "let a: [UInt8] = [")?,
"f" => writeln!(locked, "let a = [|")?,
_ => writeln!(locked, "unknown array format")?,
}
let mut i: u64 = 0x0;
for line in page.body.iter() {
write!(locked, " ")?;
for hex in line.hex_body.iter() {
i += 1;
if i == page.bytes && array_format != "g" {
if array_format != "f" {
write!(locked, "{}", Format::LowerHex.format(*hex, true))?;
} else {
write!(locked, "{}uy", Format::LowerHex.format(*hex, true))?;
}
} else if array_format != "f" {
write!(locked, "{}, ", Format::LowerHex.format(*hex, true))?;
} else {
write!(locked, "{}uy; ", Format::LowerHex.format(*hex, true))?;
}
}
writeln!(locked)?;
}
writeln!(
locked,
"{}",
match array_format {
"r" => "];",
"c" | "j" => "};",
"g" => "}",
"p" => "]",
"k" => ")",
"s" => "]",
"f" => "|]",
_ => "unknown array format",
}
)
}
pub fn output_function(len: u64, places: usize) {
for y in 0..len {
let y_float: f64 = y as f64;
let len_float: f64 = len as f64;
let x: f64 = (((y_float / len_float) * f64::consts::PI) / 2.0).sin();
let formatted_number = format!("{:.*}", places, x);
print!("{}", formatted_number);
print!(",");
if (y % 10) == 9 {
println!();
}
}
println!();
}
pub fn buf_to_array(
buf: &mut dyn Read,
buf_len: u64,
column_width: u64,
) -> Result<Page, Box<dyn ::std::error::Error>> {
let mut column_count: u64 = 0x0;
let max_array_size: u16 = <u16>::max_value(); let mut page: Page = Page::new();
let mut line: Line = Line::new();
for b in buf.bytes() {
let b1: u8 = b?;
line.bytes += 1;
page.bytes += 1;
line.hex_body.push(b1);
column_count += 1;
if column_count >= column_width {
page.body.push(line);
line = Line::new();
column_count = 0;
}
if buf_len > 0 && (page.bytes == buf_len || u64::from(max_array_size) == buf_len) {
break;
}
}
page.body.push(line);
Ok(page)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offset() {
let b: u64 = 0x6;
assert_eq!(offset(b), "0x000006");
assert_eq!(offset(b), format!("{:#08x}", b));
}
#[test]
pub fn test_hex_octal() {
let b: u8 = 0x6;
assert_eq!(Format::Octal.format(b, true), "0o0006");
assert_eq!(Format::Octal.format(b, true), format!("{:#06o}", b));
assert_eq!(Format::Octal.format(b, false), "0006");
assert_eq!(Format::Octal.format(b, false), format!("{:04o}", b));
}
#[test]
fn test_hex_lower_hex() {
let b: u8 = <u8>::max_value(); assert_eq!(Format::LowerHex.format(b, true), "0xff");
assert_eq!(Format::LowerHex.format(b, true), format!("{:#04x}", b));
assert_eq!(Format::LowerHex.format(b, false), "ff");
assert_eq!(Format::LowerHex.format(b, false), format!("{:02x}", b));
}
#[test]
fn test_hex_upper_hex() {
let b: u8 = <u8>::max_value();
assert_eq!(Format::UpperHex.format(b, true), "0xFF");
assert_eq!(Format::UpperHex.format(b, true), format!("{:#04X}", b));
assert_eq!(Format::UpperHex.format(b, false), "FF");
assert_eq!(Format::UpperHex.format(b, false), format!("{:02X}", b));
}
#[test]
fn test_hex_binary() {
let b: u8 = <u8>::max_value();
assert_eq!(Format::Binary.format(b, true), "0b11111111");
assert_eq!(Format::Binary.format(b, true), format!("{:#010b}", b));
assert_eq!(Format::Binary.format(b, false), "11111111");
assert_eq!(Format::Binary.format(b, false), format!("{:08b}", b));
}
#[test]
fn test_line_struct() {
let mut ascii_line: Line = Line::new();
ascii_line.ascii.push(b'.');
assert_eq!(ascii_line.ascii[0], b'.');
assert_eq!(ascii_line.offset, 0x0);
}
use assert_cmd::Command;
#[test]
fn test_cli_arg_order_1() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("-ar").arg("tests/files/tiny.txt").assert();
assert.success().code(0);
}
#[test]
fn test_cli_arg_order_2() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("tests/files/tiny.txt").arg("-ar").assert();
assert.success().code(0);
}
#[test]
fn test_cli_missing_param_value() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("--len").arg("tests/files/tiny.txt").assert();
assert.failure().code(1);
}
#[test]
fn test_cli_input_missing_file() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("missing-file").assert();
assert.failure().code(1);
}
#[test]
fn test_cli_input_directory() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("src").assert();
assert.failure().code(1);
}
#[test]
fn test_cli_input_stdin() {
let mut cmd = Command::cargo_bin("hx").unwrap();
let assert = cmd.arg("-t0").write_stdin("012").assert();
assert.success().code(0).stdout(
"0x000000: 0x30 0x31 0x32 012\n bytes: 3\n",
);
}
}