net_backend_resources/
mac_address.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! MAC address type for sending across mesh channels and displaying with
5//! `inspect`.
6
7use inspect::Inspect;
8use mesh::payload::Protobuf;
9use std::fmt::Display;
10use std::str::FromStr;
11use thiserror::Error;
12
13/// A 48-bit Ethernet MAC address.
14#[derive(Debug, Protobuf, Inspect, Clone, Copy, PartialEq, Eq, Hash)]
15#[mesh(transparent)]
16#[inspect(display)]
17#[repr(transparent)]
18pub struct MacAddress([u8; 6]);
19
20impl MacAddress {
21    /// Returns a new MAC address from the given bytes.
22    pub const fn new(value: [u8; 6]) -> Self {
23        Self(value)
24    }
25
26    /// Returns the bytes of the MAC address.
27    pub const fn to_bytes(self) -> [u8; 6] {
28        self.0
29    }
30}
31
32impl From<[u8; 6]> for MacAddress {
33    fn from(value: [u8; 6]) -> Self {
34        Self::new(value)
35    }
36}
37
38impl From<MacAddress> for [u8; 6] {
39    fn from(value: MacAddress) -> Self {
40        value.0
41    }
42}
43
44impl Display for MacAddress {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(
47            f,
48            "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
49            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
50        )
51    }
52}
53
54/// Error returned when parsing a [`MacAddress`] fails.
55#[derive(Debug, Error)]
56#[error("invalid mac address")]
57pub struct InvalidMacAddress;
58
59impl FromStr for MacAddress {
60    type Err = InvalidMacAddress;
61
62    fn from_str(val: &str) -> Result<Self, InvalidMacAddress> {
63        if val.len() != 17 {
64            return Err(InvalidMacAddress);
65        }
66        let sep = val.as_bytes()[2];
67        if sep != b'-' && sep != b':' {
68            return Err(InvalidMacAddress);
69        }
70        let mut mac_address = [0u8; 6];
71        for (src, dst) in val.split(sep as char).zip(&mut mac_address) {
72            if src.len() != 2 {
73                return Err(InvalidMacAddress);
74            }
75            *dst = u8::from_str_radix(src, 16).map_err(|_| InvalidMacAddress)?;
76        }
77        Ok(MacAddress(mac_address))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::mac_address::InvalidMacAddress;
84    use crate::mac_address::MacAddress;
85    use std::str::FromStr;
86
87    #[test]
88    fn test_parse_mac_address() {
89        let bad_macs = &[
90            "",
91            "00:00:00-00-00-00",
92            "00:00:00:00:00",
93            "00:00:00:00:00:00:",
94            "00:00:00:00:00:00:00",
95            "00:00:00:00:00::0",
96            "00:00:00:00:00:0g",
97        ];
98        for mac in bad_macs {
99            assert!(matches!(MacAddress::from_str(mac), Err(InvalidMacAddress)));
100        }
101
102        let good_macs = &[
103            ("00:00:00:00:00:00", [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
104            ("00-00-00-00-00-00", [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
105            ("ff:ff:ff:ff:ff:ff", [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
106            ("FF:FF:FF:FF:FF:FF", [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
107            ("01:23:45:67:89:ab", [0x01, 0x23, 0x45, 0x67, 0x89, 0xab]),
108            ("01-23-45-67-89-ab", [0x01, 0x23, 0x45, 0x67, 0x89, 0xab]),
109        ];
110        for &(mac, parsed) in good_macs {
111            assert_eq!(MacAddress::from_str(mac).unwrap().to_bytes(), parsed);
112        }
113    }
114}