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