Cli implemented, somewhat complete functionality
This commit is contained in:
parent
4bd8c3984d
commit
44e63e679e
10 changed files with 696 additions and 100 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
waker.json
|
||||||
|
|
11
README.md
11
README.md
|
@ -1,7 +1,18 @@
|
||||||
TODO: Review parsing methods
|
TODO: Review parsing methods
|
||||||
|
Add confirm for wake all
|
||||||
|
|
||||||
Hosts should be pinged on listing
|
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
|
The format of a Wake-on-LAN (WOL) magic packet is defined
|
||||||
as a byte array with 6 bytes of value 255 (0xFF) and
|
as a byte array with 6 bytes of value 255 (0xFF) and
|
||||||
16 repetitions of the target machine’s 48-bit (6-byte) MAC address.
|
16 repetitions of the target machine’s 48-bit (6-byte) MAC address.
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
use crate::{BackupMode, RunMode, WakeMode};
|
||||||
use clap::{App, Arg, ArgMatches};
|
use clap::{App, Arg, ArgMatches};
|
||||||
|
|
||||||
// use crate::main::RunMode;
|
// use crate::main::RunMode;
|
||||||
|
@ -11,6 +14,32 @@ use clap::{App, Arg, ArgMatches};
|
||||||
// waker -e, --edit // Enters interactive editing mode
|
// waker -e, --edit // Enters interactive editing mode
|
||||||
// waker --backup-config [file] // Prints to stdout unless file is specified
|
// 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> {
|
pub fn get_cli_matches() -> ArgMatches<'static> {
|
||||||
/* Move this out to a function that returns a config struct with all the
|
/* Move this out to a function that returns a config struct with all the
|
||||||
* options */
|
* options */
|
||||||
|
@ -21,29 +50,48 @@ pub fn get_cli_matches() -> ArgMatches<'static> {
|
||||||
.author("Imbus64")
|
.author("Imbus64")
|
||||||
.about("Utility for sending magic packets to configured machines.")
|
.about("Utility for sending magic packets to configured machines.")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("all")
|
Arg::with_name("add")
|
||||||
.short("a")
|
.short("a")
|
||||||
|
.long("add")
|
||||||
|
.help("Add a new host"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("all")
|
||||||
.long("all")
|
.long("all")
|
||||||
.help("Wake all configured hosts"),
|
.help("Wake all configured hosts"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("edit")
|
||||||
|
.short("e")
|
||||||
|
.long("edit")
|
||||||
|
.help("Enter edit mode"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("list")
|
Arg::with_name("list")
|
||||||
.short("l")
|
.short("l")
|
||||||
.long("list")
|
.long("list")
|
||||||
.conflicts_with("weight")
|
.help("List all configured entries"),
|
||||||
.help("Print all entries"),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("raw")
|
Arg::with_name("backup")
|
||||||
.long("raw")
|
.long("backup")
|
||||||
.conflicts_with_all(&["list", "plain"])
|
.conflicts_with_all(&["list", "all"])
|
||||||
.help("Print raw log file to stdout"),
|
.help("Backup configuration file")
|
||||||
|
.value_name("File"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("plain")
|
Arg::with_name("print_config")
|
||||||
.long("plain")
|
.long("print-config")
|
||||||
.conflicts_with_all(&["list", "raw"])
|
.short("p")
|
||||||
.help("Print all entries without pretty table formatting"),
|
.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();
|
.get_matches();
|
||||||
}
|
}
|
||||||
|
|
20
src/host.rs
20
src/host.rs
|
@ -1,10 +1,12 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::packet::MagicPacket;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Host {
|
pub struct Host {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub macs: Vec<String>,
|
pub macs: Vec<String>,
|
||||||
// pub ips: Vec<String>,
|
pub ips: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Host {
|
impl Host {
|
||||||
|
@ -12,7 +14,21 @@ impl Host {
|
||||||
Host {
|
Host {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
macs: vec![mac.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::host::Host;
|
use crate::host::Host;
|
||||||
|
use crate::packet::MagicPacket;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Possibly rename to HostList
|
// Possibly rename to HostList
|
||||||
|
@ -22,30 +23,45 @@ impl Machines {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, name: &str, mac_addr: &str) {
|
// This one needs refactoring...
|
||||||
self.list.push(Host {
|
/// Add new host to the list, taking a name and a mac, with an optional IP-adress
|
||||||
name: name.to_string(),
|
pub fn add(&mut self, name: &str, mac_addr: &str, ip_addr: Option<String>) {
|
||||||
macs: vec![mac_addr.to_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>> {
|
pub fn from_json_file(json_path: &PathBuf) -> Result<Machines, Box<dyn Error>> {
|
||||||
let machines: Machines;
|
let machines: Machines;
|
||||||
if json_path.exists() || json_path.is_file() {
|
if json_path.exists() || json_path.is_file() {
|
||||||
let json: String = std::fs::read_to_string(&json_path)?.parse()?;
|
let json: String = std::fs::read_to_string(&json_path)?.parse()?;
|
||||||
machines = serde_json::from_str(&json)?;
|
machines = serde_json::from_str(&json)?;
|
||||||
// println!("Machines loaded from json");
|
|
||||||
} else {
|
} else {
|
||||||
machines = Machines::new();
|
machines = Machines::new();
|
||||||
let serialized = serde_json::to_string_pretty(&machines)?;
|
let serialized = serde_json::to_string_pretty(&machines)?;
|
||||||
let mut file = File::create(&json_path)?;
|
let mut file = File::create(&json_path)?;
|
||||||
file.write_all(&serialized.as_bytes())?;
|
file.write_all(&serialized.as_bytes())?;
|
||||||
std::fs::write(&json_path, &serialized)?;
|
std::fs::write(&json_path, &serialized)?;
|
||||||
// println!("Machines created");
|
|
||||||
}
|
}
|
||||||
Ok(machines)
|
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.
|
/// Dump this struct in json format. Will NOT create file.
|
||||||
pub fn dump(&self, json_path: &PathBuf) -> Result<bool, Box<dyn Error>> {
|
pub fn dump(&self, json_path: &PathBuf) -> Result<bool, Box<dyn Error>> {
|
||||||
let serialized = serde_json::to_string_pretty(&self)?;
|
let serialized = serde_json::to_string_pretty(&self)?;
|
||||||
|
@ -54,10 +70,30 @@ impl Machines {
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(&json_path)?;
|
.open(&json_path)?;
|
||||||
file.write_all(&serialized.as_bytes())?;
|
file.write_all(&serialized.as_bytes())?;
|
||||||
// println!("Object written to existing file (truncated)");
|
|
||||||
// println!("{}", json_path.to_str().unwrap());
|
|
||||||
Ok(true)
|
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
|
// 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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn init_machines() {
|
fn init_machines() {
|
||||||
let m = Machines::new();
|
let _machine_initialization_test = Machines::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_machines_and_add() {
|
fn init_machines_and_add() {
|
||||||
let mut m = Machines::new();
|
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());
|
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());
|
assert_eq!(2, m.list.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_and_load_from_file() {
|
fn write_and_load_from_file() {
|
||||||
let mut m = Machines::new();
|
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());
|
assert_eq!(1, m.list.len());
|
||||||
let path = PathBuf::from("./DEMO_MACHINES.json");
|
let path = PathBuf::from("./DEMO_MACHINES.json");
|
||||||
std::fs::File::create(&path).unwrap();
|
std::fs::File::create(&path).unwrap();
|
||||||
|
|
498
src/main.rs
498
src/main.rs
|
@ -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::{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::{Deserialize, Serialize};
|
||||||
//use serde_json::to;
|
//use serde_json::to;
|
||||||
|
|
||||||
mod cli_args; // Provides a custom function that specifies our command line options
|
mod cli_args; // Provides a custom function that specifies our command line options
|
||||||
mod host; // The actual Host struct
|
mod host; // The actual Host struct
|
||||||
mod input; // Gives us a python-like input function, as well as a simple confirm function
|
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 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 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 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::packet::*;
|
||||||
use crate::machines::*;
|
use crate::machines::*;
|
||||||
use crate::packet::MagicPacket;
|
use host::Host;
|
||||||
use random_machine::random_host;
|
use input::*;
|
||||||
|
|
||||||
// waker -a, --all // Wake all configured machines
|
// waker -a, --all // Wake all configured machines
|
||||||
// waker -n, --name name1, name2 // Specified which configured name to wake
|
// 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 -e, --edit // Enters interactive editing mode
|
||||||
// waker --backup-config [file] // Prints to stdout unless file is specified
|
// waker --backup-config [file] // Prints to stdout unless file is specified
|
||||||
|
|
||||||
enum RunMode {
|
// Returned by cli argument parser (get_runmode())
|
||||||
Wake { mode: WakeMode },
|
// This should later be matched in the main program to execute the corresponding functionality
|
||||||
Edit { mode: EditMode },
|
/// Root enum for dictating program behaviour
|
||||||
|
pub enum RunMode {
|
||||||
|
Wake(WakeMode),
|
||||||
|
Edit,
|
||||||
|
Add,
|
||||||
List,
|
List,
|
||||||
ConfigBak,
|
Backup(BackupMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies how and which machines should be woken
|
/// Specifies how and which machines should be wol'ed
|
||||||
enum WakeMode {
|
pub enum WakeMode {
|
||||||
WakeAll, // Wake every configured machine
|
WakeAll, // Wake every configured machine
|
||||||
WakeSome { indexes: Vec<i32> }, // Wake machines with these indexes/ids
|
WakeSome, // Interactively pick hosts to wake
|
||||||
Direct { mac_strings: Vec<String> }, // Wake these mac adresses, non-blocking
|
DirectMacs(Vec<String>), // Wake these mac adresses, non-blocking
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies how to perform edits
|
// /// Specifies how to perform edits
|
||||||
enum EditMode {
|
// pub enum EditMode {
|
||||||
Pick, // Prompt the user for which machine to edit
|
// Pick, // Prompt the user for which machine to edit
|
||||||
Direct { name: String }, // Edit machine with specified name
|
// Direct(String), // Edit machine with specified name
|
||||||
DirectID { id: i32 }, // Edit machine with specified id
|
// 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
|
/// Specifies how the program should backup its config file
|
||||||
enum BackupMode {
|
pub enum BackupMode {
|
||||||
ToFile { path: PathBuf }, // Write to file
|
ToFile(String), // Write to file
|
||||||
ToStdout, // Write to stdout
|
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>> {
|
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 file does not exist -> Ask to create it -> dump skeleton json into it
|
||||||
if !config_path.is_file() {
|
if !config_path.is_file() {
|
||||||
let msg = format!(
|
let file = prompt_file_creation(&config_path);
|
||||||
"File \"{}\" does not seem to exist...\nCreate it?",
|
if file.is_none() {
|
||||||
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 {
|
|
||||||
println!("Exiting...");
|
println!("Exiting...");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -92,19 +351,144 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
// TODO: More sophisticated error checking and logging
|
// TODO: More sophisticated error checking and logging
|
||||||
let mut machines = Machines::from_json_file(&config_path)?;
|
let mut machines = Machines::from_json_file(&config_path)?;
|
||||||
// let rhost = random_host();
|
|
||||||
// machines.add("test", "FF:FF:FF:FF:FF:FF");
|
// Figure out how the program should behave
|
||||||
// machines.add(&rhost.name, &rhost.macs[0]); // Hack, needs to have a method for this..
|
let run_mode = cli_args::get_runmode();
|
||||||
|
|
||||||
for (index, machine) in machines.list.iter().enumerate() {
|
match run_mode {
|
||||||
let macs_str = format!("{:?}", machine.macs);
|
RunMode::List => {
|
||||||
println!("{:<3}{:25}{}", index, macs_str, machine.name); // TODO: CHANGE THIS FORMAT TO INDEX, NAME, MACS
|
println!("{}", machines);
|
||||||
for mac in &machine.macs {
|
}
|
||||||
let mp = MagicPacket::from_str(&mac).expect("Could not parse mac address...");
|
RunMode::Wake(wake_mode) => {
|
||||||
mp.send();
|
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)?;
|
machines.dump(&config_path)?;
|
||||||
return Ok(());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,13 @@ impl MagicPacket {
|
||||||
return Ok(MagicPacket::new(&MagicPacket::parse(mac_str).unwrap()));
|
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)
|
// 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>> {
|
pub fn parse<S: AsRef<str>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
|
||||||
use hex::FromHex;
|
use hex::FromHex;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use eff_wordlist::large::random_word;
|
|
||||||
use crate::host::Host;
|
use crate::host::Host;
|
||||||
use rand::thread_rng;
|
use eff_wordlist::large::random_word;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
// This file exists purely for debugging/testing purposes
|
// 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 bytes: [u8; 6] = rng.gen(); // rand can handle array initialization
|
||||||
let mac_str = format!(
|
let mac_str = format!(
|
||||||
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
|
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
|
||||||
bytes[0],
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
|
||||||
bytes[1],
|
);
|
||||||
bytes[2],
|
|
||||||
bytes[3],
|
|
||||||
bytes[4],
|
|
||||||
bytes[5],
|
|
||||||
);
|
|
||||||
return mac_str;
|
return mac_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +19,7 @@ fn random_ip() -> String {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let bytes: [u8; 4] = rng.gen(); // rand can handle array initialization
|
let bytes: [u8; 4] = rng.gen(); // rand can handle array initialization
|
||||||
|
|
||||||
let mut ip_str = format!(
|
let ip_str = format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3],);
|
||||||
"{}.{}.{}.{}",
|
|
||||||
bytes[0],
|
|
||||||
bytes[1],
|
|
||||||
bytes[2],
|
|
||||||
bytes[3],
|
|
||||||
);
|
|
||||||
return ip_str;
|
return ip_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +34,7 @@ pub fn random_host() -> Host {
|
||||||
return Host::new(random_name(), random_mac(), random_ip());
|
return Host::new(random_name(), random_mac(), random_ip());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
|
|
73
src/sanitizers.rs
Normal file
73
src/sanitizers.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
31
waker.json
31
waker.json
|
@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
Loading…
Reference in a new issue