1use 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
26pub type Result<T> = std::result::Result<T, WakeyError>;
28
29#[derive(Debug)]
31pub enum WakeyError {
32 InvalidMacLength,
34 InvalidMacFormat,
36 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#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct WolPacket {
63 packet: Packet,
65}
66
67impl WolPacket {
68 pub fn from_bytes(mac: &[u8]) -> Result<WolPacket> {
74 Ok(WolPacket {
75 packet: WolPacket::create_packet_bytes(mac)?,
76 })
77 }
78
79 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 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 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 pub fn into_inner(self) -> Packet {
124 self.packet
125 }
126
127 fn mac_to_byte<T: AsRef<str>>(data: T, sep: char) -> Result<Mac> {
131 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 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 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}