use core::fmt;
use core::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum MacAddressError {
InvalidLength(usize),
InvalidFormat,
InvalidOctet,
}
impl fmt::Display for MacAddressError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(len) => {
write!(f, "MAC address must be 6 bytes (got {len})")
}
Self::InvalidFormat => {
write!(
f,
"MAC address must be in format XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX"
)
}
Self::InvalidOctet => {
write!(f, "MAC address octet is invalid (must be 00-FF)")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for MacAddressError {}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
pub struct MacAddress([u8; 6]);
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for MacAddress {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut octets = [0u8; 6];
for octet in &mut octets {
*octet = u8::arbitrary(u)?;
}
Ok(Self(octets))
}
}
impl MacAddress {
#[inline]
pub const fn new(octets: [u8; 6]) -> Result<Self, MacAddressError> {
Ok(Self(octets))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MacAddressError> {
if bytes.len() != 6 {
return Err(MacAddressError::InvalidLength(bytes.len()));
}
let mut octets = [0u8; 6];
octets.copy_from_slice(bytes);
Ok(Self(octets))
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Self, MacAddressError> {
let parts: Vec<&str> = if s.contains(':') {
s.split(':').collect()
} else if s.contains('-') {
s.split('-').collect()
} else {
return Err(MacAddressError::InvalidFormat);
};
if parts.len() != 6 {
return Err(MacAddressError::InvalidFormat);
}
let mut octets = [0u8; 6];
for (i, part) in parts.iter().enumerate() {
let octet = u8::from_str_radix(part, 16).map_err(|_| MacAddressError::InvalidOctet)?;
octets[i] = octet;
}
Ok(Self(octets))
}
#[must_use]
pub fn as_str(&self) -> String {
format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
#[must_use]
pub fn as_str_dash(&self) -> String {
format!(
"{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
#[must_use]
pub fn as_str_lower(&self) -> String {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
#[must_use]
#[inline]
pub const fn as_inner(&self) -> &[u8; 6] {
&self.0
}
#[must_use]
#[inline]
pub const fn into_inner(self) -> [u8; 6] {
self.0
}
#[must_use]
#[inline]
pub const fn octets(&self) -> [u8; 6] {
self.0
}
#[must_use]
#[inline]
pub const fn is_unicast(&self) -> bool {
self.0[0] & 0x01 == 0
}
#[must_use]
#[inline]
pub const fn is_multicast(&self) -> bool {
self.0[0] & 0x01 == 1
}
#[must_use]
#[inline]
pub const fn is_globally_administered(&self) -> bool {
self.0[0] & 0x02 == 0
}
#[must_use]
#[inline]
pub const fn is_locally_administered(&self) -> bool {
self.0[0] & 0x02 == 2
}
#[must_use]
#[inline]
pub const fn oui(&self) -> [u8; 3] {
[self.0[0], self.0[1], self.0[2]]
}
#[must_use]
#[inline]
pub const fn nic(&self) -> [u8; 3] {
[self.0[3], self.0[4], self.0[5]]
}
}
impl TryFrom<&[u8]> for MacAddress {
type Error = MacAddressError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl TryFrom<&str> for MacAddress {
type Error = MacAddressError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::from_str(s)
}
}
impl From<MacAddress> for [u8; 6] {
fn from(mac: MacAddress) -> Self {
mac.0
}
}
impl From<[u8; 6]> for MacAddress {
fn from(octets: [u8; 6]) -> Self {
Self(octets)
}
}
impl FromStr for MacAddress {
type Err = MacAddressError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s)
}
}
impl fmt::Display for MacAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid_mac() {
assert!(MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).is_ok());
assert!(MacAddress::new([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).is_ok());
assert!(MacAddress::new([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).is_ok());
}
#[test]
fn test_from_bytes_valid() {
let bytes = vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
assert!(MacAddress::from_bytes(&bytes).is_ok());
}
#[test]
fn test_from_bytes_invalid_length() {
let bytes = vec![0x00, 0x11, 0x22];
assert_eq!(
MacAddress::from_bytes(&bytes),
Err(MacAddressError::InvalidLength(3))
);
}
#[test]
fn test_from_str_valid() {
assert!(MacAddress::from_str("00:11:22:33:44:55").is_ok());
assert!(MacAddress::from_str("00-11-22-33-44-55").is_ok());
assert!(MacAddress::from_str("FF:FF:FF:FF:FF:FF").is_ok());
}
#[test]
fn test_from_str_invalid_format() {
assert_eq!(
MacAddress::from_str("00:11:22:33:44"),
Err(MacAddressError::InvalidFormat)
);
assert_eq!(
MacAddress::from_str("00:11:22:33:44:55:66"),
Err(MacAddressError::InvalidFormat)
);
}
#[test]
fn test_from_str_invalid_octet() {
assert_eq!(
MacAddress::from_str("GG:11:22:33:44:55"),
Err(MacAddressError::InvalidOctet)
);
}
#[test]
fn test_as_str() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.as_str(), "00:11:22:33:44:55");
}
#[test]
fn test_as_str_dash() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.as_str_dash(), "00-11-22-33-44-55");
}
#[test]
fn test_as_str_lower() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.as_str_lower(), "00:11:22:33:44:55");
}
#[test]
fn test_as_inner() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let octets = mac.as_inner();
assert_eq!(octets, &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_into_inner() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let octets = mac.into_inner();
assert_eq!(octets, [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_octets() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_is_unicast() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert!(mac.is_unicast());
assert!(!mac.is_multicast());
}
#[test]
fn test_is_multicast() {
let mac = MacAddress::new([0x01, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert!(mac.is_multicast());
assert!(!mac.is_unicast());
}
#[test]
fn test_is_globally_administered() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert!(mac.is_globally_administered());
assert!(!mac.is_locally_administered());
}
#[test]
fn test_is_locally_administered() {
let mac = MacAddress::new([0x02, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert!(mac.is_locally_administered());
assert!(!mac.is_globally_administered());
}
#[test]
fn test_oui() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.oui(), [0x00, 0x11, 0x22]);
}
#[test]
fn test_nic() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(mac.nic(), [0x33, 0x44, 0x55]);
}
#[test]
fn test_try_from_bytes() {
let bytes = vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let mac = MacAddress::try_from(bytes.as_slice()).unwrap();
assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_try_from_str() {
let mac = MacAddress::try_from("00:11:22:33:44:55").unwrap();
assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_from_mac_to_array() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let octets: [u8; 6] = mac.into();
assert_eq!(octets, [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_from_array_to_mac() {
let octets = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let mac: MacAddress = octets.into();
assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_from_str() {
let mac: MacAddress = "00:11:22:33:44:55".parse().unwrap();
assert_eq!(mac.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
#[test]
fn test_from_str_invalid() {
assert!("00:11:22:33:44".parse::<MacAddress>().is_err());
assert!("GG:11:22:33:44:55".parse::<MacAddress>().is_err());
}
#[test]
fn test_display() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(format!("{mac}"), "00:11:22:33:44:55");
}
#[test]
fn test_equality() {
let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac3 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
assert_eq!(mac1, mac2);
assert_ne!(mac1, mac3);
}
#[test]
fn test_ordering() {
let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
assert!(mac1 < mac2);
}
#[test]
fn test_clone() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac2 = mac.clone();
assert_eq!(mac, mac2);
}
#[test]
fn test_error_display() {
assert_eq!(
format!("{}", MacAddressError::InvalidLength(3)),
"MAC address must be 6 bytes (got 3)"
);
assert_eq!(
format!("{}", MacAddressError::InvalidFormat),
"MAC address must be in format XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX"
);
assert_eq!(
format!("{}", MacAddressError::InvalidOctet),
"MAC address octet is invalid (must be 00-FF)"
);
}
#[test]
fn test_broadcast_address() {
let mac = MacAddress::new([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).unwrap();
assert!(mac.is_multicast());
}
#[test]
fn test_hash() {
use core::hash::Hash;
use core::hash::Hasher;
#[derive(Default)]
struct SimpleHasher(u64);
impl Hasher for SimpleHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
}
}
}
let mac1 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac2 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let mac3 = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x56]).unwrap();
let mut hasher1 = SimpleHasher::default();
let mut hasher2 = SimpleHasher::default();
let mut hasher3 = SimpleHasher::default();
mac1.hash(&mut hasher1);
mac2.hash(&mut hasher2);
mac3.hash(&mut hasher3);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_ne!(hasher1.finish(), hasher3.finish());
}
#[test]
fn test_debug() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
assert_eq!(format!("{:?}", mac), "MacAddress([0, 17, 34, 51, 68, 85])");
}
#[test]
fn test_from_into_inner_roundtrip() {
let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).unwrap();
let octets = mac.into_inner();
let mac2 = MacAddress::new(octets).unwrap();
assert_eq!(mac2.octets(), [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
}
}