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