1use std::error::Error;
2use std::fmt::Display;
3use std::net::{Ipv4Addr, UdpSocket};
4use std::num::ParseIntError;
5
6#[derive(Debug)]
7pub struct MagicPacket([u8; 102]);
8
9type MacAddress = [u8; 6];
10
11#[derive(Debug)]
12pub enum MagicError {
13 ParseInt(ParseIntError),
14 InvalidMac,
15 IoError(std::io::Error),
16}
17
18impl Error for MagicError {}
19impl Display for MagicError {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "Magic Error")
22 }
23}
24
25impl From<ParseIntError> for MagicError {
26 fn from(e: ParseIntError) -> Self {
27 MagicError::ParseInt(e)
28 }
29}
30
31impl From<std::io::Error> for MagicError {
32 fn from(e: std::io::Error) -> Self {
33 MagicError::IoError(e)
34 }
35}
36
37impl From<Vec<u8>> for MagicError {
38 fn from(_e: Vec<u8>) -> Self {
39 MagicError::InvalidMac
40 }
41}
42
43const PREFIX: [u8; 6] = [0xFF; 6];
44
45impl MagicPacket {
46 pub fn new(mac: MacAddress) -> Self {
47 MagicPacket(
48 [
49 PREFIX, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac, mac,
50 mac,
51 ]
52 .concat()
53 .try_into()
54 .unwrap(),
55 )
56 }
57
58 pub fn send(&self) -> Result<(), MagicError> {
59 let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
60 socket.set_broadcast(true)?;
61
62 socket.send_to(&self.0, (Ipv4Addr::BROADCAST, 9))?;
63
64 Ok(())
65 }
66}
67
68impl From<[u8; 102]> for MagicPacket {
69 fn from(packet: [u8; 102]) -> Self {
70 MagicPacket(packet)
71 }
72}
73
74impl TryFrom<&str> for MagicPacket {
75 type Error = MagicError;
76
77 fn try_from(value: &str) -> Result<Self, Self::Error> {
78 let mac: [u8; 6] = value
79 .split(':')
80 .map(|e| u8::from_str_radix(e, 16))
81 .collect::<Result<Vec<u8>, _>>()?
82 .try_into()?;
83
84 Ok(MagicPacket::new(mac))
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_magic_packet_from_string() {
94 let expected_packet: [u8; 102] = [
95 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01,
96 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03,
97 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
98 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01,
99 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03,
100 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
101 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01,
102 0x02, 0x03, 0x04, 0x05,
103 ];
104
105 let magic_packet: MagicPacket = "00:01:02:03:04:05".try_into().unwrap();
106
107 assert_eq!(expected_packet, magic_packet.0);
108 }
109
110 #[test]
111 fn test_invalid_segment() {
112 let result: Result<MagicPacket, MagicError> = "GG:00:00:00:00:00".try_into();
113
114 match result {
115 Err(MagicError::ParseInt(_)) => (),
116 r => unreachable!("Should have been MagicError::ParseInt but was {:?}", r),
117 }
118 }
119
120 #[test]
121 fn test_invalid_length() {
122 let result: Result<MagicPacket, MagicError> = "00:00:00:00:00".try_into();
123
124 match result {
125 Err(MagicError::InvalidMac) => (),
126 r => unreachable!("Should have been MagicError::InvalidMac but was {:?}", r),
127 }
128 }
129}