use std::cmp;
use std::io::{Write, stdout};
use clap::ValueEnum;
use colored::*;
use crate::schema::{Args, IDInfo, Output};
impl std::fmt::Display for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value().expect("-").get_name().fmt(f)
}
}
impl IDInfo {
pub fn print_card(&self) {
let timestamp = match self.timestamp.as_deref() {
Some(value) => format!("{} ({})", value, self.datetime.as_deref().unwrap_or("-")),
None => "-".to_string(),
};
const MIN_R_SPACE: usize = 43;
let l_space = 9;
let r_space = cmp::max(MIN_R_SPACE, timestamp.chars().count());
fn limit_r(text: String) -> String {
match text.char_indices().nth(MIN_R_SPACE) {
None => text,
Some((idx, _)) => format!("{}...", &text[..idx - 3]),
}
}
let size = match self.size {
0 => "-",
_ => match &self.parsed {
Some(parsed) => &format!("{} bits ({})", self.size, parsed),
None => &format!("{} bits", self.size),
},
};
let entropy = match self.size {
0 => "-",
_ => &format!("{} bits", self.entropy),
};
println!("┏━{:━<l_space$}━┯{:━<r_space$}━━┓", "", "");
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "ID Type", self.id_type);
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Version".yellow(), self.version.as_deref().unwrap_or("-"));
println!("┠─{:─<l_space$}─┼─{:─<r_space$}─┨", "", "");
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "String", limit_r(self.standard.clone()));
if let Some(value) = self.integer {
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Integer", value);
}
if let Some(value) = self.uuid_wrap.as_deref() {
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "UUID wrap", value);
}
let sequence = match self.sequence {
Some(value) => value.to_string(),
None => "-".to_string(),
};
println!("┠─{:─<l_space$}─┼─{:─<r_space$}─┨", "", "");
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Size", size);
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Entropy".green(), entropy);
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Timestamp".cyan(), timestamp);
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Node 1".purple(), limit_r(self.node1.clone().unwrap_or("-".to_string())));
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Node 2".red(), limit_r(self.node2.clone().unwrap_or("-".to_string())));
println!("┃ {:<l_space$} │ {:<r_space$} ┃", "Sequence".blue(), sequence);
println!("┠─{:─<l_space$}─┼─{:─<r_space$}─┨", "", "");
let (hex_lines, bin_lines) = self.get_hex_bin_lines();
let fix_space = r_space - MIN_R_SPACE; for (i, hex_line) in hex_lines.into_iter().enumerate() {
if self.bits.is_some() {
println!("┃ {:<l_space$} │ {}{:<fix_space$} ┃", hex_line, bin_lines[i], "");
} else {
println!("┃ {:<l_space$} │ {:<r_space$} ┃", hex_line, bin_lines[i]);
}
}
println!("┗━{:━<l_space$}━┷{:━<r_space$}━━┛", "", "");
}
fn get_hex_bin_lines(&self) -> (Vec<String>, Vec<String>) {
let mut bin_lines: Vec<String> = vec![];
let mut hex_lines: Vec<String> = vec![];
match self.bits.clone() {
Some(bits) => {
let remaining_bits = (32 - (bits.chars().count() % 32)) % 32;
let bits = format!("{}{}", bits, (0..remaining_bits).map(|_| ".").collect::<String>());
let color_map = Some(format!("{}{}", self.color_map.clone().unwrap(), (0..remaining_bits).map(|_| "0").collect::<String>()));
let hex = format!("{}{}", self.hex.clone().unwrap(), (0..remaining_bits / 4).map(|_| ".").collect::<String>());
let mut bin_line: String = "".to_string();
let mut hex_line: String = "".to_string();
for (i, c) in bits.chars().enumerate() {
let colored_bit = match color_map.clone() {
Some(color_map) => match color_map.chars().nth(i).unwrap() {
'1' => format!("{}", c.to_string().yellow()),
'2' => format!("{}", c.to_string().green()),
'3' => format!("{}", c.to_string().cyan()),
'4' => format!("{}", c.to_string().purple()),
'5' => format!("{}", c.to_string().red()),
'6' => format!("{}", c.to_string().blue()),
_ => format!("{}", c.to_string().normal()),
},
None => c.to_string(),
};
bin_line.push_str(&colored_bit);
if ((i + 1) % 4) == 0 {
bin_line.push(' ');
hex_line.push(hex.clone().chars().nth(i / 4).unwrap());
}
if ((i + 1) % 8) == 0 {
bin_line.push(' ');
}
if ((i + 1) % 16) == 0 {
bin_line.push(' ');
hex_line.push(' ');
}
if ((i + 1) % 32) == 0 {
bin_lines.push(bin_line.trim().to_string());
hex_lines.push(hex_line.trim().to_string());
bin_line = "".to_string();
hex_line = "".to_string();
}
}
}
None => {
bin_lines.push("No bits (non-numeric ID)".to_string());
hex_lines.push("No hex".to_string());
}
}
(hex_lines, bin_lines)
}
pub fn print_short(&self) {
match &self.version {
Some(version) => println!("ID Type: {}, version: {}.", self.id_type, version),
None => println!("ID Type: {}.", self.id_type),
}
}
pub fn print_json(&self) {
match serde_json::to_string(self) {
Ok(json) => println!("{}", json),
Err(_) => {
println!("Error rendering JSON");
std::process::exit(3);
}
};
}
pub fn print_binary(&self) {
match &self.integer {
Some(number) => {
let slice_offset: usize = ((128 - &self.size) / 8).into();
stdout().write_all(&number.to_be_bytes()[slice_offset..16]).unwrap();
}
None => match &self.hex {
Some(value) => {
let bytes = hex::decode(value).unwrap();
stdout().write_all(&bytes).unwrap();
}
None => println!("{}", self.standard),
},
}
}
pub fn print(&self, args: &Args) {
if args.everything {
self.print_card();
} else {
match args.output {
Output::Short => self.print_short(),
Output::Json => self.print_json(),
Output::Binary => self.print_binary(),
_ => self.print_card(),
}
}
}
}