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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Imbus
						Imbus