Cli implemented, somewhat complete functionality

This commit is contained in:
Imbus 2021-07-20 16:16:50 +02:00
parent 4bd8c3984d
commit 44e63e679e
10 changed files with 696 additions and 100 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
waker.json

View file

@ -1,7 +1,18 @@
TODO: Review parsing methods
Add confirm for wake all
Hosts should be pinged on listing
Structs defined:
Host
- Implement methods to retrieve as macbytes/ipv4addr
- Field containing MagicPacket?
Machines
- WakeAll method
- PingAll method
MagicPacket
- Finalze parsers
The format of a Wake-on-LAN (WOL) magic packet is defined
as a byte array with 6 bytes of value 255 (0xFF) and
16 repetitions of the target machines 48-bit (6-byte) MAC address.

View file

@ -1,3 +1,6 @@
// use std::{path::PathBuf, str::FromStr};
use crate::{BackupMode, RunMode, WakeMode};
use clap::{App, Arg, ArgMatches};
// use crate::main::RunMode;
@ -11,6 +14,32 @@ use clap::{App, Arg, ArgMatches};
// waker -e, --edit // Enters interactive editing mode
// waker --backup-config [file] // Prints to stdout unless file is specified
// This is essentially and abstraction of clap
/// Parses command line arguments and returns a RunMode enum containing desired run mode.
pub fn get_runmode() -> RunMode {
let matches = get_cli_matches();
if matches.is_present("add") {
return RunMode::Add;
}
if matches.is_present("all") {
return RunMode::Wake(WakeMode::WakeAll);
}
if matches.is_present("edit") {
return RunMode::Edit;
}
if matches.is_present("list") {
return RunMode::List;
}
if matches.is_present("backup") {
let path_str = matches.value_of("backup").unwrap();
return RunMode::Backup(BackupMode::ToFile(path_str.to_string()));
}
if matches.is_present("print_config") {
return RunMode::Backup(BackupMode::ToStdout);
}
return RunMode::Wake(WakeMode::WakeSome);
}
pub fn get_cli_matches() -> ArgMatches<'static> {
/* Move this out to a function that returns a config struct with all the
* options */
@ -21,29 +50,48 @@ pub fn get_cli_matches() -> ArgMatches<'static> {
.author("Imbus64")
.about("Utility for sending magic packets to configured machines.")
.arg(
Arg::with_name("all")
Arg::with_name("add")
.short("a")
.long("add")
.help("Add a new host"),
)
.arg(
Arg::with_name("all")
.long("all")
.help("Wake all configured hosts"),
)
.arg(
Arg::with_name("edit")
.short("e")
.long("edit")
.help("Enter edit mode"),
)
.arg(
Arg::with_name("list")
.short("l")
.long("list")
.conflicts_with("weight")
.help("Print all entries"),
.help("List all configured entries"),
)
.arg(
Arg::with_name("raw")
.long("raw")
.conflicts_with_all(&["list", "plain"])
.help("Print raw log file to stdout"),
Arg::with_name("backup")
.long("backup")
.conflicts_with_all(&["list", "all"])
.help("Backup configuration file")
.value_name("File"),
)
.arg(
Arg::with_name("plain")
.long("plain")
.conflicts_with_all(&["list", "raw"])
.help("Print all entries without pretty table formatting"),
Arg::with_name("print_config")
.long("print-config")
.short("p")
.conflicts_with_all(&["list", "all"])
.help("Print contents of configuration file to stdout"),
)
.arg(
Arg::with_name("MAC ADDRESSES")
.conflicts_with_all(&["all", "list", "edit", "backup"])
.multiple(true),
)
// .short("MAC to be directly woken")
// .long("asdf")
.get_matches();
}

View file

@ -1,10 +1,12 @@
use serde::{Deserialize, Serialize};
use crate::packet::MagicPacket;
#[derive(Serialize, Deserialize)]
pub struct Host {
pub name: String,
pub macs: Vec<String>,
// pub ips: Vec<String>,
pub ips: Vec<String>,
}
impl Host {
@ -12,7 +14,21 @@ impl Host {
Host {
name: name.into(),
macs: vec![mac.into()],
// ips: vec![ipv4],
ips: vec![ipv4.into()],
}
}
pub fn wake(&self) {
for mac_str in &self.macs {
MagicPacket::from_str(&mac_str).unwrap().send().unwrap();
}
}
}
impl std::fmt::Display for Host {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let macs_str = format!("{:?}", &self.macs);
let ips_str = format!("{:?}", &self.ips);
write!(f, "{:<16} {} - {}", self.name, macs_str, ips_str)
}
}

View file

@ -7,6 +7,7 @@ use std::{
};
use crate::host::Host;
use crate::packet::MagicPacket;
use serde::{Deserialize, Serialize};
// Possibly rename to HostList
@ -22,30 +23,45 @@ impl Machines {
}
}
pub fn add(&mut self, name: &str, mac_addr: &str) {
self.list.push(Host {
name: name.to_string(),
macs: vec![mac_addr.to_string()],
});
// This one needs refactoring...
/// Add new host to the list, taking a name and a mac, with an optional IP-adress
pub fn add(&mut self, name: &str, mac_addr: &str, ip_addr: Option<String>) {
match ip_addr {
Some(ip_addr) => {
self.list.push(Host::new(name.to_string(), mac_addr.to_string(), ip_addr.to_string()))
}
None => {
self.list.push(Host {
name: name.to_string(),
macs: vec![mac_addr.to_string()],
ips: vec![],
});
}
}
}
/// Parses the Machine object from a json file
pub fn from_json_file(json_path: &PathBuf) -> Result<Machines, Box<dyn Error>> {
let machines: Machines;
if json_path.exists() || json_path.is_file() {
let json: String = std::fs::read_to_string(&json_path)?.parse()?;
machines = serde_json::from_str(&json)?;
// println!("Machines loaded from json");
} else {
machines = Machines::new();
let serialized = serde_json::to_string_pretty(&machines)?;
let mut file = File::create(&json_path)?;
file.write_all(&serialized.as_bytes())?;
std::fs::write(&json_path, &serialized)?;
// println!("Machines created");
}
Ok(machines)
}
fn create_skeleton_config(file: &PathBuf) -> Result<(), Box<dyn Error>> {
let skel_machines = Machines::new();
skel_machines.dump(file)?;
return Ok(());
}
/// Dump this struct in json format. Will NOT create file.
pub fn dump(&self, json_path: &PathBuf) -> Result<bool, Box<dyn Error>> {
let serialized = serde_json::to_string_pretty(&self)?;
@ -54,10 +70,30 @@ impl Machines {
.truncate(true)
.open(&json_path)?;
file.write_all(&serialized.as_bytes())?;
// println!("Object written to existing file (truncated)");
// println!("{}", json_path.to_str().unwrap());
Ok(true)
}
/// Attempts to wake all configured hosts via the default os-provided network interface
pub fn wakeall(&self) {
for host in &self.list {
for mac_str in &host.macs {
MagicPacket::from_str(mac_str).unwrap().send().unwrap();
}
}
}
}
// I would like to have some kind of iterator comparison here (for the newline), for now this will do...
impl std::fmt::Display for Machines {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (index, host) in self.list.iter().enumerate() {
write!(f, "{:<3}{}", index, host).unwrap();
if index != self.list.len()-1 { // Hacky
write!(f, "\n").unwrap();
}
}
return Ok(());
}
}
// The Machine struct holds no information about where its json configuration file resides, if it
@ -68,26 +104,27 @@ impl Machines {
// }
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_machines() {
let m = Machines::new();
let _machine_initialization_test = Machines::new();
}
#[test]
fn init_machines_and_add() {
let mut m = Machines::new();
m.add("Demo_Machine", "FF:FF:FF:FF:FF:FF");
m.add("Demo_Machine", "FF:FF:FF:FF:FF:FF", None);
assert_eq!(1, m.list.len());
m.add("Demo_Machine2", "FF:FF:FF:FF:FF:FF");
m.add("Demo_Machine2", "FF:FF:FF:FF:FF:FF", None);
assert_eq!(2, m.list.len());
}
#[test]
fn write_and_load_from_file() {
let mut m = Machines::new();
m.add("File_Demo_Machine", "FF:FF:FF:FF:FF:FF");
m.add("File_Demo_Machine", "FF:FF:FF:FF:FF:FF", None);
assert_eq!(1, m.list.len());
let path = PathBuf::from("./DEMO_MACHINES.json");
std::fs::File::create(&path).unwrap();

View file

@ -1,23 +1,28 @@
#![allow(dead_code)]
// #![allow(dead_code)]
// #![allow(unused_imports)]
use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::error::Error;
use std::str::FromStr;
// use std::{fs::{File, OpenOptions, metadata}, io::{Read, Write}, path::{Path, PathBuf}};
use std::path::PathBuf;
use std::{error::Error, iter::Enumerate, string};
// use serde::{Deserialize, Serialize};
//use serde_json::to;
mod cli_args; // Provides a custom function that specifies our command line options
mod host; // The actual Host struct
mod input; // Gives us a python-like input function, as well as a simple confirm function
mod machines; // Struct that holds a vec of Hosts, as well as operations on those
mod packet; // The actual magic packet struct, with wake methods e.t.c.
mod random_machine; // Exposes a function that generates a random Host, with random mac, ip and name // The actual host struct.
mod cli_args; // Provides a custom function that specifies our command line options
mod host; // The actual Host struct
mod input; // Gives us a python-like input function, as well as a simple confirm function
mod machines; // Struct that holds a vec of Hosts, as well as operations on those
mod packet; // The actual magic packet struct, with wake methods e.t.c.
// mod random_machine; // Exposes a function that generates a random Host, with random mac, ip and name // The actual host struct.
mod sanitizers; // Functions that sanitizes MAC and IP addresses
// use crate::packet::*;
use crate::machines::*;
use crate::packet::MagicPacket;
use random_machine::random_host;
use host::Host;
use input::*;
// waker -a, --all // Wake all configured machines
// waker -n, --name name1, name2 // Specified which configured name to wake
@ -26,31 +31,302 @@ use random_machine::random_host;
// waker -e, --edit // Enters interactive editing mode
// waker --backup-config [file] // Prints to stdout unless file is specified
enum RunMode {
Wake { mode: WakeMode },
Edit { mode: EditMode },
// Returned by cli argument parser (get_runmode())
// This should later be matched in the main program to execute the corresponding functionality
/// Root enum for dictating program behaviour
pub enum RunMode {
Wake(WakeMode),
Edit,
Add,
List,
ConfigBak,
Backup(BackupMode),
}
/// Specifies how and which machines should be woken
enum WakeMode {
WakeAll, // Wake every configured machine
WakeSome { indexes: Vec<i32> }, // Wake machines with these indexes/ids
Direct { mac_strings: Vec<String> }, // Wake these mac adresses, non-blocking
/// Specifies how and which machines should be wol'ed
pub enum WakeMode {
WakeAll, // Wake every configured machine
WakeSome, // Interactively pick hosts to wake
DirectMacs(Vec<String>), // Wake these mac adresses, non-blocking
}
/// Specifies how to perform edits
enum EditMode {
Pick, // Prompt the user for which machine to edit
Direct { name: String }, // Edit machine with specified name
DirectID { id: i32 }, // Edit machine with specified id
// /// Specifies how to perform edits
// pub enum EditMode {
// Pick, // Prompt the user for which machine to edit
// Direct(String), // Edit machine with specified name
// DirectID(i32), // Edit machine with specified id
// }
// Describes how to edit a host
enum HostEditMode {
EditName,
EditIps,
EditMacs,
}
/// Specifies how the program should backup its config file
enum BackupMode {
ToFile { path: PathBuf }, // Write to file
ToStdout, // Write to stdout
pub enum BackupMode {
ToFile(String), // Write to file
ToStdout, // Write to stdout
}
// fn prompt_file_creation(config_path: &PathBuf) -> Result<(), Box<dyn Error>> {
fn prompt_file_creation(config_path: &PathBuf) -> Option<File> {
let msg = format!(
"File \"{}\" does not seem to exist...\nCreate it?",
config_path.to_str().unwrap()
);
if input::confirm(&msg) {
let newfile = std::fs::File::create(&config_path).expect(&format!(
"Could not create file: {}",
config_path.to_str().unwrap()
));
// Deserialize an empty Machines struct into the new config file.
// The parser is not equipped to handle empty or malformatted files...
// This could potentially be moved into a static method in Machines, like
// Machines::init_config_file(), which does this for us.
//
// For now, this will do.
let skeleton_machines = Machines::new();
skeleton_machines.dump(&config_path).unwrap();
return Some(newfile);
} else {
return None;
}
}
// I think this one is pretty ok. It gets no points for readability, however.
// This could arguably be done with some kind of dictionary as
// well. A custom struct with a builder pattern would maybe work...
/// Presents a prompt. The user picks between the strings in the vector. The function returns an
/// Option<i32> containing the vector index of the picked element.
/// Returns None if input is empty, re-prompts if input invalid or index is out of range.
fn select_option(text: &str, options: &Vec<String>) -> Option<i32> {
for (index, option) in options.iter().enumerate() {
println!("{:<5}{}", format!("{}.", index), option)
}
loop {
let input_str = input(&text);
if input_str.is_empty() {
return None;
}
match input_str.parse::<i32>() {
Ok(index) => {
if index >= 0 && (index as usize) < options.len() {
return Some(index);
}
return None;
}
Err(_what) => {
println!("Invalid input");
}
}
}
}
// INCOMPLETE
// Return Result<(), dyn Error> ?
/// Takes a host reference and a HostEditMode.
/// Behaves according to the HostEditMode provided.
fn edit_host(host: &mut Host, editmode: HostEditMode) {
println!("Works");
match editmode {
HostEditMode::EditName => {
let new_name = input("New name: ");
if new_name.len() > 0 {
host.name = new_name;
} else {
println!("Name unchanged...");
}
}
HostEditMode::EditIps => {
let select = select_option(
"What do you want to do?: ",
&vec![
"Add an IP-address".to_string(),
"Edit an IP-address".to_string(),
"Remove an IP-address".to_string(),
],
);
match select {
Some(index) => {
match index {
0 => { // Add
let newip = input("New IP: ");
match sanitizers::sanitize(&newip, sanitizers::AddrType::IPv4) {
Some(ip) => {
host.ips.push(ip);
}
None => {
println!("Could not parse IP");
}
}
}
1 => { // Edit
let select = select_option("Which ip?: ", &host.ips);
match select {
Some(index) => {
let newip = input("New IP: ");
match sanitizers::sanitize(&newip, sanitizers::AddrType::IPv4) {
Some(ip) => {
host.ips[index as usize] = newip;
}
None => {
println!("Could not parse IP");
}
}
}
None => {}
}
}
2 => { // Remove
let select = select_option("Which ip?: ", &host.ips);
match select {
Some(index) => {
host.ips.remove(index as usize);
}
None => {}
}
}
_ => {}
}
}
None => {
println!("None selected.");
}
}
}
HostEditMode::EditMacs => {
let select = select_option(
"What do you want to do?: ",
&vec![
"Add a MAC-address".to_string(),
"Edit a MAC-address".to_string(),
"Remove a MAC-address".to_string(),
],
);
match select {
Some(index) => {
match index {
0 => { // Add
let newmac = input("New MAC: ");
match sanitizers::sanitize(&newmac, sanitizers::AddrType::MAC) {
Some(mac_addr) => {
host.macs.push(mac_addr);
}
None => {
println!("Could not parse mac_addr");
}
}
}
1 => { // Edit
let select = select_option("Which MAC?: ", &host.macs);
match select {
Some(index) => {
let newmac = input("New MAC: ");
match sanitizers::sanitize(&newmac, sanitizers::AddrType::MAC) {
Some(mac_addr) => {
host.macs[index as usize] = newmac;
}
None => {
println!("Could not parse MAC");
}
}
}
None => {}
}
}
2 => { // Remove
let select = select_option("Which MAC?: ", &host.macs);
match select {
Some(index) => {
host.macs.remove(index as usize);
}
None => {}
}
}
_ => {}
}
}
None => {
println!("None selected.");
}
}
}
}
}
// This code seems to be complete
/// Drops the user into a prompt for editing a host
fn edit_machines(machines: &mut Machines) {
loop {
println!("{}", machines);
let index_vec = which_indexes("Which host do you wish to edit? (Integer): ", machines);
match index_vec.len() {
0 => break,
1 => {
println!("Selected: {}", machines.list[index_vec[0] as usize].name);
let index = index_vec[0] as usize;
let host = &mut machines.list[index];
println!("1. Name\n2. IP addresses\n3. Mac addresses\n4. Delete");
let choice = parse_integers(&input("What would you like to edit? (Integer): "));
match choice.len() {
0 => break,
1 => match choice[0] {
1 => edit_host(host, HostEditMode::EditName),
2 => edit_host(host, HostEditMode::EditIps),
3 => edit_host(host, HostEditMode::EditMacs),
4 => {
if confirm(&format!("Really delete host \"{}\"", machines.list[index].name)) {
machines.list.remove(index);
}
}
_ => break,
},
_ => break,
}
}
_ => break,
}
println!("{:?}", index_vec);
}
}
// Needs reworking. It works as intended but can be written significantly more elegant and
// efficient.
// TODO: In place searching and parsing
/// Parses any integer found in string into a vector of i32.
/// Integers can be arbitrarily separated
fn parse_integers(int_str: &String) -> Vec<i32> {
let mut return_vector = Vec::<i32>::new();
let mut intstr = String::new();
for c in int_str.chars() {
let mut parse = false;
if c.is_digit(10) {
intstr.push(c);
} else {
parse = true;
}
if !intstr.is_empty() && parse == true {
return_vector.push(intstr.parse().unwrap());
intstr.clear();
}
}
if !intstr.is_empty() {
return_vector.push(intstr.parse().unwrap());
intstr.clear();
}
return return_vector;
}
fn which_indexes<S: AsRef<str>>(message: S, _machines: &Machines) -> Vec<i32> {
let indexes = input(message.as_ref());
let integers = parse_integers(&indexes);
return integers;
}
fn main() -> Result<(), Box<dyn Error>> {
@ -66,25 +342,8 @@ fn main() -> Result<(), Box<dyn Error>> {
// If file does not exist -> Ask to create it -> dump skeleton json into it
if !config_path.is_file() {
let msg = format!(
"File \"{}\" does not seem to exist...\nCreate it?",
config_path.to_str().unwrap()
);
if input::confirm(&msg) {
let newfile = std::fs::File::create(&config_path).expect(&format!(
"Could not create file: {}",
config_path.to_str().unwrap()
));
// Deserialize an empty Machines struct into the new config file.
// The parser is not equipped to handle empty or malformatted files...
// This could potentially be moved into a static method in Machines, like
// Machines::init_config_file(), which does this for us.
//
// For now, this will do.
let skeleton_machines = Machines::new();
skeleton_machines.dump(&config_path);
} else {
let file = prompt_file_creation(&config_path);
if file.is_none() {
println!("Exiting...");
return Ok(());
}
@ -92,19 +351,144 @@ fn main() -> Result<(), Box<dyn Error>> {
// TODO: More sophisticated error checking and logging
let mut machines = Machines::from_json_file(&config_path)?;
// let rhost = random_host();
// machines.add("test", "FF:FF:FF:FF:FF:FF");
// machines.add(&rhost.name, &rhost.macs[0]); // Hack, needs to have a method for this..
// Figure out how the program should behave
let run_mode = cli_args::get_runmode();
for (index, machine) in machines.list.iter().enumerate() {
let macs_str = format!("{:?}", machine.macs);
println!("{:<3}{:25}{}", index, macs_str, machine.name); // TODO: CHANGE THIS FORMAT TO INDEX, NAME, MACS
for mac in &machine.macs {
let mp = MagicPacket::from_str(&mac).expect("Could not parse mac address...");
mp.send();
match run_mode {
RunMode::List => {
println!("{}", machines);
}
RunMode::Wake(wake_mode) => {
match wake_mode {
WakeMode::WakeAll => {
machines.wakeall();
for host in &machines.list {
println!("Woke {}", host.name)
}
}
WakeMode::WakeSome => {
println!("{}", machines);
let indexes = which_indexes(
"Select which hosts to wake up (Comma separated integers): ",
&machines,
);
for index in indexes {
// TODO: Bounds checking
let host = &machines.list[index as usize];
host.wake();
println!("Woke {}", host.name)
}
}
_ => println!("undefined"),
}
}
RunMode::Edit => {
edit_machines(&mut machines);
}
RunMode::Add => {
println!("Add new machine:");
let mut add_machine: bool = true;
let mut name: String = String::from("");
let mut mac_addr: String = String::from("");
let mut ip_addr: Option<String> = None;
while add_machine {
name = input("What would you like to call your host?:\n");
if name.is_empty() {
add_machine = false;
break;
}
if confirm(&format!("Name: {}, is this correct?", &name)) {
break;
}
}
while add_machine {
mac_addr = input("What MAC address is assigned to your host?:\n");
if mac_addr.is_empty() {
add_machine = false;
break;
}
if confirm(&format!("MAC: {}, is this correct?", &mac_addr)) {
break;
}
}
while add_machine {
let ip_str = input("What IP address is assigned to your host?: (Blank for none)\n");
if ip_str.is_empty() {
ip_addr = None;
break;
}
if confirm(&format!("IP: {}, is this correct?", &ip_str)) {
ip_addr = Some(ip_str);
break;
}
}
if add_machine {
machines.add(&name, &mac_addr, ip_addr);
}
}
// Might need some polish in regards to guards and error handling.
// Perhaps there is a cleaner way to do the writing...
// This seems to work fine for now
RunMode::Backup(backup_mode) => {
// Read entire file unbuffered into memory.
let content = fs::read_to_string(&config_path).unwrap();
match backup_mode {
// Another valid, and maybe more concise way of doing this is to just do a plain
// copy of the config file into the *valid* file_string destination.
// This does its job "good enough"(TM) for now...
BackupMode::ToFile(file_string) => {
let backup_file = PathBuf::from_str(&file_string).unwrap();
// If the target file does not already exist, and is not a directory
// TODO: Further guards
if !backup_file.exists() && !backup_file.is_dir() {
println!("Executing file backup");
let mut backup_file_handle = OpenOptions::new().create(true).write(true).open(backup_file).unwrap();
backup_file_handle.write_all(content.as_bytes()).unwrap();
}
else if backup_file.exists() && backup_file.is_file() {
if confirm(&format!("The file \"{}\" already exists...\nOverwrite?", &file_string)) {
let mut backup_file_handle = OpenOptions::new().write(true).open(backup_file).unwrap();
backup_file_handle.write_all(content.as_bytes()).unwrap();
}
}
else {
// Well, what else should i do? If a user enters a directory as backup
// file, id consider that a dead end.
//
// Some other obscure errors that i havent considered might end up in this branch as well.
println!("Invalid path... Exiting");
}
},
BackupMode::ToStdout => {
for line in content.lines() {
println!("{}", line);
}
},
}
}
}
machines.dump(&config_path)?;
return Ok(());
}
#[cfg(test)]
mod main_test {
use super::*;
#[test]
fn parse_ints_test() {
let numvec = vec![10, 20, 30, 2, 4, 1923];
let numstr = format!("{:?}", numvec);
let numstr2 = String::from("10kfsa20fav?30!::]2sd4::;sdalkd c 1923"); // Seriously borked input
let parsed_vec = parse_integers(&numstr);
let parsed_vec2 = parse_integers(&numstr2);
assert_eq!(numvec, parsed_vec);
assert_eq!(numvec, parsed_vec2);
println!("{:?}", numvec);
println!("{:?}", parsed_vec);
println!("{:?}", parsed_vec2);
}
}

View file

@ -63,6 +63,13 @@ impl MagicPacket {
return Ok(MagicPacket::new(&MagicPacket::parse(mac_str).unwrap()));
}
// Stolen
fn mac_to_byte(data: &str, sep: char) -> Vec<u8> {
data.split(sep)
.flat_map(|x| hex::decode(x).expect("Invalid mac!"))
.collect()
}
// Parses string by position if string is 12+5 characters long (delimited by : for example)
pub fn parse<S: AsRef<str>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
use hex::FromHex;

View file

@ -1,7 +1,7 @@
use eff_wordlist::large::random_word;
use crate::host::Host;
use rand::thread_rng;
use eff_wordlist::large::random_word;
use rand::prelude::*;
use rand::thread_rng;
// This file exists purely for debugging/testing purposes
@ -10,13 +10,8 @@ fn random_mac() -> String {
let bytes: [u8; 6] = rng.gen(); // rand can handle array initialization
let mac_str = format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
);
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
);
return mac_str;
}
@ -24,13 +19,7 @@ fn random_ip() -> String {
let mut rng = thread_rng();
let bytes: [u8; 4] = rng.gen(); // rand can handle array initialization
let mut ip_str = format!(
"{}.{}.{}.{}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
);
let ip_str = format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3],);
return ip_str;
}
@ -45,6 +34,7 @@ pub fn random_host() -> Host {
return Host::new(random_name(), random_mac(), random_ip());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]

73
src/sanitizers.rs Normal file
View file

@ -0,0 +1,73 @@
/// For use as parameter in the sanitize function
pub enum AddrType {
MAC,
IPv4,
}
/// Takes an AddrType enum and returns an Option<String> containing the sanitized string.
/// Returns None if failed to sanitize.
/// This is a very permissive sanitizer, and it will parse even the most malformatted strings
/// It is guaranteed to return a valid MAC/IP
pub fn sanitize(address: &str, addr_type: AddrType) -> Option<String> {
match addr_type {
AddrType::MAC => {
let mut mac_str = String::new();
for c in address.to_string().chars() {
if c.is_digit(16) {
mac_str.push(c);
if mac_str.len() == 12 { break; }
}
}
mac_str.insert(10, ':');
mac_str.insert(8, ':');
mac_str.insert(6, ':');
mac_str.insert(4, ':');
mac_str.insert(2, ':');
return Some(mac_str);
}
AddrType::IPv4 => {
let mut bytes: Vec<u8> = Vec::new(); // Explicit type to avoid any typing errors
let ip_str = address.to_string();
let byte_list = ip_str.split('.');
for byte_base10_str in byte_list {
match byte_base10_str.parse::<u8>() {
Ok(byte) => bytes.push(byte),
Err(_what) => continue,
}
// bytes.push(byte_base10_str.parse::<u8>().unwrap());
}
return Some(format!("{}.{}.{}.{}", bytes[0], bytes[2], bytes[2], bytes[3]));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_mac() {
let macstr = String::from("FFFFFFFFFFFF");
let formatted = String::from("FF:FF:FF:FF:FF:FF");
assert_eq!(formatted, sanitize(&macstr, AddrType::MAC).unwrap());
}
#[test]
// No comparison check, just check so the length is correct
fn sanitize_mac_garbage() {
let macstr = String::from("sdakjaojoiwjvoievoijevioqjoijeriojkljlknxxx218913981389981jixjxxjk1kj1k");
assert_eq!(17, sanitize(&macstr, AddrType::MAC).unwrap().len());
}
#[test]
fn sanitize_ip() {
let ipstr = String::from("255.255.255.255");
let formatted = String::from("255.255.255.255");
assert_eq!(formatted, sanitize(&ipstr, AddrType::IPv4).unwrap());
}
#[test]
fn sanitize_ip_garbage() {
let ipstr = String::from("asdafasd.255.255.asdakfjjkjkfjk.255.255.asdafa");
let formatted = String::from("255.255.255.255");
assert_eq!(formatted, sanitize(&ipstr, AddrType::IPv4).unwrap());
}
}

View file

@ -1,3 +1,32 @@
{
"list": []
"list": [
{
"name": "Name",
"macs": [
"E9:A3:29:73:FE:D2"
],
"ips": []
},
{
"name": "i",
"macs": [
"7D:E1:AC:94:29:79"
],
"ips": []
},
{
"name": "PartyMaskinen",
"macs": [
"96:F8:FF:30:4C:EE"
],
"ips": []
},
{
"name": "Kalle",
"macs": [
"FF:FF:FF:FF:FF:FF"
],
"ips": []
}
]
}