wakey/
lib.rs

1//! Library for managing Wake-on-LAN packets.
2//! # Example
3//! ```
4//! let wol = wakey::WolPacket::from_string("01:02:03:04:05:06", ':').unwrap();
5//! if wol.send_magic().is_ok() {
6//!     println!("Sent the magic packet!");
7//! } else {
8//!     println!("Failed to send the magic packet!");
9//! }
10//! ```
11
12use std::error::Error;
13use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
14use std::{fmt, iter};
15
16use arrayvec::ArrayVec;
17
18const MAC_SIZE: usize = 6;
19const MAC_PER_MAGIC: usize = 16;
20const HEADER: [u8; 6] = [0xFF; 6];
21const PACKET_LEN: usize = HEADER.len() + MAC_SIZE * MAC_PER_MAGIC;
22
23type Packet = ArrayVec<u8, PACKET_LEN>;
24type Mac = ArrayVec<u8, MAC_SIZE>;
25
26/// Wrapper `Result` for the module errors.
27pub type Result<T> = std::result::Result<T, WakeyError>;
28
29/// Wrapper Error for fiascoes occuring in this module.
30#[derive(Debug)]
31pub enum WakeyError {
32    /// The provided MAC address has invalid length.
33    InvalidMacLength,
34    /// The provided MAC address has invalid format.
35    InvalidMacFormat,
36    /// There was an error sending the WoL packet.
37    SendFailure(std::io::Error),
38}
39
40impl Error for WakeyError {}
41
42impl fmt::Display for WakeyError {
43    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44        match self {
45            WakeyError::InvalidMacLength => {
46                write!(f, "Invalid MAC address length")
47            }
48            WakeyError::InvalidMacFormat => write!(f, "Invalid MAC address format"),
49            WakeyError::SendFailure(e) => write!(f, "Couldn't send WoL packet: {e}"),
50        }
51    }
52}
53
54impl From<std::io::Error> for WakeyError {
55    fn from(error: std::io::Error) -> Self {
56        WakeyError::SendFailure(error)
57    }
58}
59
60/// Wake-on-LAN packet
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct WolPacket {
63    /// WOL packet bytes
64    packet: Packet,
65}
66
67impl WolPacket {
68    /// Creates WOL packet from byte MAC representation
69    /// # Example
70    /// ```
71    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
72    /// ```
73    pub fn from_bytes(mac: &[u8]) -> Result<WolPacket> {
74        Ok(WolPacket {
75            packet: WolPacket::create_packet_bytes(mac)?,
76        })
77    }
78
79    /// Creates WOL packet from string MAC representation (e.x. 00:01:02:03:04:05)
80    /// # Example
81    /// ```
82    /// let wol = wakey::WolPacket::from_string("00:01:02:03:04:05", ':');
83    /// ```
84    /// # Panic
85    ///  Panics when input MAC is invalid (i.e. contains non-byte characters)
86    pub fn from_string<T: AsRef<str>>(data: T, sep: char) -> Result<WolPacket> {
87        WolPacket::from_bytes(&WolPacket::mac_to_byte(data, sep)?)
88    }
89
90    /// Broadcasts the magic packet from / to default address
91    /// Source: 0.0.0.0:0
92    /// Destination 255.255.255.255:9
93    /// # Example
94    /// ```
95    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]).unwrap();
96    /// wol.send_magic();
97    /// ```
98    pub fn send_magic(&self) -> Result<()> {
99        self.send_magic_to(
100            SocketAddr::from(([0, 0, 0, 0], 0)),
101            SocketAddr::from(([255, 255, 255, 255], 9)),
102        )
103    }
104
105    /// Broadcasts the magic packet from / to specified address.
106    /// # Example
107    /// ```
108    /// use std::net::SocketAddr;
109    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]).unwrap();
110    /// let src = SocketAddr::from(([0,0,0,0], 0));
111    /// let dst = SocketAddr::from(([255,255,255,255], 9));
112    /// wol.send_magic_to(src, dst);
113    /// ```
114    pub fn send_magic_to<A: ToSocketAddrs>(&self, src: A, dst: A) -> Result<()> {
115        let udp_sock = UdpSocket::bind(src)?;
116        udp_sock.set_broadcast(true)?;
117        udp_sock.send_to(&self.packet, dst)?;
118
119        Ok(())
120    }
121
122    /// Returns the underlying WoL packet bytes
123    pub fn into_inner(self) -> Packet {
124        self.packet
125    }
126
127    /// Converts string representation of MAC address (e.x. 00:01:02:03:04:05) to raw bytes.
128    /// # Panic
129    /// Panics when input MAC is invalid (i.e. contains non-byte characters)
130    fn mac_to_byte<T: AsRef<str>>(data: T, sep: char) -> Result<Mac> {
131        // hex-encoded bytes * 2 plus separators
132        if data.as_ref().len() != MAC_SIZE * 3 - 1 {
133            return Err(WakeyError::InvalidMacLength);
134        }
135
136        let bytes = data
137            .as_ref()
138            .split(sep)
139            .map(|x| hex::decode(x).map_err(|_| WakeyError::InvalidMacFormat))
140            .collect::<Result<ArrayVec<_, MAC_SIZE>>>()?
141            .into_iter()
142            .flatten()
143            .collect::<Mac>();
144
145        debug_assert_eq!(MAC_SIZE, bytes.len());
146
147        Ok(bytes)
148    }
149
150    /// Extends the MAC address to fill the magic packet
151    fn extend_mac(mac: &[u8]) -> ArrayVec<u8, { MAC_SIZE * MAC_PER_MAGIC }> {
152        let magic = iter::repeat(mac)
153            .take(MAC_PER_MAGIC)
154            .flatten()
155            .copied()
156            .collect::<ArrayVec<u8, { MAC_SIZE * MAC_PER_MAGIC }>>();
157
158        debug_assert_eq!(MAC_SIZE * MAC_PER_MAGIC, magic.len());
159
160        magic
161    }
162
163    /// Creates bytes of the magic packet from MAC address
164    fn create_packet_bytes(mac: &[u8]) -> Result<Packet> {
165        if mac.len() != MAC_SIZE {
166            return Err(WakeyError::InvalidMacLength);
167        }
168        let mut packet = Packet::new();
169
170        packet.extend(HEADER);
171        packet.extend(WolPacket::extend_mac(mac));
172
173        debug_assert_eq!(PACKET_LEN, packet.len());
174
175        Ok(packet)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn extend_mac_test() {
185        let mac = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
186
187        let extended_mac = WolPacket::extend_mac(&mac);
188
189        assert_eq!(extended_mac.len(), MAC_PER_MAGIC * MAC_SIZE);
190        assert_eq!(&extended_mac[(MAC_PER_MAGIC - 1) * MAC_SIZE..], &mac[..]);
191    }
192
193    #[test]
194    #[should_panic]
195    fn extend_mac_mac_too_long_test() {
196        let mac = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
197        WolPacket::extend_mac(&mac);
198    }
199
200    #[test]
201    #[should_panic]
202    fn extend_mac_mac_too_short_test() {
203        let mac = vec![0x01, 0x02, 0x03, 0x04, 0x05];
204        WolPacket::extend_mac(&mac);
205    }
206
207    #[test]
208    fn mac_to_byte_test() {
209        let mac = "01:02:03:04:05:06";
210        let result = WolPacket::mac_to_byte(mac, ':');
211
212        assert_eq!(
213            result.unwrap().into_inner().unwrap(),
214            [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]
215        );
216    }
217
218    #[test]
219    fn mac_to_byte_invalid_chars_test() {
220        let mac = "ZZ:02:03:04:05:06";
221        assert!(matches!(
222            WolPacket::mac_to_byte(mac, ':'),
223            Err(WakeyError::InvalidMacFormat)
224        ));
225    }
226
227    #[test]
228    fn mac_to_byte_invalid_separator_test() {
229        let mac = "01002:03:04:05:06";
230        assert!(matches!(
231            WolPacket::mac_to_byte(mac, ':'),
232            Err(WakeyError::InvalidMacFormat)
233        ));
234    }
235
236    #[test]
237    fn mac_to_byte_mac_too_long_test() {
238        let mac = "01:02:03:04:05:06:07";
239        assert!(matches!(
240            WolPacket::mac_to_byte(mac, ':'),
241            Err(WakeyError::InvalidMacLength)
242        ));
243    }
244
245    #[test]
246    fn mac_to_byte_mac_too_short_test() {
247        let mac = "01:02:03:04:05";
248        assert!(matches!(
249            WolPacket::mac_to_byte(mac, ':'),
250            Err(WakeyError::InvalidMacLength)
251        ));
252    }
253
254    #[test]
255    fn create_packet_bytes_test() {
256        let bytes = WolPacket::create_packet_bytes(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).unwrap();
257
258        assert_eq!(bytes.len(), MAC_SIZE * MAC_PER_MAGIC + HEADER.len());
259        assert!(bytes.iter().all(|&x| x == 0xFF));
260    }
261
262    #[test]
263    fn create_wol_packet() {
264        let mac = vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
265        let wol = WolPacket::from_bytes(&mac).unwrap();
266        let packet = wol.into_inner();
267
268        assert_eq!(packet.len(), PACKET_LEN);
269        assert_eq!(
270            [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
271            &packet[0..HEADER.len()]
272        );
273        for offset in (HEADER.len()..PACKET_LEN).step_by(MAC_SIZE) {
274            assert_eq!(&mac, &packet[offset..offset + MAC_SIZE]);
275        }
276    }
277}