mac_addr/
addr.rs

1#[cfg(all(not(feature = "std"), feature = "alloc"))]
2extern crate alloc;
3
4#[cfg(all(not(feature = "std"), feature = "alloc"))]
5use alloc::format;
6
7use crate::error::ParseMacAddrError;
8use core::fmt;
9use core::str::FromStr;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
13
14#[cfg(all(feature = "alloc", not(feature = "std")))]
15use alloc as alloc_mod;
16#[cfg(feature = "std")]
17use std as alloc_mod;
18
19#[cfg(any(feature = "std", feature = "alloc"))]
20use alloc_mod::string::String;
21
22/// 48-bit MAC address (IEEE EUI-48).
23#[repr(C)]
24#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
25pub struct MacAddr(pub u8, pub u8, pub u8, pub u8, pub u8, pub u8);
26
27impl MacAddr {
28    /// Constructs a new [`MacAddr`] from six octets.
29    #[inline]
30    pub fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> MacAddr {
31        MacAddr(a, b, c, d, e, f)
32    }
33
34    /// Constructs from a `[u8; 6]` array.
35    #[inline]
36    pub fn from_octets(octets: [u8; 6]) -> MacAddr {
37        MacAddr(
38            octets[0], octets[1], octets[2], octets[3], octets[4], octets[5],
39        )
40    }
41
42    /// Returns the 6 octets backing this address.
43    #[inline]
44    pub fn octets(&self) -> [u8; 6] {
45        [self.0, self.1, self.2, self.3, self.4, self.5]
46    }
47
48    /// Returns a colon-separated lowercase hex string (`xx:xx:xx:xx:xx:xx`).
49    #[cfg(any(feature = "std", feature = "alloc"))]
50    #[inline]
51    pub fn address(&self) -> String {
52        format!(
53            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
54            self.0, self.1, self.2, self.3, self.4, self.5
55        )
56    }
57
58    /// Returns the all-zeros address.
59    #[inline]
60    pub fn zero() -> MacAddr {
61        MacAddr(0, 0, 0, 0, 0, 0)
62    }
63
64    /// Returns the broadcast address (`ff:ff:ff:ff:ff:ff`).
65    #[inline]
66    pub fn broadcast() -> MacAddr {
67        MacAddr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff)
68    }
69
70    /// Parses a fixed-width hex string (`xx:xx:xx:xx:xx:xx`).
71    #[inline]
72    pub fn from_hex_format(hex_mac_addr: &str) -> MacAddr {
73        if hex_mac_addr.len() != 17 {
74            return MacAddr::zero();
75        }
76        let mut fields = hex_mac_addr.split(':');
77        let o1 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
78        let o2 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
79        let o3 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
80        let o4 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
81        let o5 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
82        let o6 = u8::from_str_radix(fields.next().unwrap_or_default(), 16).unwrap_or(0);
83        MacAddr(o1, o2, o3, o4, o5, o6)
84    }
85
86    #[inline]
87    pub fn is_broadcast(&self) -> bool {
88        self.0 == 0xff
89            && self.1 == 0xff
90            && self.2 == 0xff
91            && self.3 == 0xff
92            && self.4 == 0xff
93            && self.5 == 0xff
94    }
95
96    /// Returns `true` if the address is multicast.
97    #[inline]
98    pub fn is_multicast(&self) -> bool {
99        self.0 & 0x01 == 0x01
100    }
101
102    /// Returns `true` if the address is unicast.
103    #[inline]
104    pub fn is_unicast(&self) -> bool {
105        !self.is_multicast() && !self.is_broadcast()
106    }
107
108    /// Returns `true` if the address is locally administered.
109    #[inline]
110    pub fn is_locally_administered(&self) -> bool {
111        self.0 & 0x02 == 0x02
112    }
113
114    /// Returns `true` if the address is universally administered.
115    #[inline]
116    pub fn is_universal(&self) -> bool {
117        !self.is_locally_administered()
118    }
119
120    /// Returns the OUI (first 3 octets).
121    #[inline]
122    pub fn oui(&self) -> [u8; 3] {
123        [self.0, self.1, self.2]
124    }
125}
126
127impl fmt::Display for MacAddr {
128    /// Lowercase hex with `:` separators.
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        let _ = write!(
131            f,
132            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
133            self.0, self.1, self.2, self.3, self.4, self.5
134        );
135        Ok(())
136    }
137}
138
139impl fmt::LowerHex for MacAddr {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(
142            f,
143            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
144            self.0, self.1, self.2, self.3, self.4, self.5
145        )
146    }
147}
148
149impl fmt::UpperHex for MacAddr {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(
152            f,
153            "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
154            self.0, self.1, self.2, self.3, self.4, self.5
155        )
156    }
157}
158
159impl FromStr for MacAddr {
160    type Err = ParseMacAddrError;
161
162    fn from_str(s: &str) -> Result<MacAddr, ParseMacAddrError> {
163        let mut parts = [0u8; 6];
164        let mut i = 0;
165        for split in s.split(':') {
166            if i == 6 {
167                return Err(ParseMacAddrError::TooManyComponents);
168            }
169            match u8::from_str_radix(split, 16) {
170                Ok(b) if !split.is_empty() => parts[i] = b,
171                _ => return Err(ParseMacAddrError::InvalidComponent),
172            }
173            i += 1;
174        }
175        if i == 6 {
176            Ok(MacAddr(
177                parts[0], parts[1], parts[2], parts[3], parts[4], parts[5],
178            ))
179        } else {
180            Err(ParseMacAddrError::TooFewComponents)
181        }
182    }
183}
184
185#[cfg(feature = "serde")]
186impl Serialize for MacAddr {
187    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
188        if serializer.is_human_readable() {
189            serializer.collect_str(self)
190        } else {
191            serializer.serialize_bytes(&[self.0, self.1, self.2, self.3, self.4, self.5])
192        }
193    }
194}
195
196#[cfg(feature = "serde")]
197impl<'de> Deserialize<'de> for MacAddr {
198    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
199        struct MacAddrVisitor;
200        impl<'de> de::Visitor<'de> for MacAddrVisitor {
201            type Value = MacAddr;
202
203            fn visit_str<E: de::Error>(self, value: &str) -> Result<MacAddr, E> {
204                value.parse().map_err(E::custom)
205            }
206            fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<MacAddr, E> {
207                if v.len() == 6 {
208                    Ok(MacAddr::new(v[0], v[1], v[2], v[3], v[4], v[5]))
209                } else {
210                    Err(E::invalid_length(v.len(), &self))
211                }
212            }
213            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
214                f.write_str("either a string MAC address or 6-byte array")
215            }
216        }
217
218        if deserializer.is_human_readable() {
219            deserializer.deserialize_str(MacAddrVisitor)
220        } else {
221            deserializer.deserialize_bytes(MacAddrVisitor)
222        }
223    }
224}
225
226impl From<[u8; 6]> for MacAddr {
227    #[inline]
228    fn from(v: [u8; 6]) -> Self {
229        MacAddr::from_octets(v)
230    }
231}
232
233impl From<MacAddr> for [u8; 6] {
234    #[inline]
235    fn from(m: MacAddr) -> Self {
236        m.octets()
237    }
238}
239
240impl TryFrom<&[u8]> for MacAddr {
241    type Error = ();
242
243    #[inline]
244    fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
245        if s.len() == 6 {
246            Ok(MacAddr::new(s[0], s[1], s[2], s[3], s[4], s[5]))
247        } else {
248            Err(())
249        }
250    }
251}
252
253impl AsRef<[u8; 6]> for MacAddr {
254    /// # Safety
255    /// This is a plain `repr(Rust)` tuple struct of six `u8`s.
256    /// Reinterpreting its memory as `[u8; 6]` is layout-compatible for all stable Rust targets.
257    #[inline]
258    fn as_ref(&self) -> &[u8; 6] {
259        unsafe { &*(self as *const MacAddr as *const [u8; 6]) }
260    }
261}