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
35pub const MAC_MAX_SIZE: usize = 23;
40pub const MAC_CANONICAL_SIZE6: usize = 17;
42pub const MAC_CANONICAL_SIZE8: usize = 23;
44pub const MAC_COLON_NOTATION_SIZE6: usize = 17;
46pub const MAC_COLON_NOTATION_SIZE8: usize = 23;
48pub const MAC_DOT_NOTATION_SIZE6: usize = 14;
50pub const MAC_DOT_NOTATION_SIZE8: usize = 19;
52pub const MAC_HEXADECIMAL_SIZE6: usize = 12;
54pub const MAC_HEXADECIMAL_SIZE8: usize = 16;
56pub const MAC_HEXADECIMAL0X_SIZE6: usize = 14;
58pub const MAC_HEXADECIMAL0X_SIZE8: usize = 18;
60
61#[derive(Copy, Clone, Eq, PartialEq)]
62pub enum MacAddrFormat {
63 Canonical,
65 ColonNotation,
67 DotNotation,
69 Hexadecimal,
71 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 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 pub const fn is_local(&self) -> bool {
108 (self.0[0] & 0b0000_0010) != 0
109 }
110
111 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 pub const fn is_multicast(&self) -> bool {
122 (self.0[0] & 0b0000_0001) != 0
123 }
124
125 pub const fn oui(&self) -> [u8; 3] {
127 [self.0[0], self.0[1], self.0[2]]
128 }
129
130 pub fn set_oui(&mut self, oui: [u8; 3]) {
132 self.0[..3].copy_from_slice(&oui);
133 }
134
135 pub const fn to_array(self) -> [u8; $sz] {
137 self.0
138 }
139
140 pub const fn as_slice(&self) -> &[u8] {
142 &self.0
143 }
144
145 pub fn as_mut_slice(&mut self) -> &mut [u8] {
147 &mut self.0
148 }
149
150 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 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 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 #[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#[repr(transparent)]
284#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
285pub struct MacAddr6([u8; 6]);
286#[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 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 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#[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#[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}