mac_address/
lib.rs

1//! `mac_address` provides a cross platform way to retrieve the MAC address of
2//! network hardware. See [the Wikipedia
3//! entry](https://en.wikipedia.org/wiki/MAC_address) for more information.
4//!
5//! Supported platforms: Linux, Windows, MacOS, FreeBSD, NetBSD
6
7#![deny(missing_docs)]
8
9#[cfg(target_os = "windows")]
10#[path = "windows.rs"]
11mod os;
12
13#[cfg(any(
14    target_os = "linux",
15    target_os = "macos",
16    target_os = "freebsd",
17    target_os = "netbsd",
18    target_os = "openbsd",
19    target_os = "android",
20    target_os = "illumos",
21))]
22#[path = "linux.rs"]
23mod os;
24
25mod iter;
26pub use iter::MacAddressIterator;
27
28/// Possible errors when attempting to retrieve a MAC address.
29///
30/// Eventually will expose more detailed error information.
31#[derive(Debug)]
32pub enum MacAddressError {
33    /// Signifies an internal API error has occurred.
34    InternalError,
35}
36
37#[cfg(any(
38    target_os = "linux",
39    target_os = "macos",
40    target_os = "freebsd",
41    target_os = "netbsd",
42    target_os = "openbsd",
43    target_os = "android",
44    target_os = "illumos",
45))]
46impl From<nix::Error> for MacAddressError {
47    fn from(_: nix::Error) -> MacAddressError {
48        MacAddressError::InternalError
49    }
50}
51
52impl std::fmt::Display for MacAddressError {
53    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54        f.write_str(match self {
55            MacAddressError::InternalError => "Internal API error",
56        })
57    }
58}
59
60impl std::error::Error for MacAddressError {}
61
62/// An error that may occur when parsing a MAC address string.
63#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
64pub enum MacParseError {
65    /// Parsing of the MAC address contained an invalid digit.
66    InvalidDigit,
67    /// The MAC address did not have the correct length.
68    InvalidLength,
69}
70
71impl std::fmt::Display for MacParseError {
72    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
73        f.write_str(match *self {
74            MacParseError::InvalidDigit => "invalid digit",
75            MacParseError::InvalidLength => "invalid length",
76        })
77    }
78}
79
80impl std::error::Error for MacParseError {}
81
82impl From<core::num::ParseIntError> for MacParseError {
83    fn from(_: core::num::ParseIntError) -> Self {
84        MacParseError::InvalidDigit
85    }
86}
87
88/// Contains the individual bytes of the MAC address.
89#[derive(Debug, Clone, Copy, PartialEq, Default, Eq, PartialOrd, Ord, Hash)]
90#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
91#[cfg_attr(feature = "serde", serde(try_from = "std::borrow::Cow<'_, str>"))]
92pub struct MacAddress {
93    bytes: [u8; 6],
94}
95
96impl MacAddress {
97    /// Creates a new `MacAddress` struct from the given bytes.
98    pub fn new(bytes: [u8; 6]) -> MacAddress {
99        MacAddress { bytes }
100    }
101}
102
103impl From<[u8; 6]> for MacAddress {
104    fn from(v: [u8; 6]) -> Self {
105        MacAddress::new(v)
106    }
107}
108
109#[cfg(feature = "serde")]
110impl serde::Serialize for MacAddress {
111    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
112    where
113        S: serde::Serializer,
114    {
115        serializer.collect_str(self)
116    }
117}
118
119/// Calls the OS-specific function for retrieving the MAC address of the first
120/// network device containing one, ignoring local-loopback.
121pub fn get_mac_address() -> Result<Option<MacAddress>, MacAddressError> {
122    let bytes = os::get_mac(None)?;
123
124    Ok(bytes.map(|b| MacAddress { bytes: b }))
125}
126
127/// Attempts to look up the MAC address of an interface via the specified name.
128/// **NOTE**: On Windows, this uses the `FriendlyName` field of the adapter, which
129/// is the same name shown in the "Network Connections" Control Panel screen.
130pub fn mac_address_by_name(name: &str) -> Result<Option<MacAddress>, MacAddressError> {
131    let bytes = os::get_mac(Some(name))?;
132
133    Ok(bytes.map(|b| MacAddress { bytes: b }))
134}
135
136/// Attempts to look up the interface name via MAC address.
137pub fn name_by_mac_address(mac: &MacAddress) -> Result<Option<String>, MacAddressError> {
138    os::get_ifname(&mac.bytes)
139}
140
141impl MacAddress {
142    /// Returns the array of MAC address bytes.
143    pub fn bytes(self) -> [u8; 6] {
144        self.bytes
145    }
146}
147
148impl std::str::FromStr for MacAddress {
149    type Err = MacParseError;
150
151    fn from_str(input: &str) -> Result<Self, Self::Err> {
152        let mut array = [0u8; 6];
153
154        // expect the `str` to be ASCII since it'll probably fail to parse
155        // anyway, this also asserts that each character in the string is only
156        // one byte in length which is necessary for the `match` below
157        if !input.is_ascii() {
158            // kind of hacky, but without `#[non_exhaustive]` on `MacParseError`
159            // adding a new variant is technically a breaking change, ugh...
160            return Err(MacParseError::InvalidLength);
161        }
162
163        match input.len() {
164            // MAC address with separators, e.g. 00:11:22:33:44:55
165            17 => {
166                array
167                    .iter_mut()
168                    .zip(input.split(|c| c == ':' || c == '-'))
169                    .try_for_each::<_, Result<(), MacParseError>>(|(b, s)| {
170                        *b = u8::from_str_radix(s, 16)?;
171                        Ok(())
172                    })?;
173            }
174            // MAC address without separators, e.g. 001122334455
175            12 => {
176                array
177                    .iter_mut()
178                    .zip((0..6).map(|i| &input[i * 2..=i * 2 + 1]))
179                    .try_for_each::<_, Result<(), MacParseError>>(|(b, s)| {
180                        *b = u8::from_str_radix(s, 16)?;
181                        Ok(())
182                    })?;
183            }
184            _ => return Err(MacParseError::InvalidLength),
185        }
186
187        Ok(MacAddress::new(array))
188    }
189}
190
191impl std::convert::TryFrom<&'_ str> for MacAddress {
192    type Error = MacParseError;
193
194    fn try_from(value: &str) -> Result<Self, Self::Error> {
195        value.parse()
196    }
197}
198
199impl std::convert::TryFrom<std::borrow::Cow<'_, str>> for MacAddress {
200    type Error = MacParseError;
201
202    fn try_from(value: std::borrow::Cow<'_, str>) -> Result<Self, Self::Error> {
203        value.parse()
204    }
205}
206
207impl std::fmt::Display for MacAddress {
208    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
209        let _ = write!(
210            f,
211            "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}",
212            self.bytes[0],
213            self.bytes[1],
214            self.bytes[2],
215            self.bytes[3],
216            self.bytes[4],
217            self.bytes[5]
218        );
219
220        Ok(())
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn parse_str_colon() {
230        let string = "80:FA:5B:41:10:6B";
231        let address = string.parse::<MacAddress>().unwrap();
232        assert_eq!(address.bytes(), [128, 250, 91, 65, 16, 107]);
233        assert_eq!(&format!("{}", address), string);
234    }
235
236    #[test]
237    fn parse_str_hyphen() {
238        let string = "01-23-45-67-89-AB";
239        let address = string.parse::<MacAddress>().unwrap();
240        assert_eq!(address.bytes(), [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]);
241        assert_eq!(format!("{}", address), string.replace('-', ":"));
242    }
243
244    #[test]
245    fn parse_str_no_sep() {
246        let string = "4827e24425d8";
247        let address = string.parse::<MacAddress>().unwrap();
248        assert_eq!(address.bytes(), [0x48, 0x27, 0xE2, 0x44, 0x25, 0xD8]);
249    }
250
251    #[test]
252    fn parse_invalid_length() {
253        let string = "80:FA:5B:41:10:6B:AC";
254        let address = string.parse::<MacAddress>().unwrap_err();
255        assert_eq!(MacParseError::InvalidLength, address);
256
257        let string = "80:FA:5B:41";
258        let address = string.parse::<MacAddress>().unwrap_err();
259        assert_eq!(MacParseError::InvalidLength, address);
260
261        let string = "80FA5B41";
262        let address = string.parse::<MacAddress>().unwrap_err();
263        assert_eq!(MacParseError::InvalidLength, address);
264
265        let string = "80:FÁ:5B:41:10:6B";
266        let address = string.parse::<MacAddress>().unwrap_err();
267        assert_eq!(MacParseError::InvalidLength, address);
268    }
269
270    #[test]
271    fn parse_invalid_digit() {
272        let string = "80:FA:ZZ:41:10:6B";
273        let address = string.parse::<MacAddress>().unwrap_err();
274        assert_eq!(MacParseError::InvalidDigit, address);
275    }
276
277    #[test]
278    fn parse_invalid_separator() {
279        let string = "80|FA|AA|41|10|6B";
280        let address = string.parse::<MacAddress>().unwrap_err();
281        assert_eq!(MacParseError::InvalidDigit, address);
282    }
283
284    #[cfg(feature = "serde")]
285    #[test]
286    fn serde_works() {
287        use serde::{Deserialize, Serialize};
288        use serde_test::{assert_tokens, Token};
289        let mac: MacAddress = "80:FA:5B:41:10:6B".parse().unwrap();
290
291        assert_tokens(&mac, &[Token::BorrowedStr("80:FA:5B:41:10:6B")]);
292
293        #[derive(Serialize, Deserialize)]
294        struct Test {
295            mac: MacAddress,
296        }
297
298        assert_eq!(
299            serde_json::to_string(&Test { mac }).unwrap(),
300            serde_json::to_string::<Test>(
301                &serde_json::from_str("{ \"mac\": \"80:FA:5B:41:10:6B\" }").unwrap()
302            )
303            .unwrap(),
304        );
305    }
306
307    #[cfg(feature = "serde")]
308    #[test]
309    fn serde_from_reader_works() {
310        use serde::{Deserialize, Serialize};
311        let mac: MacAddress = "80:FA:5B:41:10:6B".parse().unwrap();
312
313        #[derive(Serialize, Deserialize, PartialEq, Debug)]
314        struct Test {
315            mac: MacAddress,
316        }
317
318        assert_eq!(
319            Test { mac },
320            serde_json::from_reader(std::io::Cursor::new(r#"{ "mac": "80:FA:5B:41:10:6B" }"#))
321                .unwrap(),
322        );
323    }
324
325    #[test]
326    fn convert() {
327        for mac in MacAddressIterator::new().unwrap() {
328            let name = name_by_mac_address(&mac).unwrap().unwrap();
329            let mac2 = mac_address_by_name(&name).unwrap().unwrap();
330            assert_eq!(mac, mac2);
331        }
332    }
333}