Working proof of concept

This commit is contained in:
Imbus 2021-06-29 15:51:57 +02:00
commit 4bd8c3984d
13 changed files with 1091 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

458
Cargo.lock generated Normal file
View file

@ -0,0 +1,458 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "dirs"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "eff-wordlist"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226cc2588a6b769f444d06dedd29a366ea92469edea1cef7873fbd76e5b34c24"
dependencies = [
"rand 0.6.5",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "libc"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg",
"rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "waker"
version = "0.1.0"
dependencies = [
"clap",
"dirs",
"eff-wordlist",
"hex",
"rand 0.8.4",
"serde",
"serde_json",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

15
Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "waker"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = "*"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
dirs = "*"
clap = "*"
eff-wordlist = "*"
rand = "*"

50
README.md Normal file
View file

@ -0,0 +1,50 @@
TODO: Review parsing methods
Hosts should be pinged on listing
The format of a Wake-on-LAN (WOL) magic packet is defined
as a byte array with 6 bytes of value 255 (0xFF) and
16 repetitions of the target machines 48-bit (6-byte) MAC address.
102 bytes
several macs per name
edit, delete, add
wakemode::all
wake(bytes) method
async ping method(ip)?
const MAGIC_BYTES_HEADER: [u8; 6] = [0xFF; 6];
vec<u8> bytes = FFFFFFFFFFFF .reserve(enough) 102?
match run_mode {
Some() => Do according to some
None => Figure out what to do(set run_mode)
}
add 8 char string -> to bytes ask for name and add
add 11 char string -> if mac[2,5,8] is same char -> split by it and ask for name -> add
store json in ~/.config/waker.conf
dump-json method
export/import? append?
struct mechine {
vec of all macs
ip of machine? so we can ping it?
name
}
struct mac_list {
Vec
}
ask what do:
1. Wake
2. Wake all
3. Add
4. Edit

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
enum_discrim_align_threshold = 20
struct_field_align_threshold = 20

49
src/cli_args.rs Normal file
View file

@ -0,0 +1,49 @@
use clap::{App, Arg, ArgMatches};
// use crate::main::RunMode;
// let a = RunMode;
// waker FF:FF:FF:FF:FF:FF // Direct mode, wakes the given mac address
// waker -a, --all // Wake all configured machines
// waker -n, --name name1, name2 // Specified which configured name to wake
// waker -m, --macs mac1, mac2 // Specifies which macs to send magic packet to in direct mode
// waker -l, --list // Lists all configured machines
// waker -e, --edit // Enters interactive editing mode
// waker --backup-config [file] // Prints to stdout unless file is specified
pub fn get_cli_matches() -> ArgMatches<'static> {
/* Move this out to a function that returns a config struct with all the
* options */
/* or just return the ArgMatches object for clarity */
return App::new("Waker")
//.version("0.01")
.version(env!("CARGO_PKG_VERSION"))
.author("Imbus64")
.about("Utility for sending magic packets to configured machines.")
.arg(
Arg::with_name("all")
.short("a")
.long("all")
.help("Wake all configured hosts"),
)
.arg(
Arg::with_name("list")
.short("l")
.long("list")
.conflicts_with("weight")
.help("Print all entries"),
)
.arg(
Arg::with_name("raw")
.long("raw")
.conflicts_with_all(&["list", "plain"])
.help("Print raw log file to stdout"),
)
.arg(
Arg::with_name("plain")
.long("plain")
.conflicts_with_all(&["list", "raw"])
.help("Print all entries without pretty table formatting"),
)
.get_matches();
}

18
src/host.rs Normal file
View file

@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Host {
pub name: String,
pub macs: Vec<String>,
// pub ips: Vec<String>,
}
impl Host {
pub fn new<S: Into<String>>(name: S, mac: S, ipv4: S) -> Host {
Host {
name: name.into(),
macs: vec![mac.into()],
// ips: vec![ipv4],
}
}
}

25
src/input.rs Normal file
View file

@ -0,0 +1,25 @@
use std::io;
use std::io::prelude::*;
/// Python like input method with prompt message
pub fn input(prompt: &str) -> String {
print!("{}", prompt);
let _ = io::stdout().flush();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Could not read line.");
// Remove leading and trailing whitespaces and return
input.trim().to_string()
}
/// Simple confirm dialogue
pub fn confirm(message: &str) -> bool {
let answer = input(format!("{} [y/N]: ", message).as_str()).to_uppercase();
if answer == "YES" || answer == "Y" {
return true;
}
return false;
}

104
src/machines.rs Normal file
View file

@ -0,0 +1,104 @@
// use std::{fs::{File, OpenOptions, metadata}, io::{Read, Write}, path::{Path, PathBuf}};
use std::error::Error;
use std::{
fs::{File, OpenOptions},
io::Write,
path::PathBuf,
};
use crate::host::Host;
use serde::{Deserialize, Serialize};
// Possibly rename to HostList
#[derive(Serialize, Deserialize)]
pub struct Machines {
pub list: Vec<Host>,
}
impl Machines {
pub fn new() -> Machines {
Machines {
list: Vec::<Host>::new(),
}
}
pub fn add(&mut self, name: &str, mac_addr: &str) {
self.list.push(Host {
name: name.to_string(),
macs: vec![mac_addr.to_string()],
});
}
pub fn from_json_file(json_path: &PathBuf) -> Result<Machines, Box<dyn Error>> {
let machines: Machines;
if json_path.exists() || json_path.is_file() {
let json: String = std::fs::read_to_string(&json_path)?.parse()?;
machines = serde_json::from_str(&json)?;
// println!("Machines loaded from json");
} else {
machines = Machines::new();
let serialized = serde_json::to_string_pretty(&machines)?;
let mut file = File::create(&json_path)?;
file.write_all(&serialized.as_bytes())?;
std::fs::write(&json_path, &serialized)?;
// println!("Machines created");
}
Ok(machines)
}
/// Dump this struct in json format. Will NOT create file.
pub fn dump(&self, json_path: &PathBuf) -> Result<bool, Box<dyn Error>> {
let serialized = serde_json::to_string_pretty(&self)?;
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(&json_path)?;
file.write_all(&serialized.as_bytes())?;
// println!("Object written to existing file (truncated)");
// println!("{}", json_path.to_str().unwrap());
Ok(true)
}
}
// The Machine struct holds no information about where its json configuration file resides, if it
// did we could deserialize it into it in the destructor...
// impl Drop for Machines {
// fn drop(&mut self) {
// println!("Destructor called...");
// }
// }
mod tests {
use super::*;
#[test]
fn init_machines() {
let m = Machines::new();
}
#[test]
fn init_machines_and_add() {
let mut m = Machines::new();
m.add("Demo_Machine", "FF:FF:FF:FF:FF:FF");
assert_eq!(1, m.list.len());
m.add("Demo_Machine2", "FF:FF:FF:FF:FF:FF");
assert_eq!(2, m.list.len());
}
#[test]
fn write_and_load_from_file() {
let mut m = Machines::new();
m.add("File_Demo_Machine", "FF:FF:FF:FF:FF:FF");
assert_eq!(1, m.list.len());
let path = PathBuf::from("./DEMO_MACHINES.json");
std::fs::File::create(&path).unwrap();
m.dump(&path).unwrap();
// println!("{}", path. to_str().unwrap());
let m2 = Machines::from_json_file(&path).unwrap();
assert_eq!(1, m2.list.len());
// This cleanup will never happen if the previous assert fails...
// TODO: Branch eq check to a panic instead
std::fs::remove_file(&path).unwrap();
}
}

110
src/main.rs Normal file
View file

@ -0,0 +1,110 @@
#![allow(dead_code)]
// use std::{fs::{File, OpenOptions, metadata}, io::{Read, Write}, path::{Path, PathBuf}};
use std::path::PathBuf;
use std::{error::Error, iter::Enumerate, string};
// use serde::{Deserialize, Serialize};
//use serde_json::to;
mod cli_args; // Provides a custom function that specifies our command line options
mod host; // The actual Host struct
mod input; // Gives us a python-like input function, as well as a simple confirm function
mod machines; // Struct that holds a vec of Hosts, as well as operations on those
mod packet; // The actual magic packet struct, with wake methods e.t.c.
mod random_machine; // Exposes a function that generates a random Host, with random mac, ip and name // The actual host struct.
// use crate::packet::*;
use crate::machines::*;
use crate::packet::MagicPacket;
use random_machine::random_host;
// waker -a, --all // Wake all configured machines
// waker -n, --name name1, name2 // Specified which configured name to wake
// waker -m, --macs mac1, mac2 // Specifies which macs to send magic packet to in direct mode
// waker -l, --list // Lists all configured machines
// waker -e, --edit // Enters interactive editing mode
// waker --backup-config [file] // Prints to stdout unless file is specified
enum RunMode {
Wake { mode: WakeMode },
Edit { mode: EditMode },
List,
ConfigBak,
}
/// Specifies how and which machines should be woken
enum WakeMode {
WakeAll, // Wake every configured machine
WakeSome { indexes: Vec<i32> }, // Wake machines with these indexes/ids
Direct { mac_strings: Vec<String> }, // Wake these mac adresses, non-blocking
}
/// Specifies how to perform edits
enum EditMode {
Pick, // Prompt the user for which machine to edit
Direct { name: String }, // Edit machine with specified name
DirectID { id: i32 }, // Edit machine with specified id
}
/// Specifies how the program should backup its config file
enum BackupMode {
ToFile { path: PathBuf }, // Write to file
ToStdout, // Write to stdout
}
fn main() -> Result<(), Box<dyn Error>> {
let config_path = match cfg!(debug_assertions) {
// If this is a debug build, the the path becomes ./waker.json, relative to project root
true => PathBuf::new().join("waker.json"),
// If this is a release build, this is essentially ~/.config/waker.json stored in a pathbuf object
false => dirs::config_dir()
.expect("Could not find config directory...")
.join("waker.json"),
};
// If file does not exist -> Ask to create it -> dump skeleton json into it
if !config_path.is_file() {
let msg = format!(
"File \"{}\" does not seem to exist...\nCreate it?",
config_path.to_str().unwrap()
);
if input::confirm(&msg) {
let newfile = std::fs::File::create(&config_path).expect(&format!(
"Could not create file: {}",
config_path.to_str().unwrap()
));
// Deserialize an empty Machines struct into the new config file.
// The parser is not equipped to handle empty or malformatted files...
// This could potentially be moved into a static method in Machines, like
// Machines::init_config_file(), which does this for us.
//
// For now, this will do.
let skeleton_machines = Machines::new();
skeleton_machines.dump(&config_path);
} else {
println!("Exiting...");
return Ok(());
}
}
// TODO: More sophisticated error checking and logging
let mut machines = Machines::from_json_file(&config_path)?;
// let rhost = random_host();
// machines.add("test", "FF:FF:FF:FF:FF:FF");
// machines.add(&rhost.name, &rhost.macs[0]); // Hack, needs to have a method for this..
for (index, machine) in machines.list.iter().enumerate() {
let macs_str = format!("{:?}", machine.macs);
println!("{:<3}{:25}{}", index, macs_str, machine.name); // TODO: CHANGE THIS FORMAT TO INDEX, NAME, MACS
for mac in &machine.macs {
let mp = MagicPacket::from_str(&mac).expect("Could not parse mac address...");
mp.send();
}
}
machines.dump(&config_path)?;
return Ok(());
}

162
src/packet.rs Normal file
View file

@ -0,0 +1,162 @@
use std::{
error::Error,
net::{Ipv4Addr, ToSocketAddrs, UdpSocket},
};
// The format of a Wake-on-LAN (WOL) magic packet is defined
// as a byte array with 6 bytes of value 255 (0xFF) followed by
// 16 repetitions of the target machines 48-bit (6-byte) MAC address.
// This becomes a total of 102 bytes:
const MAGIC_HEADER: [u8; 6] = [0xFF; 6];
/// Contains raw bytes for magic packet
pub struct MagicPacket {
pub bytes: [u8; 102],
}
impl MagicPacket {
/// Create new MagicPacket from a raw 6-byte MAC address
pub fn new(mac_bytes: &[u8; 6]) -> MagicPacket {
let mut magic_bytes: [u8; 102];
unsafe {
magic_bytes = std::mem::MaybeUninit::uninit().assume_init();
let mut src: *const u8 = &MAGIC_HEADER[0];
let mut dst: *mut u8 = &mut magic_bytes[0];
dst.copy_from_nonoverlapping(src, 6);
src = &mac_bytes[0];
for _ in 0..16 {
dst = dst.offset(6);
dst.copy_from_nonoverlapping(src, 6);
}
}
return MagicPacket { bytes: magic_bytes };
}
// This is horrible code
/// Parse a MAC-string into a packet.
/// Takes both separated and unseparated
pub fn from_str(mac_str: &str) -> Result<MagicPacket, Box<dyn Error>> {
use hex::FromHex;
let mut mac_string = mac_str.trim().to_string();
if mac_string.len() == 17 {
for i in 0..5 {
mac_string.remove(14 - (i * 3)); // Deal with it
}
}
let mut mac_arr: [u8; 6];
let hex_vec = Vec::from_hex(mac_string)?;
unsafe {
mac_arr = std::mem::MaybeUninit::uninit().assume_init();
let src: *const u8 = &hex_vec[0];
let dst: *mut u8 = &mut mac_arr[0];
dst.copy_from_nonoverlapping(src, 6);
}
return Ok(MagicPacket::new(&mac_arr));
}
pub fn from_str2(mac_str: &str) -> Result<MagicPacket, Box<dyn Error>> {
return Ok(MagicPacket::new(&MagicPacket::parse(mac_str).unwrap()));
}
// Parses string by position if string is 12+5 characters long (delimited by : for example)
pub fn parse<S: AsRef<str>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
use hex::FromHex;
let mut mstr: &str = mac_str.as_ref();
mstr = mstr.trim();
let mut bytes: [u8; 6] = [0, 0, 0, 0, 0, 0];
for (index, byte) in bytes.iter_mut().enumerate() {
// 0,3,6,9...
let substr = &mstr[3 * index..3 * index + 2];
*byte = <[u8; 1]>::from_hex(substr).unwrap()[0];
}
return Ok(Box::new(bytes));
}
// Loops the string and parses any valid hex
pub fn parse_harder<S: Into<String>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
let mstr: String = mac_str.into();
let mut hexdigits: String = mstr
.chars()
.filter(|c| char::is_ascii_hexdigit(c))
.collect();
let mut bytes: [u8; 6] = [0; 6];
if hexdigits.len() >= 12 {
hexdigits.truncate(12); // May not be needed, since bytes is only 6 bytes, and from_hex might be smart enough to realize...
//let bytes: [u8; 6] = hex::FromHex::from_hex(hexdigits)?;
bytes = hex::FromHex::from_hex(hexdigits)?;
}
return Ok(Box::new(bytes));
}
/// Send packet to/from specific address/interface
pub fn send_to<A: ToSocketAddrs>(&self, to_addr: A, from_addr: A) -> std::io::Result<()> {
let socket = UdpSocket::bind(from_addr)?;
socket.set_broadcast(true)?;
socket.send_to(&self.bytes, to_addr)?;
Ok(())
}
/// Send package from whatever interface the os picks
pub fn send(&self) -> std::io::Result<()> {
self.send_to(
(Ipv4Addr::new(0xFF, 0xFF, 0xFF, 0xFF), 9),
(Ipv4Addr::new(0x0, 0x0, 0x0, 0x0), 0),
)
}
/// Return raw bytes, ready to be broadcasted
pub fn get_bytes(&self) -> &[u8; 102] {
&self.bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_packet() {
let packet1 = MagicPacket::new(&[0xFF; 6]);
let packet2 = MagicPacket::new(&[0xAA; 6]);
let vec: [u8; 102] = [0xFF; 102];
assert_eq!(packet1.bytes, vec);
assert_ne!(packet2.bytes, vec);
}
#[test]
fn magic_packet_from_new() {
let packet3 = MagicPacket::from_str2("10:10:10:10:10:10").unwrap();
//let vec: [u8; 102] = [0xFF; 102];
let slice = &packet3.bytes[packet3.bytes.len() - 6..];
for a in slice {
println!("{}", a);
}
let bytes: [u8; 6] = [0x10; 6];
assert_eq!(slice, bytes);
}
#[test]
fn test_parse() {
let mp = MagicPacket::parse("ff:ff:ff:ff:ff:ff").unwrap();
assert_eq!([0xFF; 6], *mp);
let mp2 = MagicPacket::parse("10:10:10:10:10:10").unwrap();
assert_eq!([0x10; 6], *mp2);
}
#[test]
fn parse_harder_test() {
let mac = MagicPacket::parse_harder("10:10:10:10:10:10").unwrap();
assert_eq!([0x10; 6], *mac);
}
}

94
src/random_machine.rs Normal file
View file

@ -0,0 +1,94 @@
use eff_wordlist::large::random_word;
use crate::host::Host;
use rand::thread_rng;
use rand::prelude::*;
// This file exists purely for debugging/testing purposes
fn random_mac() -> String {
let mut rng = thread_rng();
let bytes: [u8; 6] = rng.gen(); // rand can handle array initialization
let mac_str = format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
);
return mac_str;
}
fn random_ip() -> String {
let mut rng = thread_rng();
let bytes: [u8; 4] = rng.gen(); // rand can handle array initialization
let mut ip_str = format!(
"{}.{}.{}.{}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
);
return ip_str;
}
fn random_name() -> String {
let mut name = String::new();
name.push_str(random_word());
name.push_str(random_word());
return name;
}
pub fn random_host() -> Host {
return Host::new(random_name(), random_mac(), random_ip());
}
mod tests {
use super::*;
#[test]
fn test_random_name() {
let mut testvec: Vec<String> = Vec::new();
for _ in 0..10 {
// println!("{}", random_name());
let test_str = random_name();
println!("{}", test_str);
for other in &testvec {
assert!(!other.eq(&test_str)); // Make sure this particular string is not already present in testvec
}
testvec.push(test_str);
}
}
#[test]
fn test_random_ip() {
let mut testvec: Vec<String> = Vec::new();
for _ in 0..10 {
// println!("{}", random_name());
let test_str = random_ip();
println!("{}", test_str);
assert!(test_str.len() >= 7 && test_str.len() <= 15);
for other in &testvec {
assert!(!other.eq(&test_str)); // Make sure this particular string is not already present in testvec
}
testvec.push(test_str);
}
}
#[test]
fn test_random_mac() {
let mut testvec: Vec<String> = Vec::new();
for _ in 0..10 {
// println!("{}", random_name());
let test_str = random_mac();
println!("{}", test_str);
// assert!(test_str.len() >= 7 && test_str.len() <= 15);
assert!(test_str.len() == 17);
for other in &testvec {
assert!(!other.eq(&test_str)); // Make sure this particular string is not already present in testvec
}
testvec.push(test_str);
}
}
}

3
waker.json Normal file
View file

@ -0,0 +1,3 @@
{
"list": []
}