1use core::fmt;
40use core::str::FromStr;
41
42#[cfg(feature = "serde")]
43use serde::{Deserialize, Serialize};
44
45#[cfg(feature = "zeroize")]
46use zeroize::Zeroize;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[non_exhaustive]
52pub enum MacAddressError {
53 InvalidLength(usize),
58 InvalidFormat,
63 InvalidOctet,
67}
68
69impl fmt::Display for MacAddressError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::InvalidLength(len) => {
73 write!(f, "MAC address must be 6 bytes (got {len})")
74 }
75 Self::InvalidFormat => {
76 write!(
77 f,
78 "MAC address must be in format XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX"
79 )
80 }
81 Self::InvalidOctet => {
82 write!(f, "MAC address octet is invalid (must be 00-FF)")
83 }
84 }
85 }
86}
87
88#[cfg(feature = "std")]
89impl std::error::Error for MacAddressError {}
90
91#[repr(transparent)]
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
124#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
125#[cfg_attr(feature = "zeroize", derive(Zeroize))]
126pub struct MacAddress([u8; 6]);
127
128#[cfg(feature = "arbitrary")]
129impl<'a> arbitrary::Arbitrary<'a> for MacAddress {
130 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
131 let mut octets = [0u8; 6];
132 for octet in &mut octets {
133 *octet = u8::arbitrary(u)?;
134 }
135 Ok(Self(octets))
136 }
137}
138
139impl MacAddress {
140 pub const fn new(octets: [u8; 6]) -> Result<Self, MacAddressError> {
156 Ok(Self(octets))
157 }
158
159 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MacAddressError> {
176 if bytes.len() != 6 {
177 return Err(MacAddressError::InvalidLength(bytes.len()));
178 }
179 let mut octets = [0u8; 6];
180 octets.copy_from_slice(bytes);
181 Ok(Self(octets))
182 }
183
184 #[allow(clippy::should_implement_trait)]
200 pub fn from_str(s: &str) -> Result<Self, MacAddressError> {
201 let parts: Vec<&str> = if s.contains(':') {
202 s.split(':').collect()
203 } else if s.contains('-') {
204 s.split('-').collect()
205 } else {
206 return Err(MacAddressError::InvalidFormat);
207 };
208
209 if parts.len() != 6 {
210 return Err(MacAddressError::InvalidFormat);
211 }
212
213 let mut octets = [0u8; 6];
214 for (i, part) in parts.iter().enumerate() {
215 let octet = u8::from_str_radix(part, 16).map_err(|_| MacAddressError::InvalidOctet)?;
216 octets[i] = octet;
217 }
218
219 Ok(Self(octets))
220 }
221
222 #[must_use]
233 pub fn as_str(&self) -> String {
234 format!(
235 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
236 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
237 )
238 }
239
240 #[must_use]
251 pub fn as_str_dash(&self) -> String {
252 format!(
253 "{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}",
254 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
255 )
256 }
257
258 #[must_use]
269 pub fn as_str_lower(&self) -> String {
270 format!(
271 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
272 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
273 )
274 }
275
276 #[must_use]
288 #[inline]
289 pub const fn as_inner(&self) -> &[u8; 6] {
290 &self.0
291 }
292
293 #[must_use]
305 #[inline]
306 pub const fn into_inner(self) -> [u8; 6] {
307 self.0
308 }
309
310 #[must_use]
321 #[inline]
322 pub const fn octets(&self) -> [u8; 6] {
323 self.0
324 }
325
326 #[must_use]
339 #[inline]
340 pub const fn is_unicast(&self) -> bool {
341 self.0[0] & 0x01 == 0
342 }
343
344 #[must_use]
357 #[inline]
358 pub const fn is_multicast(&self) -> bool {
359 self.0[0] & 0x01 == 1
360 }
361
362 #[must_use]
375 #[inline]
376 pub const fn is_globally_administered(&self) -> bool {
377 self.0[0] & 0x02 == 0
378 }
379
380 #[must_use]
393 #[inline]
394 pub const fn is_locally_administered(&self) -> bool {
395 self.0[0] & 0x02 == 2
396 }
397
398 #[must_use]
411 #[inline]
412 pub const fn oui(&self) -> [u8; 3] {
413 [self.0[0], self.0[1], self.0[2]]
414 }
415
416 #[must_use]
429 #[inline]
430 pub const fn nic(&self) -> [u8; 3] {
431 [self.0[3], self.0[4], self.0[5]]
432 }
433}
434
435impl TryFrom<&[u8]> for MacAddress {
436 type Error = MacAddressError;
437
438 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
439 Self::from_bytes(bytes)
440 }
441}
442
443impl TryFrom<&str> for MacAddress {
444 type Error = MacAddressError;
445
446 fn try_from(s: &str) -> Result<Self, Self::Error> {
447 Self::from_str(s)
448 }
449}
450
451impl From<MacAddress> for [u8; 6] {
452 fn from(mac: MacAddress) -> Self {
453 mac.0
454 }
455}
456
457impl From<[u8; 6]> for MacAddress {
458 fn from(octets: [u8; 6]) -> Self {
459 Self(octets)
460 }
461}
462
463impl FromStr for MacAddress {
464 type Err = MacAddressError;
465
466 fn from_str(s: &str) -> Result<Self, Self::Err> {
467 Self::from_str(s)
468 }
469}
470
471impl fmt::Display for MacAddress {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 write!(
474 f,
475 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
476 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
477 )
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
486 fn test_new_valid_mac() {
487 assert!(MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).is_ok());
488 assert!(MacAddress::new([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).is_ok());
489 assert!(MacAddress::new([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).is_ok());
490 }
491
492 #[test]
493 fn test_from_bytes_valid() {
494 let bytes = vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
495 assert!(MacAddress::from_bytes(&bytes).is_ok());
496 }
497
498 #[test]
499 fn test_from_bytes_invalid_length() {
500 let bytes = vec![0x00, 0x11, 0x22];
501 assert_eq!(
502 MacAddress::from_bytes(&bytes),
503 Err(MacAddressError::InvalidLength(3))
504 );
505 }
506
507 #[test]
508 fn test_from_str_valid() {
509 assert!(MacAddress::from_str("00:11:22:33:44:55").is_ok());
510 assert!(MacAddress::from_str("00-11-22-33-44-55").is_ok());
511 assert!(MacAddress::from_str("FF:FF:FF:FF:FF:FF").is_ok());
512 }
513
514 #[test]
515 fn test_from_str_invalid_format() {
516 assert_eq!(
517 MacAddress::from_str("00:11:22:33:44"),
518 Err(MacAddressError::InvalidFormat)
519 );
520 assert_eq!(
521 MacAddress::from_str("00:11:22:33:44:55:66"),
522 Err(MacAddressError::InvalidFormat)
523 );
524 }
525
526 #[test]
527 fn test_from_str_invalid_octet() {
528 assert_eq!(
529 MacAddress::from_str("GG:11:22:33:44:55"),
530 Err(MacAddressError::InvalidOctet)
531 );
532 }
533
534 #[test]
535 fn test_as_str() {
536 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
537 assert_eq!(mac.as_str(), "00:11:22:33:44:55");
538 }
539
540 #[test]
541 fn test_as_str_dash() {
542 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
543 assert_eq!(mac.as_str_dash(), "00-11-22-33-44-55");
544 }
545
546 #[test]
547 fn test_as_str_lower() {
548 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
549 assert_eq!(mac.as_str_lower(), "00:11:22:33:44:55");
550 }
551
552 #[test]
553 fn test_as_inner() {
554 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
555 let octets = mac.as_inner();
556 assert_eq!(octets, &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
557 }
558
559 #[test]
560 fn test_into_inner() {
561 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
562 let octets = mac.into_inner();
563 assert_eq!(octets, [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
564 }
565
566 #[test]
567 fn test_octets() {
568 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
569 assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
570 }
571
572 #[test]
573 fn test_is_unicast() {
574 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
575 assert!(mac.is_unicast());
576 assert!(!mac.is_multicast());
577 }
578
579 #[test]
580 fn test_is_multicast() {
581 let mac = MacAddress::new([0x01, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
582 assert!(mac.is_multicast());
583 assert!(!mac.is_unicast());
584 }
585
586 #[test]
587 fn test_is_globally_administered() {
588 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
589 assert!(mac.is_globally_administered());
590 assert!(!mac.is_locally_administered());
591 }
592
593 #[test]
594 fn test_is_locally_administered() {
595 let mac = MacAddress::new([0x02, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
596 assert!(mac.is_locally_administered());
597 assert!(!mac.is_globally_administered());
598 }
599
600 #[test]
601 fn test_oui() {
602 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
603 assert_eq!(mac.oui(), [0x00, 0x11, 0x22]);
604 }
605
606 #[test]
607 fn test_nic() {
608 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
609 assert_eq!(mac.nic(), [0x33, 0x44, 0x55]);
610 }
611
612 #[test]
613 fn test_try_from_bytes() {
614 let bytes = vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
615 let mac = MacAddress::try_from(bytes.as_slice()).unwrap();
616 assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
617 }
618
619 #[test]
620 fn test_try_from_str() {
621 let mac = MacAddress::try_from("00:11:22:33:44:55").unwrap();
622 assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
623 }
624
625 #[test]
626 fn test_from_mac_to_array() {
627 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
628 let octets: [u8; 6] = mac.into();
629 assert_eq!(octets, [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
630 }
631
632 #[test]
633 fn test_from_array_to_mac() {
634 let octets = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
635 let mac: MacAddress = octets.into();
636 assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
637 }
638
639 #[test]
640 fn test_from_str() {
641 let mac: MacAddress = "00:11:22:33:44:55".parse().unwrap();
642 assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
643 }
644
645 #[test]
646 fn test_from_str_invalid() {
647 assert!("00:11:22:33:44".parse::<MacAddress>().is_err());
648 assert!("GG:11:22:33:44:55".parse::<MacAddress>().is_err());
649 }
650
651 #[test]
652 fn test_display() {
653 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
654 assert_eq!(format!("{mac}"), "00:11:22:33:44:55");
655 }
656
657 #[test]
658 fn test_equality() {
659 let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
660 let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
661 let mac3 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
662
663 assert_eq!(mac1, mac2);
664 assert_ne!(mac1, mac3);
665 }
666
667 #[test]
668 fn test_ordering() {
669 let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
670 let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
671
672 assert!(mac1 < mac2);
673 }
674
675 #[test]
676 fn test_clone() {
677 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
678 let mac2 = mac.clone();
679 assert_eq!(mac, mac2);
680 }
681
682 #[test]
683 fn test_error_display() {
684 assert_eq!(
685 format!("{}", MacAddressError::InvalidLength(3)),
686 "MAC address must be 6 bytes (got 3)"
687 );
688 assert_eq!(
689 format!("{}", MacAddressError::InvalidFormat),
690 "MAC address must be in format XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX"
691 );
692 assert_eq!(
693 format!("{}", MacAddressError::InvalidOctet),
694 "MAC address octet is invalid (must be 00-FF)"
695 );
696 }
697
698 #[test]
699 fn test_broadcast_address() {
700 let mac = MacAddress::new([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).unwrap();
701 assert!(mac.is_multicast());
702 }
703
704 #[test]
705 fn test_hash() {
706 use core::hash::Hash;
707 use core::hash::Hasher;
708
709 #[derive(Default)]
710 struct SimpleHasher(u64);
711
712 impl Hasher for SimpleHasher {
713 fn finish(&self) -> u64 {
714 self.0
715 }
716
717 fn write(&mut self, bytes: &[u8]) {
718 for byte in bytes {
719 self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
720 }
721 }
722 }
723
724 let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
725 let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
726 let mac3 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
727
728 let mut hasher1 = SimpleHasher::default();
729 let mut hasher2 = SimpleHasher::default();
730 let mut hasher3 = SimpleHasher::default();
731
732 mac1.hash(&mut hasher1);
733 mac2.hash(&mut hasher2);
734 mac3.hash(&mut hasher3);
735
736 assert_eq!(hasher1.finish(), hasher2.finish());
737 assert_ne!(hasher1.finish(), hasher3.finish());
738 }
739
740 #[test]
741 fn test_debug() {
742 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
743 assert_eq!(format!("{:?}", mac), "MacAddress([0, 17, 34, 51, 68, 85])");
744 }
745
746 #[test]
747 fn test_from_into_inner_roundtrip() {
748 let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
749 let octets = mac.into_inner();
750 let mac2 = MacAddress::new(octets).unwrap();
751 assert_eq!(mac2.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
752 }
753}