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
53pub const MAC_MAX_SIZE: usize = 23;
58pub const MAC_CANONICAL_SIZE6: usize = 17;
60pub const MAC_CANONICAL_SIZE8: usize = 23;
62pub const MAC_COLON_NOTATION_SIZE6: usize = 17;
64pub const MAC_COLON_NOTATION_SIZE8: usize = 23;
66pub const MAC_DOT_NOTATION_SIZE6: usize = 14;
68pub const MAC_DOT_NOTATION_SIZE8: usize = 19;
70pub const MAC_HEXADECIMAL_SIZE6: usize = 12;
72pub const MAC_HEXADECIMAL_SIZE8: usize = 16;
74pub const MAC_HEXADECIMAL0X_SIZE6: usize = 14;
76pub const MAC_HEXADECIMAL0X_SIZE8: usize = 18;
78
79#[derive(Copy, Clone, Eq, PartialEq)]
80pub enum MacAddrFormat {
81 Canonical,
83 ColonNotation,
85 DotNotation,
87 Hexadecimal,
89 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 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 pub const fn is_local(&self) -> bool {
126 (self.0[0] & 0b0000_0010) != 0
127 }
128
129 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 pub const fn is_multicast(&self) -> bool {
140 (self.0[0] & 0b0000_0001) != 0
141 }
142
143 pub const fn oui(&self) -> [u8; 3] {
145 [self.0[0], self.0[1], self.0[2]]
146 }
147
148 pub fn set_oui(&mut self, oui: [u8; 3]) {
150 self.0[..3].copy_from_slice(&oui);
151 }
152
153 pub const fn to_array(self) -> [u8; $sz] {
155 self.0
156 }
157
158 pub const fn as_slice(&self) -> &[u8] {
160 &self.0
161 }
162
163 pub fn as_mut_slice(&mut self) -> &mut [u8] {
165 &mut self.0
166 }
167
168 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 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 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 #[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 impl TryFrom<&[core::ffi::c_char]> for $nm {
248 type Error = ParseError;
249
250 fn try_from(value: &[core::ffi::c_char]) -> Result<Self, Self::Error> {
251 Self::try_from(unsafe { &*(value as *const _ as *const [u8]) })
252 }
253 }
254
255 impl TryFrom<&str> for $nm {
256 type Error = ParseError;
257
258 fn try_from(value: &str) -> Result<Self, Self::Error> {
259 Self::parse_str(value)
260 }
261 }
262
263 #[cfg(feature = "std")]
264 impl TryFrom<String> for $nm {
265 type Error = ParseError;
266
267 fn try_from(value: String) -> Result<Self, Self::Error> {
268 Self::parse_str(&value)
269 }
270 }
271
272 impl FromStr for $nm {
273 type Err = ParseError;
274
275 fn from_str(s: &str) -> Result<Self, Self::Err> {
276 Self::parse_str(s)
277 }
278 }
279
280 #[cfg(feature = "serde")]
281 impl Serialize for $nm {
282 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
283 let mut buf = ArrayString::<MAC_MAX_SIZE>::new();
284 self.format_write(&mut buf, MacAddrFormat::Canonical)
285 .unwrap();
286 s.serialize_str(buf.as_ref())
287 }
288 }
289
290 #[cfg(feature = "serde")]
291 impl<'de> Deserialize<'de> for $nm {
292 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
293 Self::from_str(ArrayString::<MAC_MAX_SIZE>::deserialize(d)?.as_ref())
294 .map_err(serde::de::Error::custom)
295 }
296 }
297 };
298}
299
300#[repr(transparent)]
302#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
303pub struct MacAddr6([u8; 6]);
304#[repr(transparent)]
306#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
307pub struct MacAddr8([u8; 8]);
308
309mac_impl!(MacAddr6, 6, 12);
310mac_impl!(MacAddr8, 8, 16);
311
312impl MacAddr6 {
313 pub const fn to_modified_eui64(self) -> MacAddr8 {
314 let b = self.to_array();
315 MacAddr8([b[0] ^ 0b00000010, b[1], b[2], 0xFF, 0xFE, b[3], b[4], b[5]])
316 }
317
318 pub const fn try_from_modified_eui64(eui64: MacAddr8) -> Result<Self, IpError> {
319 let b = eui64.to_array();
320 if (b[3] == 0xFF) | (b[4] == 0xFE) {
321 Ok(Self([b[0] ^ 0b00000010, b[1], b[2], b[5], b[6], b[7]]))
322 } else {
323 Err(IpError::NotLinkLocal)
324 }
325 }
326
327 #[cfg(feature = "std")]
328 pub const fn to_link_local_ipv6(self) -> Ipv6Addr {
329 let mac64 = self.to_modified_eui64().to_array();
330
331 Ipv6Addr::new(
332 0xFE80,
333 0x0000,
334 0x0000,
335 0x0000,
336 ((mac64[0] as u16) << 8) + mac64[1] as u16,
337 ((mac64[2] as u16) << 8) + mac64[3] as u16,
338 ((mac64[4] as u16) << 8) + mac64[5] as u16,
339 ((mac64[6] as u16) << 8) + mac64[7] as u16,
340 )
341 }
342
343 #[cfg(feature = "std")]
344 pub const fn try_from_link_local_ipv6(ip: Ipv6Addr) -> Result<Self, IpError> {
345 let octets = ip.octets();
346 if (octets[0] != 0xFE)
347 | (octets[1] != 0x80)
348 | (octets[2] != 0x00)
349 | (octets[3] != 0x00)
350 | (octets[4] != 0x00)
351 | (octets[5] != 0x00)
352 | (octets[6] != 0x00)
353 | (octets[7] != 0x00)
354 | (octets[11] != 0xFF)
355 | (octets[12] != 0xFE)
356 {
357 return Err(IpError::NotLinkLocal);
358 }
359
360 Ok(Self([
361 octets[8] ^ 0b00000010,
362 octets[9],
363 octets[10],
364 octets[13],
365 octets[14],
366 octets[15],
367 ]))
368 }
369
370 #[cfg(feature = "std")]
371 pub const fn try_from_multicast_ipv4(ip: Ipv4Addr) -> Result<Self, IpError> {
372 if !ip.is_multicast() {
373 return Err(IpError::NotMulticast);
374 }
375 let b = ip.octets();
376 Ok(Self::new([0x01, 0x00, 0x5E, b[1] & 0x7F, b[2], b[3]]))
377 }
378
379 #[cfg(feature = "std")]
380 pub const fn try_from_multicast_ipv6(ip: Ipv6Addr) -> Result<Self, IpError> {
381 if !ip.is_multicast() {
382 return Err(IpError::NotMulticast);
383 }
384 let b = ip.octets();
385 Ok(Self::new([0x33, 0x33, b[12], b[13], b[14], b[15]]))
386 }
387
388 #[cfg(feature = "std")]
389 pub const fn try_from_multicast_ip(ip: IpAddr) -> Result<Self, IpError> {
390 match ip {
391 IpAddr::V4(ip) => Self::try_from_multicast_ipv4(ip),
392 IpAddr::V6(ip) => Self::try_from_multicast_ipv6(ip),
393 }
394 }
395}
396
397impl MacAddr6 {
398 fn write_internal<T: fmt::Write>(
400 &self,
401 f: &mut T,
402 pre: &str,
403 sep: &str,
404 sep2: &str,
405 ) -> fmt::Result {
406 write!(
407 f,
408 "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}",
409 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
410 )
411 }
412}
413
414impl MacAddr8 {
415 fn write_internal<T: fmt::Write>(
417 &self,
418 f: &mut T,
419 pre: &str,
420 sep: &str,
421 sep2: &str,
422 ) -> fmt::Result {
423 write!(
424 f,
425 "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}",
426 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
427 )
428 }
429}
430
431#[macro_export]
440macro_rules! mac6 {
441 ($s:expr) => {
442 match $crate::MacAddr6::parse_str($s) {
443 Ok(mac) => mac,
444 Err(_) => panic!("Invalid MAC address"),
445 }
446 };
447}
448
449#[macro_export]
458macro_rules! mac8 {
459 ($s:expr) => {
460 match $crate::MacAddr8::parse_str($s) {
461 Ok(mac) => mac,
462 Err(_) => panic!("Invalid MAC address"),
463 }
464 };
465}
466
467#[cfg(test)]
468mod test {
469 #[test]
470 fn test_flags_roundtrip() {
471 let mut addr = mac6!("50:74:f2:b1:a8:7f");
472 assert!(!addr.is_local());
473 assert!(!addr.is_multicast());
474
475 addr.set_multicast(true);
476 assert!(!addr.is_local());
477 assert!(addr.is_multicast());
478
479 addr.set_local(true);
480 assert!(addr.is_local());
481 assert!(addr.is_multicast());
482
483 addr.set_multicast(false);
484 assert!(addr.is_local());
485 assert!(!addr.is_multicast());
486
487 addr.set_local(false);
488 assert!(!addr.is_local());
489 assert!(!addr.is_multicast());
490 }
491}