use std::convert::TryFrom;
pub mod oui;
use oui::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use crate::{checks::data_link::validate_mac_length, errors::data_link::mac_addres::MacParseError};
pub const MAC_LEN: usize = 6;
#[cfg_attr(all(doc, feature = "doc-diagrams"), aquamarine::aquamarine)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MacAddress(pub [u8; MAC_LEN]);
impl Serialize for MacAddress {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for MacAddress {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
let mut addr = [0u8; MAC_LEN];
let mut parts = s.split(':');
for byte in addr.iter_mut() {
let part = parts
.next()
.ok_or_else(|| de::Error::custom("adresse MAC invalide"))?;
*byte = u8::from_str_radix(part, 16)
.map_err(|_| de::Error::custom("adresse MAC invalide"))?;
}
if parts.next().is_some() {
return Err(de::Error::custom("adresse MAC invalide"));
}
Ok(Self(addr))
}
}
impl MacAddress {
pub fn display_with_oui(&self) -> String {
match self.get_oui() {
Oui::Unknown => 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]
),
oui => format!(
"{:?}:{:02x}:{:02x}:{:02x}",
oui, self.0[3], self.0[4], self.0[5]
),
}
}
pub fn get_oui(&self) -> Oui {
Oui::from_bytes(&self.0[0..3])
}
}
impl TryFrom<&[u8]> for MacAddress {
type Error = MacParseError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
validate_mac_length(bytes)?;
let mut addr = [0u8; MAC_LEN];
addr.copy_from_slice(bytes);
Ok(Self(addr))
}
}
impl TryFrom<String> for MacAddress {
type Error = MacParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let bytes = value
.split(':')
.map(|s| u8::from_str_radix(s, 16).unwrap())
.collect::<Vec<u8>>();
Self::try_from(&bytes[..])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_mac_address_with_known_oui() {
let bytes = [0x2C, 0xFD, 0xA1, 0x3C, 0x4D, 0x5E];
let mac = MacAddress::try_from(&bytes[..]).expect("Conversion should succeed");
assert_eq!(mac.display_with_oui(), "ASUSTek:3c:4d:5e");
}
#[test]
fn display_mac_address_with_unknown_oui() {
let bytes = [0xAA, 0xBB, 0xCC, 0x3C, 0x4D, 0x5E];
let mac = MacAddress::try_from(&bytes[..]).expect("Conversion should succeed");
assert_eq!(mac.display_with_oui(), "aa:bb:cc:3c:4d:5e");
}
#[test]
fn valid_mac_address_conversion() {
let bytes = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E];
let mac = MacAddress::try_from(&bytes[..]).expect("Conversion should succeed");
assert_eq!(mac, MacAddress([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]));
}
#[test]
fn invalid_mac_address_length() {
let bytes = [0x00, 0x1A, 0x2B, 0x3C, 0x4D];
let result = MacAddress::try_from(&bytes[..]);
assert_eq!(
result,
Err(MacParseError::InvalidLength {
actual: bytes.len()
})
);
}
#[test]
fn display_mac_address_format() {
let mac = MacAddress([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]);
assert_eq!(mac.to_string(), "00:1a:2b:3c:4d:5e");
}
#[test]
fn valid_mac_address_conversion_all_zeros() {
let bytes = [0x00; 6];
let mac = MacAddress::try_from(&bytes[..]).expect("Conversion should succeed");
assert_eq!(mac, MacAddress([0x00; 6]));
}
#[test]
fn invalid_mac_address_format_too_long() {
let bytes = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F];
let result = MacAddress::try_from(&bytes[..]);
assert_eq!(
result,
Err(MacParseError::InvalidLength {
actual: bytes.len()
})
);
}
}
#[cfg(test)]
mod serde_tests {
use super::*;
#[test]
fn serialize_as_hex_string() {
let mac = MacAddress([0x2C, 0xFD, 0xA1, 0x3C, 0x4D, 0x5E]);
let json = serde_json::to_string(&mac).unwrap();
assert_eq!(json, "\"2c:fd:a1:3c:4d:5e\"");
}
#[test]
fn deserialize_from_hex_string() {
let mac: MacAddress = serde_json::from_str("\"2c:fd:a1:3c:4d:5e\"").unwrap();
assert_eq!(mac, MacAddress([0x2C, 0xFD, 0xA1, 0x3C, 0x4D, 0x5E]));
}
#[test]
fn serde_round_trip() {
let mac = MacAddress([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]);
let json = serde_json::to_string(&mac).unwrap();
let back: MacAddress = serde_json::from_str(&json).unwrap();
assert_eq!(mac, back);
}
#[test]
fn deserialize_rejects_invalid_strings() {
for bad in ["\"2c:fd:a1:3c:4d\"", "\"2c:fd:a1:3c:4d:5e:6f\"", "\"zz:fd:a1:3c:4d:5e\""] {
assert!(
serde_json::from_str::<MacAddress>(bad).is_err(),
"aurait dû échouer : {bad}"
);
}
assert!(serde_json::from_str::<MacAddress>("[0,1,2,3,4,5]").is_err());
}
#[test]
fn try_from_string_valid() {
let mac = MacAddress::try_from("00:1a:2b:3c:4d:5e".to_string()).unwrap();
assert_eq!(mac, MacAddress([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]));
}
#[test]
fn try_from_string_wrong_length() {
assert!(MacAddress::try_from("00:1a:2b".to_string()).is_err());
}
}