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