magic_packet/
lib.rs

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}