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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! `mac_address` provides a cross platform way to retrieve the MAC address of
//! network hardware. See [the Wikipedia
//! entry](https://en.wikipedia.org/wiki/MAC_address) for more information.
//!
//! Supported platforms: Linux, Windows, MacOS, FreeBSD

#![deny(missing_docs)]

#[cfg(target_os = "windows")]
#[path = "windows.rs"]
mod os;

#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
#[path = "linux.rs"]
mod os;

mod iter;
pub use iter::MacAddressIterator;

/// Possible errors when attempting to retrieve a MAC address.
///
/// Eventually will expose more detailed error information.
#[derive(Debug)]
pub enum MacAddressError {
    /// Signifies an internal API error has occurred.
    InternalError,
}

#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
impl From<nix::Error> for MacAddressError {
    fn from(_: nix::Error) -> MacAddressError {
        MacAddressError::InternalError
    }
}

impl std::fmt::Display for MacAddressError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        use MacAddressError::*;

        write!(
            f,
            "{}",
            match self {
                InternalError => "Internal API error",
            }
        )?;

        Ok(())
    }
}

impl std::error::Error for MacAddressError {
    fn description(&self) -> &str {
        use MacAddressError::*;

        match self {
            InternalError => "Internal API error",
        }
    }
}

/// An error that may occur when parsing a MAC address string.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum MacParseError {
    /// Parsing of the MAC address contained an invalid digit.
    InvalidDigit,
    /// The MAC address did not have the correct length.
    InvalidLength,
}

impl std::fmt::Display for MacParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.write_str(match *self {
            MacParseError::InvalidDigit => "invalid digit",
            MacParseError::InvalidLength => "invalid length",
        })
    }
}

impl std::error::Error for MacParseError {}

/// Contains the individual bytes of the MAC address.
#[derive(Debug, Clone, Copy, PartialEq, Default, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "&str"))]
pub struct MacAddress {
    bytes: [u8; 6],
}

impl MacAddress {
    /// Creates a new `MacAddress` struct from the given bytes.
    pub fn new(bytes: [u8; 6]) -> MacAddress {
        MacAddress { bytes }
    }
}

impl From<[u8; 6]> for MacAddress {
    fn from(v: [u8; 6]) -> Self {
        MacAddress::new(v)
    }
}

#[cfg(feature = "serde")]
impl serde::Serialize for MacAddress {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_str(self)
    }
}

/// Calls the OS-specific function for retrieving the MAC address of the first
/// network device containing one, ignoring local-loopback.
pub fn get_mac_address() -> Result<Option<MacAddress>, MacAddressError> {
    let bytes = os::get_mac(None)?;

    Ok(match bytes {
        Some(b) => Some(MacAddress { bytes: b }),
        None => None,
    })
}

/// Attempts to look up the MAC address of an interface via the specified name.
/// **NOTE**: On Windows, this uses the `FriendlyName` field of the adapter, which
/// is the same name shown in the "Network Connections" Control Panel screen.
pub fn mac_address_by_name(name: &str) -> Result<Option<MacAddress>, MacAddressError> {
    let bytes = os::get_mac(Some(name))?;

    Ok(match bytes {
        Some(b) => Some(MacAddress { bytes: b }),
        None => None,
    })
}

/// Attempts to look up the interface name via MAC address.
pub fn name_by_mac_address(mac: &MacAddress) -> Result<Option<String>, MacAddressError> {
    os::get_ifname(&mac.bytes)
}

impl MacAddress {
    /// Returns the array of MAC address bytes.
    pub fn bytes(self) -> [u8; 6] {
        self.bytes
    }
}

impl std::str::FromStr for MacAddress {
    type Err = MacParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        let mut array = [0u8; 6];

        let mut nth = 0;
        for byte in input.split(|c| c == ':' || c == '-') {
            if nth == 6 {
                return Err(MacParseError::InvalidLength);
            }

            array[nth] = u8::from_str_radix(byte, 16).map_err(|_| MacParseError::InvalidDigit)?;

            nth += 1;
        }

        if nth != 6 {
            return Err(MacParseError::InvalidLength);
        }

        Ok(MacAddress::new(array))
    }
}

impl std::convert::TryFrom<&'_ str> for MacAddress {
    type Error = MacParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        value.parse()
    }
}

impl std::fmt::Display for MacAddress {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let _ = write!(
            f,
            "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}",
            self.bytes[0],
            self.bytes[1],
            self.bytes[2],
            self.bytes[3],
            self.bytes[4],
            self.bytes[5]
        );

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_str_colon() {
        let string = "80:FA:5B:41:10:6B";
        let address = string.parse::<MacAddress>().unwrap();
        assert_eq!(address.bytes(), [128, 250, 91, 65, 16, 107]);
        assert_eq!(&format!("{}", address), string);
    }

    #[test]
    fn parse_str_hyphen() {
        let string = "01-23-45-67-89-AB";
        let address = string.parse::<MacAddress>().unwrap();
        assert_eq!(address.bytes(), [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]);
        assert_eq!(format!("{}", address), string.replace("-", ":"));
    }

    #[test]
    fn parse_invalid_length() {
        let string = "80:FA:5B:41:10:6B:AC";
        let address = string.parse::<MacAddress>().unwrap_err();
        assert_eq!(MacParseError::InvalidLength, address);

        let string = "80:FA:5B:41";
        let address = string.parse::<MacAddress>().unwrap_err();
        assert_eq!(MacParseError::InvalidLength, address);
    }

    #[test]
    fn parse_invalid_digit() {
        let string = "80:FA:ZZ:41:10:6B:AC";
        let address = string.parse::<MacAddress>().unwrap_err();
        assert_eq!(MacParseError::InvalidDigit, address);
    }

    #[cfg(feature = "serde")]
    #[test]
    fn serde_works() {
        use serde::{Deserialize, Serialize};
        use serde_test::{assert_tokens, Token};
        let mac: MacAddress = "80:FA:5B:41:10:6B".parse().unwrap();

        assert_tokens(&mac, &[Token::BorrowedStr("80:FA:5B:41:10:6B")]);

        #[derive(Serialize, Deserialize)]
        struct Test {
            mac: MacAddress,
        }

        assert_eq!(
            serde_json::to_string(&Test { mac }).unwrap(),
            serde_json::to_string::<Test>(
                &serde_json::from_str("{ \"mac\": \"80:FA:5B:41:10:6B\" }").unwrap()
            )
            .unwrap(),
        );
    }

    #[test]
    fn convert() {
        for mac in MacAddressIterator::new().unwrap() {
            let name = name_by_mac_address(&mac).unwrap().unwrap();
            let mac2 = mac_address_by_name(&name).unwrap().unwrap();
            assert_eq!(mac, mac2);
        }
    }
}