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 #[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#[repr(transparent)]
303#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
304pub struct MacAddr6([u8; 6]);
305#[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 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 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#[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#[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}