Table of contents
Occasionally, I find a need to convert large amounts of data into a slightly different form. For example, I may get message data in hexadecimal in various forms from Wireshark or similar and want to use the raw data within GoogleTest. There are a number of online converters out there which are useful, but I thought I'd use my Rust learning to create a little Swiss army knife tool for such things.
Goals:
Convert hex data to c-style byte array initializer format
linux-like command line interface to extend the toolbox over time
Implementation
This is a simple, one-file application presently. I haven't used any Rust command-line parsers to date. Clap was recommended by ChatGPT, so I thought I'd give it a try.
main.rs
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "Rusty Toolbox")]
#[command(version = "1.0")]
#[command(author = "Eric Zimmerman <ericjzim@gmail.com>")]
#[command(about = "Does awesome things", long_about = None)]
struct Cli {
/// Turn debugging information on
#[arg(short, long, action = clap::ArgAction::Count)]
debug: u8,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// does testing things
Test {
/// lists test values
#[arg(short, long)]
list: bool,
},
/// reformat input
Reformat {
/// Required argument for get
#[arg(required = true)]
raw_input: String,
/// Sets the output format
#[arg(short = 'o', long = "outputformat")]
outputformat: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
// You can check the value provided by positional arguments, or option arguments
// You can see how many times a particular flag or argument occurred
// Note, only flags can have multiple occurrences
match cli.debug {
0 => {} // Do nothing for case 0
1 => println!("Debug level 1"),
2 => println!("Debug level 2"),
_ => println!("Don't be crazy"),
}
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Some(Commands::Test { list }) => {
if *list {
println!("Printing testing lists...");
} else {
println!("Not printing testing lists...");
}
}
Some(Commands::Reformat {
raw_input,
outputformat,
}) => {
let desired_format = outputformat.as_deref().unwrap_or("");
reformat_input(raw_input, desired_format);
}
None => {}
}
// Continued program logic goes here...
}
fn reformat_input(input: &str, output_format: &str) {
match output_format {
"" => {
// Get the input hex string from arguments and trim whitespace
let input = input.trim();
// Convert the hex string to a C array and print the result
match hex_string_to_c_array(input) {
Ok(result) => println!("{}", result),
Err(e) => println!("Error: {}", e),
}
}
"bin" => {
let bin = input
.as_bytes()
.iter()
.map(|b| format!("{:08b}", b))
.collect::<String>();
println!("Binary: {}", bin);
}
_ => eprintln!("Invalid output format"),
}
}
fn hex_string_to_c_array(input: &str) -> Result<String, &'static str> {
// Normalize input by removing spaces and `0x` prefixes
let normalized_input = input.replace(" ", "").replace("0x", "");
// Ensure the input length is even for valid byte pairs
if normalized_input.len() % 2 != 0 {
return Err("Invalid input: Length of input string is not even.");
}
// Split the normalized input into chunks of two characters
// and convert each chunk into a formatted string with `0x` prefix
let hex_bytes: Vec<String> = normalized_input
.as_bytes()
.chunks(2)
.map(|chunk| {
// Convert each chunk to a slice and then to a string
let hex_str = std::str::from_utf8(chunk).unwrap(); // In real application, handle this Result properly
// Format the string with `0x` prefix
format!("0x{}", hex_str.to_uppercase())
})
.collect();
// Join the formatted strings with `, ` and wrap with `{ }` for C-style initialization
Ok(format!("{{ {} }}", hex_bytes.join(", ")))
}
Output
Help
> rusty-toolbox.exe --help
Does awesome things
Usage: rusty-toolbox.exe [OPTIONS] [COMMAND]
Commands:
test does testing things
reformat reformat input
help Print this message or the help of the given subcommand(s)
Options:
-d, --debug... Turn debugging information on
-h, --help Print help
-V, --version Print version
> rusty-toolbox.exe reformat --help
reformat input
Usage: rusty-toolbox.exe reformat [OPTIONS] <RAW_INPUT>
Arguments:
<RAW_INPUT> Required argument for get
Options:
-o, --outputformat <OUTPUTFORMAT> Sets the output format
-h, --help Print help
Reformat
> rusty-toolbox reformat " 010203 040506 07 08 "
{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }