wol/
lib.rs

1//! Wake-on-LAN implementation for sending magic packets to wake devices on a network.
2//!
3//! This library allows you to easily wake devices that support the Wake-on-LAN protocol
4//! by creating and sending magic packets to the target MAC address.
5
6use std::error::Error;
7use std::fmt;
8use std::net::{ToSocketAddrs, UdpSocket};
9
10/// Default port for Wake-on-LAN packets
11pub const DEFAULT_PORT: u16 = 65530;
12
13/// Default broadcast address
14pub const DEFAULT_BROADCAST_ADDRESS: &str = "255.255.255.255";
15
16/// Errors that can occur when working with Wake-on-LAN
17#[derive(Debug)]
18pub enum WolError {
19    /// Error parsing MAC address format
20    InvalidMacAddress(String),
21    /// Network-related error
22    NetworkError(std::io::Error),
23    /// Other error
24    Other(String),
25}
26
27impl fmt::Display for WolError {
28    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29        match self {
30            WolError::InvalidMacAddress(mac) => write!(f, "Invalid MAC address format: {}", mac),
31            WolError::NetworkError(err) => write!(f, "Network error: {}", err),
32            WolError::Other(msg) => write!(f, "{}", msg),
33        }
34    }
35}
36
37impl Error for WolError {}
38
39impl From<std::io::Error> for WolError {
40    fn from(err: std::io::Error) -> Self {
41        WolError::NetworkError(err)
42    }
43}
44
45impl From<std::num::ParseIntError> for WolError {
46    fn from(_: std::num::ParseIntError) -> Self {
47        WolError::Other("Failed to parse hexadecimal value in MAC address".to_string())
48    }
49}
50
51/// Options for configuring Wake-on-LAN packet sending
52#[derive(Debug, Clone)]
53pub struct WolOptions {
54    /// Broadcast address to send the packet to (default: 255.255.255.255)
55    pub broadcast_address: String,
56    /// Port to send the packet to (default: 65530)
57    pub port: u16,
58    /// Number of times to send the packet (default: 1)
59    pub retries: u32,
60}
61
62impl Default for WolOptions {
63    fn default() -> Self {
64        WolOptions {
65            broadcast_address: DEFAULT_BROADCAST_ADDRESS.to_string(),
66            port: DEFAULT_PORT,
67            retries: 1,
68        }
69    }
70}
71
72/// Wake-on-LAN client for sending magic packets
73#[derive(Debug)]
74pub struct WolClient {
75    options: WolOptions,
76}
77
78impl WolClient {
79    /// Create a new Wake-on-LAN client with default options
80    pub fn new() -> Self {
81        WolClient {
82            options: WolOptions::default(),
83        }
84    }
85
86    /// Create a new Wake-on-LAN client with custom options
87    pub fn with_options(options: WolOptions) -> Self {
88        WolClient { options }
89    }
90
91    /// Send a Wake-on-LAN packet to the specified MAC address
92    ///
93    /// # Arguments
94    ///
95    /// * `mac_address` - The MAC address of the target device (e.g., "01:23:45:67:89:AB" or "01-23-45-67-89-AB")
96    ///
97    /// # Returns
98    ///
99    /// Result indicating success or the specific error that occurred
100    pub fn wake(&self, mac_address: &str) -> Result<(), WolError> {
101        let frame = create_magic_packet(mac_address)?;
102        let socket = self.create_socket()?;
103        let target = format!("{}:{}", self.options.broadcast_address, self.options.port)
104            .to_socket_addrs()?
105            .next()
106            .ok_or_else(|| WolError::Other("Failed to resolve address".to_string()))?;
107
108        for _ in 0..self.options.retries {
109            socket.send_to(&frame, &target)?;
110        }
111
112        Ok(())
113    }
114
115    fn create_socket(&self) -> Result<UdpSocket, std::io::Error> {
116        let socket = UdpSocket::bind("0.0.0.0:0")?;
117        socket.set_broadcast(true)?;
118        Ok(socket)
119    }
120}
121
122/// Create a magic packet for the specified MAC address
123///
124/// The magic packet consists of 6 bytes of 0xFF followed by the target MAC address repeated 16 times.
125fn create_magic_packet(mac_address: &str) -> Result<Vec<u8>, WolError> {
126    let mac_bytes = parse_mac_address(mac_address)?;
127
128    // Create the magic packet (6 bytes of 0xFF + MAC repeated 16 times)
129    let mut frame: Vec<u8> = Vec::with_capacity(6 + 16 * 6);
130    frame.extend_from_slice(&[0xFF; 6]);
131
132    for _ in 0..16 {
133        frame.extend_from_slice(&mac_bytes);
134    }
135
136    Ok(frame)
137}
138
139/// Parse a MAC address string into bytes
140fn parse_mac_address(mac_address: &str) -> Result<[u8; 6], WolError> {
141    let mac_parts: Vec<&str> = mac_address.split(|c| c == ':' || c == '-').collect();
142
143    if mac_parts.len() != 6 {
144        return Err(WolError::InvalidMacAddress(mac_address.to_string()));
145    }
146
147    let mut mac_bytes = [0u8; 6];
148    for (i, part) in mac_parts.iter().enumerate() {
149        mac_bytes[i] = u8::from_str_radix(part, 16)
150            .map_err(|_| WolError::InvalidMacAddress(mac_address.to_string()))?;
151    }
152
153    Ok(mac_bytes)
154}