use std::{fmt, str::FromStr};
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MacAddr(pub [u8; 6]);
impl MacAddr {
pub const ZERO: Self = MacAddr([0; 6]);
pub const BROADCAST: Self = MacAddr([0xff; 6]);
#[inline]
pub const fn new(bytes: [u8; 6]) -> Self {
Self(bytes)
}
#[inline]
pub const fn as_bytes(&self) -> &[u8; 6] {
&self.0
}
#[inline]
pub const fn into_bytes(self) -> [u8; 6] {
self.0
}
#[inline]
pub fn is_zero(&self) -> bool {
self.0 == [0; 6]
}
#[inline]
pub fn is_broadcast(&self) -> bool {
self.0 == [0xff; 6]
}
#[inline]
pub fn is_multicast(&self) -> bool {
self.0[0] & 0x01 != 0
}
#[inline]
pub fn is_unicast(&self) -> bool {
!self.is_multicast()
}
#[inline]
pub fn is_locally_administered(&self) -> bool {
self.0[0] & 0x02 != 0
}
}
impl fmt::Display for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let b = &self.0;
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5],
)
}
}
impl fmt::Debug for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MacAddr({self})")
}
}
impl From<[u8; 6]> for MacAddr {
#[inline]
fn from(bytes: [u8; 6]) -> Self {
Self(bytes)
}
}
impl From<MacAddr> for [u8; 6] {
#[inline]
fn from(mac: MacAddr) -> Self {
mac.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseMacAddrError;
impl fmt::Display for ParseMacAddrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid MAC address (expected six colon- or dash-separated hex bytes)")
}
}
impl std::error::Error for ParseMacAddrError {}
impl FromStr for MacAddr {
type Err = ParseMacAddrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let sep = if s.as_bytes().get(2) == Some(&b':') {
':'
} else if s.as_bytes().get(2) == Some(&b'-') {
'-'
} else {
return Err(ParseMacAddrError);
};
let mut bytes = [0u8; 6];
let mut it = s.split(sep);
for slot in &mut bytes {
let chunk = it.next().ok_or(ParseMacAddrError)?;
if chunk.len() != 2 {
return Err(ParseMacAddrError);
}
*slot = u8::from_str_radix(chunk, 16).map_err(|_| ParseMacAddrError)?;
}
if it.next().is_some() {
return Err(ParseMacAddrError);
}
Ok(MacAddr(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_lowercase_colon_separated() {
let m = MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
assert_eq!(m.to_string(), "aa:bb:cc:dd:ee:ff");
}
#[test]
fn display_pads_single_digit_bytes() {
let m = MacAddr([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
assert_eq!(m.to_string(), "01:02:03:04:05:06");
}
#[test]
fn debug_includes_type_name() {
let m = MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
assert_eq!(format!("{m:?}"), "MacAddr(aa:bb:cc:dd:ee:ff)");
}
#[test]
fn zero_constant() {
assert_eq!(MacAddr::ZERO, MacAddr([0; 6]));
assert!(MacAddr::ZERO.is_zero());
}
#[test]
fn broadcast_constant() {
assert_eq!(MacAddr::BROADCAST, MacAddr([0xff; 6]));
assert!(MacAddr::BROADCAST.is_broadcast());
assert!(MacAddr::BROADCAST.is_multicast());
}
#[test]
fn multicast_detection() {
let m = MacAddr([0x01, 0x00, 0x5e, 0x00, 0x00, 0x01]);
assert!(m.is_multicast());
assert!(!m.is_unicast());
let m = MacAddr([0x02, 0x00, 0x5e, 0x00, 0x00, 0x01]);
assert!(!m.is_multicast());
assert!(m.is_unicast());
}
#[test]
fn locally_administered_bit() {
let m = MacAddr([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
assert!(m.is_locally_administered());
let m = MacAddr([0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
assert!(!m.is_locally_administered());
}
#[test]
fn from_into_byte_array() {
let bytes = [1u8, 2, 3, 4, 5, 6];
let m: MacAddr = bytes.into();
let back: [u8; 6] = m.into();
assert_eq!(back, bytes);
}
#[test]
fn as_bytes_returns_ref() {
let m = MacAddr([1, 2, 3, 4, 5, 6]);
assert_eq!(m.as_bytes(), &[1, 2, 3, 4, 5, 6]);
}
#[test]
fn repr_transparent_layout() {
assert_eq!(std::mem::size_of::<MacAddr>(), 6);
assert_eq!(std::mem::align_of::<MacAddr>(), 1);
}
#[test]
fn hash_and_eq_are_consistent() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(MacAddr([1, 2, 3, 4, 5, 6]));
assert!(set.contains(&MacAddr([1, 2, 3, 4, 5, 6])));
assert!(!set.contains(&MacAddr([1, 2, 3, 4, 5, 7])));
}
#[test]
fn ordering_is_lexicographic() {
let a = MacAddr([0, 0, 0, 0, 0, 1]);
let b = MacAddr([0, 0, 0, 0, 0, 2]);
assert!(a < b);
}
#[test]
fn from_str_colon_separated() {
let m: MacAddr = "aa:bb:cc:dd:ee:ff".parse().unwrap();
assert_eq!(m, MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]));
}
#[test]
fn from_str_dash_separated() {
let m: MacAddr = "aa-bb-cc-dd-ee-ff".parse().unwrap();
assert_eq!(m, MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]));
}
#[test]
fn from_str_uppercase_accepted() {
let m: MacAddr = "AA:BB:CC:DD:EE:FF".parse().unwrap();
assert_eq!(m, MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]));
}
#[test]
fn from_str_round_trips_display() {
let m = MacAddr([1, 2, 3, 4, 5, 6]);
let s = m.to_string();
let back: MacAddr = s.parse().unwrap();
assert_eq!(m, back);
}
#[test]
fn from_str_rejects_malformed() {
assert!("aabbccddeeff".parse::<MacAddr>().is_err()); assert!("aa:bb:cc:dd:ee".parse::<MacAddr>().is_err()); assert!("aa:bb:cc:dd:ee:ff:00".parse::<MacAddr>().is_err()); assert!("aa:bb:cc:dd:ee:gg".parse::<MacAddr>().is_err()); assert!("a:bb:cc:dd:ee:ff".parse::<MacAddr>().is_err()); assert!("".parse::<MacAddr>().is_err()); }
}