Split to crates
This commit is contained in:
Generated
+9
-2
@@ -3,5 +3,12 @@
|
|||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "woly"
|
name = "wolz"
|
||||||
version = "0.1.0"
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"wolz-lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wolz-lib"
|
||||||
|
version = "0.0.1"
|
||||||
|
|||||||
+5
-6
@@ -1,6 +1,5 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "woly"
|
members = [
|
||||||
version = "0.1.0"
|
"crates/*",
|
||||||
edition = "2024"
|
]
|
||||||
|
resolver = "2"
|
||||||
[dependencies]
|
|
||||||
|
|||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wolz"
|
||||||
|
version = "0.0.1"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "wolz"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wolz-lib = { path = "../wolz-lib" }
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use std::env::args;
|
||||||
|
|
||||||
|
use wolz_lib::{error::WResult, wolz::Wolz};
|
||||||
|
|
||||||
|
fn main() -> WResult<()> {
|
||||||
|
let arg_1 = args().nth(1);
|
||||||
|
let Some(arg_1) = arg_1 else {
|
||||||
|
print_usage();
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let wolz = Wolz::create()?;
|
||||||
|
wolz.wake_up(&arg_1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_usage() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
r#"usage: woly ARG
|
||||||
|
|
||||||
|
Options:
|
||||||
|
ARG Either the mac address of the device to send the wol magic
|
||||||
|
packet to, or a nickname specified in woly.conf to lookup
|
||||||
|
the mac.
|
||||||
|
Eg: woly aa:bb:cc:dd:ee:ff
|
||||||
|
Eg: woly potato
|
||||||
|
where potato is configured in the woly.conf file as
|
||||||
|
potato=aa:bb:cc:dd:ee:ff
|
||||||
|
|
||||||
|
Config file:
|
||||||
|
Filename woly.conf
|
||||||
|
Location $WOLY_CONFIG_DIR/woly.conf, then $XDG_CONFIG_DIR/woly.conf,
|
||||||
|
then $HOME/woly.conf
|
||||||
|
Format one KEY=VALUE per line without spaces
|
||||||
|
Example potato=aa:bb:cc:dd:ee:ff
|
||||||
|
idly=12:34:23:e2:3d:ff
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[package]
|
||||||
|
name = "wolz-lib"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2024"
|
||||||
@@ -1,101 +1,21 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::Display,
|
|
||||||
fs::{self},
|
fs::{self},
|
||||||
mem::take,
|
mem::take,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
string::FromUtf8Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::{self, AppError, EResult};
|
use crate::{
|
||||||
|
error::{WError, WResult}, mac::{Device, Mac, Nick},
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Mac {
|
|
||||||
pub mac_bytes: [u8; 6],
|
|
||||||
original_string: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Mac {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.original_string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mac {
|
|
||||||
pub fn create(mac: &str) -> EResult<Self> {
|
|
||||||
let mut mac_bytes = [0u8; 6];
|
|
||||||
let mut mac_bytes_index = 0;
|
|
||||||
let split: Vec<&str> = mac.split(':').collect();
|
|
||||||
if split.len() != 6 {
|
|
||||||
return Err(AppError::NotAMac);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in &split {
|
|
||||||
mac_bytes[mac_bytes_index] = u8::from_str_radix(i, 16).map_err(|e| {
|
|
||||||
AppError::ParseMacError(format!(
|
|
||||||
"'{} is not a valid mac string, failed to parse '{}' as hex due to {}",
|
|
||||||
&mac, &i, e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
mac_bytes_index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
mac_bytes,
|
|
||||||
original_string: mac.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
|
||||||
pub struct Nick {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Nick {
|
|
||||||
pub fn create(nick: &str) -> EResult<Self> {
|
|
||||||
for b in nick.bytes() {
|
|
||||||
if !b.is_ascii_alphanumeric() {
|
|
||||||
return Err(AppError::NickNotAlphanumeric(format!(
|
|
||||||
"Nickname should be alphanumeric for a valid nick, Invalid char {}",
|
|
||||||
&b
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { name: nick.to_string() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Device {
|
|
||||||
Mac(Mac),
|
|
||||||
Nick(Nick),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Device {
|
|
||||||
pub fn create(mac_or_nick: &str) -> EResult<Self> {
|
|
||||||
let as_mac = Mac::create(mac_or_nick);
|
|
||||||
let Err(AppError::NotAMac) = as_mac else {
|
|
||||||
return as_mac.map(|m| Device::mac(m.mac_bytes, mac_or_nick.to_string()));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let as_nick = Nick::create(mac_or_nick)?;
|
|
||||||
Ok(Device::Nick(as_nick))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mac(mac_bytes: [u8; 6], original_string: String) -> Self {
|
|
||||||
Self::Mac(Mac {
|
|
||||||
mac_bytes,
|
|
||||||
original_string,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
nick_map: HashMap<Nick, Mac>,
|
nick_map: HashMap<Nick, Mac>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn create() -> EResult<Self> {
|
pub(crate) fn create() -> WResult<Self> {
|
||||||
let mut config_parent = Config::get_parent()?;
|
let mut config_parent = Config::get_parent()?;
|
||||||
config_parent.push("woly.conf");
|
config_parent.push("woly.conf");
|
||||||
let config_file = config_parent;
|
let config_file = config_parent;
|
||||||
@@ -104,7 +24,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::fs::File::create_new(&config_file).map_err(|e| {
|
std::fs::File::create_new(&config_file).map_err(|e| {
|
||||||
AppError::ConfigDirInitError(format!(
|
WError::ConfigDirInitError(format!(
|
||||||
"Failed to create file at {} due to {}",
|
"Failed to create file at {} due to {}",
|
||||||
config_file.display(),
|
config_file.display(),
|
||||||
e
|
e
|
||||||
@@ -116,13 +36,13 @@ impl Config {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mac(&self, mac_or_nick: &str) -> EResult<Mac> {
|
pub(crate) fn get_mac(&self, mac_or_nick: &str) -> WResult<Mac> {
|
||||||
match Device::create(mac_or_nick)? {
|
match Device::create(mac_or_nick)? {
|
||||||
Device::Mac(mac) => Ok(mac),
|
Device::Mac(mac) => Ok(mac),
|
||||||
Device::Nick(nick) => self
|
Device::Nick(nick) => self
|
||||||
.nick_map
|
.nick_map
|
||||||
.get(&nick)
|
.get(&nick)
|
||||||
.ok_or(AppError::NickNotMapped(format!(
|
.ok_or(WError::NickNotMapped(format!(
|
||||||
"Nickname {} is not mapped to any MAC address in the config file",
|
"Nickname {} is not mapped to any MAC address in the config file",
|
||||||
&nick.name
|
&nick.name
|
||||||
)))
|
)))
|
||||||
@@ -130,9 +50,9 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_existing_conf(config_file: PathBuf) -> EResult<Self> {
|
fn read_existing_conf(config_file: PathBuf) -> WResult<Self> {
|
||||||
let bytes = fs::read(&config_file).map_err(|e| {
|
let bytes = fs::read(&config_file).map_err(|e| {
|
||||||
AppError::ConfigDirInitError(format!(
|
WError::ConfigDirInitError(format!(
|
||||||
"Unable to read the contents of {} because {}",
|
"Unable to read the contents of {} because {}",
|
||||||
config_file.display(),
|
config_file.display(),
|
||||||
e
|
e
|
||||||
@@ -154,15 +74,14 @@ impl Config {
|
|||||||
|
|
||||||
// TODO: Also consider \r and \r\n
|
// TODO: Also consider \r and \r\n
|
||||||
if b == b'\n' {
|
if b == b'\n' {
|
||||||
let key_str =
|
let key_str = String::from_utf8(take(&mut key)).map_err(|e| map_config_error_utf8(&e, line_number))?;
|
||||||
String::from_utf8(take(&mut key)).map_err(|e| error::map_config_error_utf8(&e, line_number))?;
|
|
||||||
let value_str =
|
let value_str =
|
||||||
String::from_utf8(take(&mut value)).map_err(|e| error::map_config_error_utf8(&e, line_number))?;
|
String::from_utf8(take(&mut value)).map_err(|e| map_config_error_utf8(&e, line_number))?;
|
||||||
let mac = Mac::create(&value_str).map_err(|e| error::map_config_error(&e, line_number))?;
|
let mac = Mac::create(&value_str).map_err(|e| map_config_error(&e, line_number))?;
|
||||||
|
|
||||||
let nick = Nick { name: key_str };
|
let nick = Nick { name: key_str };
|
||||||
if nick_map.contains_key(&nick) {
|
if nick_map.contains_key(&nick) {
|
||||||
return Err(AppError::ConfigFileFormatError(format!(
|
return Err(WError::ConfigFileFormatError(format!(
|
||||||
"Duplicate key {} at line number {}",
|
"Duplicate key {} at line number {}",
|
||||||
nick.name, line_number
|
nick.name, line_number
|
||||||
)));
|
)));
|
||||||
@@ -183,13 +102,13 @@ impl Config {
|
|||||||
Ok(Config { nick_map })
|
Ok(Config { nick_map })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_parent() -> EResult<PathBuf> {
|
fn get_parent() -> WResult<PathBuf> {
|
||||||
let config_dir_env_var_name = "WOLY_CONFIG_HOME";
|
let config_dir_env_var_name = "WOLY_CONFIG_HOME";
|
||||||
let config_dir_name = std::env::var(config_dir_env_var_name);
|
let config_dir_name = std::env::var(config_dir_env_var_name);
|
||||||
if let Ok(config_dir_name) = config_dir_name {
|
if let Ok(config_dir_name) = config_dir_name {
|
||||||
let path = PathBuf::from(config_dir_name);
|
let path = PathBuf::from(config_dir_name);
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(AppError::InvalidConfigDir(format!(
|
return Err(WError::InvalidConfigDir(format!(
|
||||||
"{} does not point to a valid directory {}",
|
"{} does not point to a valid directory {}",
|
||||||
config_dir_env_var_name,
|
config_dir_env_var_name,
|
||||||
path.display()
|
path.display()
|
||||||
@@ -211,16 +130,16 @@ impl Config {
|
|||||||
return Config::validate_config_dir(config_dir, "$HOME/.config");
|
return Config::validate_config_dir(config_dir, "$HOME/.config");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(AppError::ConfigDirInitError(format!(
|
return Err(WError::ConfigDirInitError(format!(
|
||||||
"Set one of {}, {} or {} environment variable to determine the config directory",
|
"Set one of {}, {} or {} environment variable to determine the config directory",
|
||||||
"$WOLY_CONFIG_HOME", "$XDG_CONFIG_HOME", "$HOME"
|
"$WOLY_CONFIG_HOME", "$XDG_CONFIG_HOME", "$HOME"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_config_dir(config_dir: PathBuf, config_dir_source: &str) -> EResult<PathBuf> {
|
fn validate_config_dir(config_dir: PathBuf, config_dir_source: &str) -> WResult<PathBuf> {
|
||||||
if !config_dir.exists() {
|
if !config_dir.exists() {
|
||||||
fs::create_dir(&config_dir).map_err(|e| {
|
fs::create_dir(&config_dir).map_err(|e| {
|
||||||
AppError::ConfigDirInitError(format!(
|
WError::ConfigDirInitError(format!(
|
||||||
"Could not create the config directory at {} due to {}. Config source {}",
|
"Could not create the config directory at {} due to {}. Config source {}",
|
||||||
config_dir.display(),
|
config_dir.display(),
|
||||||
e,
|
e,
|
||||||
@@ -232,7 +151,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !config_dir.is_dir() {
|
if !config_dir.is_dir() {
|
||||||
return Err(AppError::ConfigDirInitError(format!(
|
return Err(WError::ConfigDirInitError(format!(
|
||||||
"config directory is not a valid directory: {}. Config source {}",
|
"config directory is not a valid directory: {}. Config source {}",
|
||||||
config_dir.display(),
|
config_dir.display(),
|
||||||
config_dir_source,
|
config_dir_source,
|
||||||
@@ -242,3 +161,11 @@ impl Config {
|
|||||||
return Ok(config_dir);
|
return Ok(config_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_config_error_utf8(error: &FromUtf8Error, line_number: usize) -> WError {
|
||||||
|
WError::ConfigFileFormatError(format!("On line {}, {}", line_number, error))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_config_error(error: &WError, line_number: usize) -> WError {
|
||||||
|
WError::ConfigFileFormatError(format!("On line {}, {}", line_number, error))
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub type WResult<T> = std::result::Result<T, WError>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WError {
|
||||||
|
ParseMacError(String),
|
||||||
|
NotAMac(String),
|
||||||
|
NickNotAlphanumeric(String),
|
||||||
|
NickNotMapped(String),
|
||||||
|
InvalidConfigDir(String),
|
||||||
|
ConfigDirInitError(String),
|
||||||
|
ConfigFileFormatError(String),
|
||||||
|
UdpSocketError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for WError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let to_write = match self {
|
||||||
|
WError::ParseMacError(s) => format!("{}", s),
|
||||||
|
WError::NotAMac(s) => format!("'{}' is not a valid MAC", s),
|
||||||
|
WError::NickNotAlphanumeric(s) => format!("{}", s),
|
||||||
|
WError::NickNotMapped(s) => format!("{}", s),
|
||||||
|
WError::InvalidConfigDir(s) => format!("{}", s),
|
||||||
|
WError::ConfigDirInitError(s) => format!("{}", s),
|
||||||
|
WError::ConfigFileFormatError(s) => format!("{}", s),
|
||||||
|
WError::UdpSocketError(s) => format!("{}", s),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", to_write)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod error;
|
||||||
|
pub(crate) mod mac;
|
||||||
|
pub(crate) mod config;
|
||||||
|
pub mod wolz;
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::error::{WError, WResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Mac {
|
||||||
|
mac_bytes: [u8; 6],
|
||||||
|
original_string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mac {
|
||||||
|
pub(crate) fn create(mac: &str) -> WResult<Self> {
|
||||||
|
let mut mac_bytes = [0u8; 6];
|
||||||
|
let mut mac_bytes_index = 0;
|
||||||
|
let split: Vec<&str> = mac.split(':').collect();
|
||||||
|
if split.len() != 6 {
|
||||||
|
return Err(WError::NotAMac(mac.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in &split {
|
||||||
|
mac_bytes[mac_bytes_index] = u8::from_str_radix(i, 16).map_err(|e| {
|
||||||
|
WError::ParseMacError(format!(
|
||||||
|
"'{} is not a valid mac string, failed to parse '{}' as hex due to {}",
|
||||||
|
&mac, &i, e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
mac_bytes_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
mac_bytes,
|
||||||
|
original_string: mac.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn copy_magic_bytes(&self, magic_bytes: &mut [u8; 102]) {
|
||||||
|
magic_bytes[..6].fill(0xFF);
|
||||||
|
for i in 0..16 {
|
||||||
|
let start = 6 + i * 6;
|
||||||
|
magic_bytes[start..start + 6].copy_from_slice(&self.mac_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Mac {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.original_string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub(crate) struct Nick {
|
||||||
|
pub(crate) name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Nick {
|
||||||
|
pub(crate) fn create(nick: &str) -> WResult<Self> {
|
||||||
|
for b in nick.bytes() {
|
||||||
|
if !b.is_ascii_alphanumeric() {
|
||||||
|
return Err(WError::NickNotAlphanumeric(format!(
|
||||||
|
"Nickname should be alphanumeric for a valid nick, Invalid char {}",
|
||||||
|
&b
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { name: nick.to_string() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Device {
|
||||||
|
Mac(Mac),
|
||||||
|
Nick(Nick),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub(crate) fn create(mac_or_nick: &str) -> WResult<Self> {
|
||||||
|
let as_mac = Mac::create(mac_or_nick);
|
||||||
|
let Err(WError::NotAMac(mac_or_nick)) = as_mac else {
|
||||||
|
return as_mac.map(|m| Device::mac(m.mac_bytes, mac_or_nick.to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let as_nick = Nick::create(&mac_or_nick)?;
|
||||||
|
Ok(Device::Nick(as_nick))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mac(mac_bytes: [u8; 6], original_string: String) -> Self {
|
||||||
|
Self::Mac(Mac {
|
||||||
|
mac_bytes,
|
||||||
|
original_string,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Mac;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_magic_bytes_for_mac() {
|
||||||
|
let mac = Mac::create("32:1a:ff:3e:10:ef").expect("Test data is curated to be valid");
|
||||||
|
let mut magic_bytes = [0u8; 102];
|
||||||
|
mac.copy_magic_bytes(&mut magic_bytes);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let expected = [
|
||||||
|
255, 255, 255, 255, 255, 255,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
50, 26, 255, 62, 16, 239,
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(magic_bytes, expected, "Magic bytes are not created correctly");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
error::{WError, WResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Wolz {
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wolz {
|
||||||
|
pub fn create() -> WResult<Self> {
|
||||||
|
let config = Config::create()?;
|
||||||
|
Ok(Self { config })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wake_up(&self, mac_or_nick: &str) -> WResult<()> {
|
||||||
|
let mac = self.config.get_mac(mac_or_nick)?;
|
||||||
|
let mut magic_bytes = [0u8; 102];
|
||||||
|
mac.copy_magic_bytes(&mut magic_bytes);
|
||||||
|
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0")
|
||||||
|
.map_err(|e| WError::UdpSocketError(format!("couldn't bind to socket because {}", e)))?;
|
||||||
|
socket.set_broadcast(true)
|
||||||
|
.map_err(|e| WError::UdpSocketError(format!("couldn't set broadcast because {}", e)))?;
|
||||||
|
socket
|
||||||
|
.send_to(&magic_bytes, "255.255.255.255:9")
|
||||||
|
.map_err(|e| WError::UdpSocketError(format!("couldn't send data because {}", e)))?;
|
||||||
|
println!("Magic packet sent to 255.255.255.255:9 for Mac {:}", mac);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use std::{fmt::Display, string::FromUtf8Error};
|
|
||||||
|
|
||||||
pub type EResult<T> = std::result::Result<T, AppError>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AppError {
|
|
||||||
ParseMacError(String),
|
|
||||||
NotAMac,
|
|
||||||
NickNotAlphanumeric(String),
|
|
||||||
NickNotMapped(String),
|
|
||||||
InvalidConfigDir(String),
|
|
||||||
ConfigDirInitError(String),
|
|
||||||
ConfigFileFormatError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for AppError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let to_write = match self {
|
|
||||||
AppError::ParseMacError(s) => format!("{}", s),
|
|
||||||
AppError::NotAMac => "Input is not a valid MAC".to_string(),
|
|
||||||
AppError::NickNotAlphanumeric(s) => format!("{}", s),
|
|
||||||
AppError::NickNotMapped(s) => format!("{}", s),
|
|
||||||
AppError::InvalidConfigDir(s) => format!("{}", s),
|
|
||||||
AppError::ConfigDirInitError(s) => format!("{}", s),
|
|
||||||
AppError::ConfigFileFormatError(s) => format!("{}", s),
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{}", to_write)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_config_error_utf8(error: &FromUtf8Error, line_number: usize) -> AppError {
|
|
||||||
AppError::ConfigFileFormatError(format!("On line {}, {}", line_number, error))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_config_error(error: &AppError, line_number: usize) -> AppError {
|
|
||||||
AppError::ConfigFileFormatError(format!("On line {}, {}", line_number, error))
|
|
||||||
}
|
|
||||||
-98
@@ -1,98 +0,0 @@
|
|||||||
mod config;
|
|
||||||
mod error;
|
|
||||||
|
|
||||||
use std::env::args;
|
|
||||||
use std::net::UdpSocket;
|
|
||||||
|
|
||||||
use crate::config::{Config, Mac};
|
|
||||||
use crate::error::EResult;
|
|
||||||
|
|
||||||
fn main() -> EResult<()> {
|
|
||||||
let config = Config::create()?;
|
|
||||||
|
|
||||||
let arg_1 = args().nth(1);
|
|
||||||
let Some(arg_1) = arg_1 else {
|
|
||||||
print_usage();
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mac = config.get_mac(&arg_1)?;
|
|
||||||
let mut magic_bytes = [0u8; 102];
|
|
||||||
get_magic_pkt(&mac, &mut magic_bytes);
|
|
||||||
|
|
||||||
let socket = UdpSocket::bind("0.0.0.0:0").expect("couldnt' bind socket");
|
|
||||||
socket.set_broadcast(true).expect("couldn't set broadcast");
|
|
||||||
socket
|
|
||||||
.send_to(&magic_bytes, "255.255.255.255:9")
|
|
||||||
.expect("couldn't send data");
|
|
||||||
println!("Magic packet sent to 255.255.255.255:9 for Mac {:}", mac);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_magic_pkt(mac: &Mac, magic_bytes: &mut [u8; 102]) {
|
|
||||||
magic_bytes[..6].fill(0xFF);
|
|
||||||
for i in 0..16 {
|
|
||||||
let start = 6 + i * 6;
|
|
||||||
magic_bytes[start..start + 6].copy_from_slice(&mac.mac_bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_usage() {
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
r#"usage: woly ARG
|
|
||||||
|
|
||||||
Options:
|
|
||||||
ARG Either the mac address of the device to send the wol magic
|
|
||||||
packet to, or a nickname specified in woly.conf to lookup
|
|
||||||
the mac.
|
|
||||||
Eg: woly aa:bb:cc:dd:ee:ff
|
|
||||||
Eg: woly potato
|
|
||||||
where potato is configured in the woly.conf file as
|
|
||||||
potato=aa:bb:cc:dd:ee:ff
|
|
||||||
|
|
||||||
Config file:
|
|
||||||
Filename woly.conf
|
|
||||||
Location $WOLY_CONFIG_DIR/woly.conf, then $XDG_CONFIG_DIR/woly.conf,
|
|
||||||
then $HOME/woly.conf
|
|
||||||
Format one KEY=VALUE per line without spaces
|
|
||||||
Example potato=aa:bb:cc:dd:ee:ff
|
|
||||||
idly=12:34:23:e2:3d:ff
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{config::Mac, get_magic_pkt};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_magic_bytes_for_mac() {
|
|
||||||
let mut magic_bytes = [0u8; 102];
|
|
||||||
let mac = Mac::create("32:1a:ff:3e:10:ef").expect("Test data is curated to be valid");
|
|
||||||
get_magic_pkt(&mac, &mut magic_bytes);
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
let expected = [
|
|
||||||
255, 255, 255, 255, 255, 255,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
50, 26, 255, 62, 16, 239,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(magic_bytes, expected, "Magic bytes are not created correctly");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user