1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Library for managing Wake-on-LAN packets.
//! # Example
//! ```
//! extern crate wakey;
//! let wol = wakey::WolPacket::from_string("01:02:03:04:05:06", ':');
//! match wol.send_magic() {
//!     Ok(_) => println!("Sent the magic packet!"),
//!     Err(_) => println!("Failed to send the magic packet!")
//! }
//! ```

extern crate hex;

use std::iter;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};

const MAC_SIZE: usize = 6;
const MAC_PER_MAGIC: usize = 16;

/// Wake-on-LAN packet
pub struct WolPacket {
    /// WOL packet bytes
    packet: Vec<u8>,
}

impl WolPacket {
    /// Creates WOL packet from byte MAC representation
    /// # Example
    /// ```
    /// extern crate wakey;
    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
    /// ```
    pub fn from_bytes(mac: &[u8]) -> WolPacket {
        static HEADER: [u8; 6] = [0xFF; 6];
        let mut packet = Vec::with_capacity(HEADER.len() + MAC_SIZE * MAC_PER_MAGIC);

        packet.extend(HEADER.iter());
        packet.extend(WolPacket::extend_mac(mac));

        WolPacket { packet }
    }

    /// Creates WOL packet from string MAC representation (e.x. 00:01:02:03:04:05)
    /// # Example
    /// ```
    /// extern crate wakey;
    /// let wol = wakey::WolPacket::from_string("00:01:02:03:04:05", ':');
    /// ```
    pub fn from_string(data: &str, sep: char) -> WolPacket {
        WolPacket::from_bytes(&WolPacket::mac_to_byte(data, sep))
    }

    /// Broadcasts the magic packet from / to default address
    /// Source: 0.0.0.0:0
    /// Destination 255.255.255.255:9
    /// # Example
    /// ```
    /// extern crate wakey;
    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
    /// wol.send_magic();
    /// ```
    pub fn send_magic(&self) -> std::io::Result<()> {
        self.send_magic_to(
            SocketAddr::from(([0, 0, 0, 0], 0)),
            SocketAddr::from(([255, 255, 255, 255], 9)),
        )
    }

    /// Broadcasts the magic packet from / to specified address.
    /// # Example
    /// ```
    /// extern crate wakey;
    /// use std::net::SocketAddr;
    /// let wol = wakey::WolPacket::from_bytes(&vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
    /// let src = SocketAddr::from(([0,0,0,0], 0));
    /// let dst = SocketAddr::from(([255,255,255,255], 9));
    /// wol.send_magic_to(src, dst);
    /// ```
    pub fn send_magic_to<A: ToSocketAddrs>(&self, src: A, dst: A) -> std::io::Result<()> {
        let udp_sock = UdpSocket::bind(src)?;
        udp_sock.set_broadcast(true)?;
        udp_sock.send_to(&self.packet, dst)?;

        Ok(())
    }

    /// Converts string representation of MAC address (e.x. 00:01:02:03:04:05) to raw bytes.
    /// Panics when input MAC is invalid (i.e. contains non-byte characters)
    fn mac_to_byte(data: &str, sep: char) -> Vec<u8> {
        data.split(sep)
            .map(|x| hex::decode(x).expect("Invalid mac!"))
            .flatten()
            .collect()
    }

    /// Extends the MAC address to fill the magic packet
    fn extend_mac(mac: &[u8]) -> Vec<u8> {
        iter::repeat(mac)
            .take(MAC_PER_MAGIC)
            .flatten()
            .cloned()
            .collect()
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn extend_mac_test() {
        let mac = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06];

        let extended_mac = super::WolPacket::extend_mac(&mac);

        assert_eq!(extended_mac.len(), super::MAC_PER_MAGIC * super::MAC_SIZE);
        assert_eq!(&extended_mac[90..], &mac[..]);
    }

    #[test]
    fn mac_to_byte_test() {
        let mac = "01:02:03:04:05:06";
        let result = super::WolPacket::mac_to_byte(mac, ':');

        assert_eq!(result, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
    }

    #[test]
    #[should_panic]
    fn mac_to_byte_invalid_chars_test() {
        let mac = "ZZ:02:03:04:05:06";
        super::WolPacket::mac_to_byte(mac, ':');
    }

    #[test]
    #[should_panic]
    fn mac_to_byte_invalid_separator_test() {
        let mac = "01002:03:04:05:06";
        super::WolPacket::mac_to_byte(mac, ':');
    }
}