about summary refs log tree commit diff
path: root/src/main.rs
blob: 65634a6132f02c13880e6eb9e74a369070ff4733 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use core::fmt;
use std::{
	net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
	ops::Deref,
	str::FromStr,
};

use scurvy::{
	Argument, Scurvy,
	formula::{Formula, U16Formula},
};

fn main() {
	let args = vec![
		Argument::new("mac").arg("addr"),
		Argument::new("host").arg("ip"),
		Argument::new("port").arg("port"),
	];
	let scurvy = Scurvy::make(args);

	if scurvy.should_print_help() {
		print_help()
	} else if scurvy.should_print_version() {
		print_version()
	}

	let mac: Mac = scurvy.parse_req("mac", Formula::new("[opt] is not a valid MAC address"));
	let port = scurvy.parse_or("port", U16Formula::new(), 9);
	let host: IpAddr = scurvy.parse_or(
		"ip",
		Formula::new("[opt] is not a valid IP address"),
		IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)),
	);

	let mut packet = vec![255; 6];
	packet.extend(mac.repeat(16));

	let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
	socket.set_broadcast(true).unwrap();

	let addr = SocketAddr::new(host, port);
	socket.send_to(&packet, addr).unwrap();

	println!("Sent magic packet!\nUDP destination {host}\nUDP port {port}\nTo MAC {mac}")
}

#[derive(Debug)]
struct Mac {
	bytes: [u8; 6],
}

impl FromStr for Mac {
	type Err = String;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		let mut bytes = [0u8; 6];

		for (idx, split) in s.split(':').enumerate() {
			let Ok(octet) = u8::from_str_radix(split, 16) else {
				return Err(format!("'{split}' is not a valid byte"));
			};

			if idx < 6 {
				bytes[idx] = octet;
			} else {
				return Err(format!("too many octets in MAC address"));
			}
		}

		return Ok(Self { bytes });
	}
}

impl Deref for Mac {
	type Target = [u8; 6];

	fn deref(&self) -> &Self::Target {
		&self.bytes
	}
}

impl fmt::Display for Mac {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		let b = self.bytes;

		write!(
			f,
			"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
			b[0], b[1], b[2], b[3], b[4], b[5]
		)
	}
}

const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");

fn print_help() -> ! {
	println!("usage: {NAME} [arguments...]\n");
	println!("ARGUMENTS");
	println!("    mac=<addr>");
	println!("        Required.");
	println!("        MAC address of the system you want to wake\n");
	println!("    host=<ip>");
	println!("        IP address to send the UDP packet containing the magic to.");
	println!("        this argument defaults to 255.255.255.255 which is the broadcast address");
	println!("        of \"this network\"");
	println!("        [Default 255.255.255.255]\n");
	println!("    port=<port>");
	println!("        Port the UDP packet is sent to.");
	println!("        [Default 9]\n");
	println!("    help= | -h | --help");
	println!("        print this message and exit\n");
	println!("    version= | -V | --version");
	println!("        print the version and authors and exit");
	std::process::exit(0)
}

fn print_version() -> ! {
	println!("{NAME} version {VERSION}");
	println!("written by {AUTHORS}");
	std::process::exit(0)
}