advmac/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![cfg_attr(not(feature = "std"), no_std)]
4mod parser;
5
6#[cfg(feature = "serde")]
7use arrayvec::ArrayString;
8use core::fmt::{self, Debug, Display, Formatter};
9use core::str::FromStr;
10#[cfg(feature = "rand")]
11use rand::Rng;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14#[cfg(feature = "std")]
15use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
16
17#[derive(Eq, PartialEq, Debug, Clone, Copy)]
18pub enum ParseError {
19    InvalidMac,
20    InvalidLength { length: usize },
21}
22
23impl Display for ParseError {
24    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::InvalidMac => write!(f, "invalid MAC address"),
27            Self::InvalidLength { length } => write!(f, "invalid string length: {}", length),
28        }
29    }
30}
31
32#[cfg(feature = "std")]
33impl std::error::Error for ParseError {}
34
35#[derive(Eq, PartialEq, Debug, Clone, Copy)]
36pub enum IpError {
37    NotLinkLocal,
38    NotMulticast,
39}
40
41impl Display for IpError {
42    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::NotLinkLocal => write!(f, "not link-local address"),
45            Self::NotMulticast => write!(f, "not multicast address"),
46        }
47    }
48}
49
50#[cfg(feature = "std")]
51impl std::error::Error for IpError {}
52
53/// Maximum formatted size.
54///
55/// It is useful for creating a stack-allocated buffer `[u8; MAC_MAX_SIZE]`
56/// and formatting address into it using [MacAddr6::format_write] or [MacAddr8::format_write].
57pub const MAC_MAX_SIZE: usize = 23;
58/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Canonical].
59pub const MAC_CANONICAL_SIZE6: usize = 17;
60/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Canonical].
61pub const MAC_CANONICAL_SIZE8: usize = 23;
62/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::ColonNotation].
63pub const MAC_COLON_NOTATION_SIZE6: usize = 17;
64/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::ColonNotation].
65pub const MAC_COLON_NOTATION_SIZE8: usize = 23;
66/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::DotNotation].
67pub const MAC_DOT_NOTATION_SIZE6: usize = 14;
68/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::DotNotation].
69pub const MAC_DOT_NOTATION_SIZE8: usize = 19;
70/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Hexadecimal].
71pub const MAC_HEXADECIMAL_SIZE6: usize = 12;
72/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Hexadecimal].
73pub const MAC_HEXADECIMAL_SIZE8: usize = 16;
74/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Hexadecimal0x].
75pub const MAC_HEXADECIMAL0X_SIZE6: usize = 14;
76/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Hexadecimal0x].
77pub const MAC_HEXADECIMAL0X_SIZE8: usize = 18;
78
79#[derive(Copy, Clone, Eq, PartialEq)]
80pub enum MacAddrFormat {
81    /// `AA-BB-CC-DD-EE-FF` (17 bytes) or `AA-BB-CC-DD-EE-FF-GG-HH` (23 bytes)
82    Canonical,
83    /// `AA:BB:CC:DD:EE:FF` (17 bytes) or `AA:BB:CC:DD:EE:FF:GG:HH` (23 bytes)
84    ColonNotation,
85    /// `AABB.CCDD.EEFF` (14 bytes) or `AABB.CCDD.EEFF.GGHH` (19 bytes)
86    DotNotation,
87    /// `AABBCCDDEEFF` (12 bytes) or `AABBCCDDEEFFGGHH` (16 bytes)
88    Hexadecimal,
89    /// `0xAABBCCDDEEFF` (14 bytes) or `0xAABBCCDDEEFFGGHH` (18 bytes)
90    Hexadecimal0x,
91}
92
93macro_rules! mac_impl {
94    ($nm:ident, $sz:literal, $hex_sz:literal) => {
95        impl $nm {
96            pub const fn new(eui: [u8; $sz]) -> Self {
97                Self(eui)
98            }
99
100            #[cfg(feature = "rand")]
101            pub fn random() -> Self {
102                let mut result = Self::default();
103                rand::rngs::OsRng.fill(result.as_mut_slice());
104                result
105            }
106
107            pub const fn broadcast() -> Self {
108                Self([0xFF; $sz])
109            }
110
111            pub const fn nil() -> Self {
112                Self([0; $sz])
113            }
114
115            /// Sets *locally administered* flag
116            pub fn set_local(&mut self, v: bool) {
117                if v {
118                    self.0[0] |= 0b0000_0010;
119                } else {
120                    self.0[0] &= !0b0000_0010;
121                }
122            }
123
124            /// Returns the state of *locally administered* flag
125            pub const fn is_local(&self) -> bool {
126                (self.0[0] & 0b0000_0010) != 0
127            }
128
129            /// Sets *multicast* flag
130            pub fn set_multicast(&mut self, v: bool) {
131                if v {
132                    self.0[0] |= 0b0000_0001;
133                } else {
134                    self.0[0] &= !0b0000_0001;
135                }
136            }
137
138            /// Returns the state of *multicast* flag
139            pub const fn is_multicast(&self) -> bool {
140                (self.0[0] & 0b0000_0001) != 0
141            }
142
143            /// Returns [organizationally unique identifier (OUI)](https://en.wikipedia.org/wiki/Organizationally_unique_identifier) of this MAC address
144            pub const fn oui(&self) -> [u8; 3] {
145                [self.0[0], self.0[1], self.0[2]]
146            }
147
148            /// Sets [organizationally unique identifier (OUI)](https://en.wikipedia.org/wiki/Organizationally_unique_identifier) for this MAC address
149            pub fn set_oui(&mut self, oui: [u8; 3]) {
150                self.0[..3].copy_from_slice(&oui);
151            }
152
153            /// Returns internal array representation for this MAC address, consuming it
154            pub const fn to_array(self) -> [u8; $sz] {
155                self.0
156            }
157
158            /// Returns internal array representation for this MAC address as [u8] slice
159            pub const fn as_slice(&self) -> &[u8] {
160                &self.0
161            }
162
163            /// Returns internal array representation for this MAC address as mutable [u8] slice
164            pub fn as_mut_slice(&mut self) -> &mut [u8] {
165                &mut self.0
166            }
167
168            /// Returns internal array representation for this MAC address as [core::ffi::c_char] slice.
169            /// This can be useful in parsing `ifr_hwaddr`, for example.
170            pub const fn as_c_slice(&self) -> &[core::ffi::c_char] {
171                unsafe { &*(self.as_slice() as *const _ as *const [core::ffi::c_char]) }
172            }
173
174            /// Parse MAC address from string and return it as `MacAddr`.
175            /// This function can be used in const context, so MAC address can be parsed in compile-time.
176            pub const fn parse_str(s: &str) -> Result<Self, ParseError> {
177                match parser::MacParser::<$sz, $hex_sz>::parse(s) {
178                    Ok(v) => Ok(Self(v)),
179                    Err(e) => Err(e),
180                }
181            }
182
183            /// Write MAC address to `impl core::fmt::Write`, which can be used in `no_std` environments.
184            ///
185            /// It can be used like this with [arrayvec::ArrayString] without allocations:
186            /// ```
187            /// use arrayvec::ArrayString;
188            /// use advmac::{MacAddr6, MacAddrFormat, MAC_CANONICAL_SIZE6};
189            ///
190            /// let mac = MacAddr6::parse_str("AA:BB:CC:DD:EE:FF").unwrap();
191            ///
192            /// let mut buf = ArrayString::<MAC_CANONICAL_SIZE6>::new();
193            /// mac.format_write(&mut buf, MacAddrFormat::Canonical).unwrap();
194            /// # assert_eq!(buf.as_str(), "AA-BB-CC-DD-EE-FF")
195            /// ```
196            pub fn format_write<T: fmt::Write>(
197                &self,
198                f: &mut T,
199                format: MacAddrFormat,
200            ) -> fmt::Result {
201                match format {
202                    MacAddrFormat::Canonical => self.write_internal(f, "", "-", "-"),
203                    MacAddrFormat::ColonNotation => self.write_internal(f, "", ":", ":"),
204                    MacAddrFormat::DotNotation => self.write_internal(f, "", "", "."),
205                    MacAddrFormat::Hexadecimal => self.write_internal(f, "", "", ""),
206                    MacAddrFormat::Hexadecimal0x => self.write_internal(f, "0x", "", ""),
207                }
208            }
209
210            /// Write MAC address to [String]. This function uses [Self::format_write] internally and
211            /// produces the same result, but in string form, which can be convenient in non-constrainted
212            /// environments.
213            #[cfg(feature = "std")]
214            pub fn format_string(&self, format: MacAddrFormat) -> String {
215                let mut buf = String::new();
216                self.format_write(&mut buf, format).unwrap();
217                buf
218            }
219        }
220
221        impl Display for $nm {
222            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
223                self.format_write(f, MacAddrFormat::Canonical)
224            }
225        }
226
227        impl Debug for $nm {
228            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
229                self.format_write(f, MacAddrFormat::Canonical)
230            }
231        }
232
233        impl From<[u8; $sz]> for $nm {
234            fn from(arr: [u8; $sz]) -> Self {
235                Self(arr)
236            }
237        }
238
239        impl TryFrom<&[u8]> for $nm {
240            type Error = ParseError;
241
242            fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
243                Ok(Self(value.try_into().map_err(|_| ParseError::InvalidMac)?))
244            }
245        }
246
247        #[cfg(not(target_arch = "aarch64"))]
248        impl TryFrom<&[core::ffi::c_char]> for $nm {
249            type Error = ParseError;
250
251            fn try_from(value: &[core::ffi::c_char]) -> Result<Self, Self::Error> {
252                Self::try_from(unsafe { &*(value as *const _ as *const [u8]) })
253            }
254        }
255
256        impl TryFrom<&str> for $nm {
257            type Error = ParseError;
258
259            fn try_from(value: &str) -> Result<Self, Self::Error> {
260                Self::parse_str(value)
261            }
262        }
263
264        #[cfg(feature = "std")]
265        impl TryFrom<String> for $nm {
266            type Error = ParseError;
267
268            fn try_from(value: String) -> Result<Self, Self::Error> {
269                Self::parse_str(&value)
270            }
271        }
272
273        impl FromStr for $nm {
274            type Err = ParseError;
275
276            fn from_str(s: &str) -> Result<Self, Self::Err> {
277                Self::parse_str(s)
278            }
279        }
280
281        #[cfg(feature = "serde")]
282        impl Serialize for $nm {
283            fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
284                let mut buf = ArrayString::<MAC_MAX_SIZE>::new();
285                self.format_write(&mut buf, MacAddrFormat::Canonical)
286                    .unwrap();
287                s.serialize_str(buf.as_ref())
288            }
289        }
290
291        #[cfg(feature = "serde")]
292        impl<'de> Deserialize<'de> for $nm {
293            fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
294                Self::from_str(ArrayString::<MAC_MAX_SIZE>::deserialize(d)?.as_ref())
295                    .map_err(serde::de::Error::custom)
296            }
297        }
298    };
299}
300
301/// MAC address, represented as EUI-48
302#[repr(transparent)]
303#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
304pub struct MacAddr6([u8; 6]);
305/// MAC address, represented as EUI-64
306#[repr(transparent)]
307#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
308pub struct MacAddr8([u8; 8]);
309
310mac_impl!(MacAddr6, 6, 12);
311mac_impl!(MacAddr8, 8, 16);
312
313impl MacAddr6 {
314    pub const fn to_modified_eui64(self) -> MacAddr8 {
315        let b = self.to_array();
316        MacAddr8([b[0] ^ 0b00000010, b[1], b[2], 0xFF, 0xFE, b[3], b[4], b[5]])
317    }
318
319    pub const fn try_from_modified_eui64(eui64: MacAddr8) -> Result<Self, IpError> {
320        let b = eui64.to_array();
321        if (b[3] == 0xFF) | (b[4] == 0xFE) {
322            Ok(Self([b[0] ^ 0b00000010, b[1], b[2], b[5], b[6], b[7]]))
323        } else {
324            Err(IpError::NotLinkLocal)
325        }
326    }
327
328    #[cfg(feature = "std")]
329    pub const fn to_link_local_ipv6(self) -> Ipv6Addr {
330        let mac64 = self.to_modified_eui64().to_array();
331
332        Ipv6Addr::new(
333            0xFE80,
334            0x0000,
335            0x0000,
336            0x0000,
337            ((mac64[0] as u16) << 8) + mac64[1] as u16,
338            ((mac64[2] as u16) << 8) + mac64[3] as u16,
339            ((mac64[4] as u16) << 8) + mac64[5] as u16,
340            ((mac64[6] as u16) << 8) + mac64[7] as u16,
341        )
342    }
343
344    #[cfg(feature = "std")]
345    pub const fn try_from_link_local_ipv6(ip: Ipv6Addr) -> Result<Self, IpError> {
346        let octets = ip.octets();
347        if (octets[0] != 0xFE)
348            | (octets[1] != 0x80)
349            | (octets[2] != 0x00)
350            | (octets[3] != 0x00)
351            | (octets[4] != 0x00)
352            | (octets[5] != 0x00)
353            | (octets[6] != 0x00)
354            | (octets[7] != 0x00)
355            | (octets[11] != 0xFF)
356            | (octets[12] != 0xFE)
357        {
358            return Err(IpError::NotLinkLocal);
359        }
360
361        Ok(Self([
362            octets[8] ^ 0b00000010,
363            octets[9],
364            octets[10],
365            octets[13],
366            octets[14],
367            octets[15],
368        ]))
369    }
370
371    #[cfg(feature = "std")]
372    pub const fn try_from_multicast_ipv4(ip: Ipv4Addr) -> Result<Self, IpError> {
373        if !ip.is_multicast() {
374            return Err(IpError::NotMulticast);
375        }
376        let b = ip.octets();
377        Ok(Self::new([0x01, 0x00, 0x5E, b[1] & 0x7F, b[2], b[3]]))
378    }
379
380    #[cfg(feature = "std")]
381    pub const fn try_from_multicast_ipv6(ip: Ipv6Addr) -> Result<Self, IpError> {
382        if !ip.is_multicast() {
383            return Err(IpError::NotMulticast);
384        }
385        let b = ip.octets();
386        Ok(Self::new([0x33, 0x33, b[12], b[13], b[14], b[15]]))
387    }
388
389    #[cfg(feature = "std")]
390    pub const fn try_from_multicast_ip(ip: IpAddr) -> Result<Self, IpError> {
391        match ip {
392            IpAddr::V4(ip) => Self::try_from_multicast_ipv4(ip),
393            IpAddr::V6(ip) => Self::try_from_multicast_ipv6(ip),
394        }
395    }
396}
397
398impl MacAddr6 {
399    // String representations
400    fn write_internal<T: fmt::Write>(
401        &self,
402        f: &mut T,
403        pre: &str,
404        sep: &str,
405        sep2: &str,
406    ) -> fmt::Result {
407        write!(
408            f,
409            "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}",
410            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
411        )
412    }
413}
414
415impl MacAddr8 {
416    // String representations
417    fn write_internal<T: fmt::Write>(
418        &self,
419        f: &mut T,
420        pre: &str,
421        sep: &str,
422        sep2: &str,
423    ) -> fmt::Result {
424        write!(
425            f,
426            "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}",
427            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
428        )
429    }
430}
431
432/// Convenience macro for creating [MacAddr6] in compile-time.
433///
434/// Example:
435/// ```
436/// use advmac::{mac6, MacAddr6};
437/// const MAC6: MacAddr6 = mac6!("11:22:33:44:55:66");
438/// # assert_eq!(MAC6.to_array(), [0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
439/// ```
440#[macro_export]
441macro_rules! mac6 {
442    ($s:expr) => {
443        match $crate::MacAddr6::parse_str($s) {
444            Ok(mac) => mac,
445            Err(_) => panic!("Invalid MAC address"),
446        }
447    };
448}
449
450/// Convenience macro for creating [MacAddr8] in compile-time.
451///
452/// Example:
453/// ```
454/// use advmac::{mac8, MacAddr8};
455/// const MAC8: MacAddr8 = mac8!("11:22:33:44:55:66:77:88");
456/// # assert_eq!(MAC8.to_array(), [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
457/// ```
458#[macro_export]
459macro_rules! mac8 {
460    ($s:expr) => {
461        match $crate::MacAddr8::parse_str($s) {
462            Ok(mac) => mac,
463            Err(_) => panic!("Invalid MAC address"),
464        }
465    };
466}
467
468#[cfg(test)]
469mod test {
470    #[test]
471    fn test_flags_roundtrip() {
472        let mut addr = mac6!("50:74:f2:b1:a8:7f");
473        assert!(!addr.is_local());
474        assert!(!addr.is_multicast());
475
476        addr.set_multicast(true);
477        assert!(!addr.is_local());
478        assert!(addr.is_multicast());
479
480        addr.set_local(true);
481        assert!(addr.is_local());
482        assert!(addr.is_multicast());
483
484        addr.set_multicast(false);
485        assert!(addr.is_local());
486        assert!(!addr.is_multicast());
487
488        addr.set_local(false);
489        assert!(!addr.is_local());
490        assert!(!addr.is_multicast());
491    }
492}