diff --git a/subscriptions/network-manager/src/hw_address.rs b/subscriptions/network-manager/src/hw_address.rs index db29158..eb49769 100644 --- a/subscriptions/network-manager/src/hw_address.rs +++ b/subscriptions/network-manager/src/hw_address.rs @@ -1,38 +1,102 @@ -#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, Default, Debug, PartialOrd, Ord)] pub struct HwAddress { - address: u64, + octets: Vec, } impl HwAddress { pub fn from_str(arg: &str) -> Option { - let columnless_vec = arg.split(":").collect::>(); - if columnless_vec.len() * 3 - 1 != arg.len() { + let segments: Vec<&str> = arg.split(":").collect(); + + // Only accept 6-byte (EUI-48) or 8-byte (EUI-64) addresses + if segments.len() != 6 && segments.len() != 8 { return None; } - for byte in &columnless_vec { - if byte.len() != 2 { + + let mut octets: Vec = Vec::new(); + for segment in segments { + if segment.len() != 2 { return None; } + let byte: u8 = u8::from_str_radix(segment, 16).ok()?; + octets.push(byte); } - u64::from_str_radix(columnless_vec.join("").as_str(), 16) - .ok() - .map(|address| HwAddress { address }) - } - pub fn from_string(arg: &str) -> Option { - HwAddress::from_str(arg) + + Some(HwAddress { octets }) } } impl std::fmt::Display for HwAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let hex = format!("{:#x}", self.address) - .trim_start_matches("0x") - .chars() - .collect::>() - .chunks(2) - .map(|chunk| chunk.iter().cloned().collect::()) - .collect::>() - .join(":"); - write!(f, "{}", hex) + let hex_parts: Vec = self + .octets + .iter() + .map(|byte| format!("{:02x}", byte)) + .collect(); + write!(f, "{}", hex_parts.join(":")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_valid_6_byte_mac() { + let mac: &str = "00:11:22:33:44:55"; + let hw_addr: HwAddress = HwAddress::from_str(mac).expect("should parse valid MAC"); + + // Access the internal octets field + assert_eq!(hw_addr.octets.len(), 6); + assert_eq!(hw_addr.octets, vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55]); + } + + #[test] + fn test_display_6_byte_mac() { + let hw_addr: HwAddress = HwAddress { + octets: vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55], + }; + + assert_eq!(format!("{}", hw_addr), "00:11:22:33:44:55"); + } + + #[test] + fn test_parse_valid_8_byte_mac() { + let mac: &str = "00:11:22:33:44:55:66:77"; + let hw_addr: HwAddress = HwAddress::from_str(mac).expect("should parse valid EUI-64 MAC"); + + assert_eq!(hw_addr.octets.len(), 8); + assert_eq!( + hw_addr.octets, + vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77] + ); + } + + #[test] + fn test_display_8_byte_mac() { + let hw_addr: HwAddress = HwAddress { + octets: vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77], + }; + + assert_eq!(format!("{}", hw_addr), "00:11:22:33:44:55:66:77"); + } + + #[test] + fn test_reject_invalid_length_macs() { + let invalid_macs: Vec<(&str, &str)> = vec![ + ("00", "1-byte MAC"), + ("00:11:22:33", "4-byte MAC"), + ("00:11:22:33:44", "5-byte MAC"), + ("00:11:22:33:44:55:66", "7-byte MAC"), + ("00:11:22:33:44:55:66:77:88", "9-byte MAC"), + ("00:11:22:33:44:55:66:77:88:99:aa:bb", "12-byte MAC"), + ]; + + for (mac, description) in invalid_macs { + assert!( + HwAddress::from_str(mac).is_none(), + "should reject {}", + description + ); + } } }