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