use std::net::Ipv6Addr;
use crate::core::models::mac::MacAddr;
pub fn hostname(name: &str) -> String {
let char_count = name.chars().count();
if char_count <= 4 {
return "XXXXX".to_string();
}
let first_two: String = name.chars().take(2).collect();
let last_two: String = name
.chars()
.rev()
.take(2)
.collect::<String>()
.chars()
.rev()
.collect();
format!("{}XXXXX{}", first_two, last_two)
}
pub fn mac_addr(mac: &MacAddr) -> String {
format!("{:02x}:{:02x}:{:02x}:XX:XX:XX", mac.0[0], mac.0[1], mac.0[2])
}
pub fn global_unicast(ip: &Ipv6Addr) -> String {
let s = ip.segments();
format!("{:x}::XXXX", s[0])
}
pub fn link_local(ip: &Ipv6Addr) -> String {
let s = ip.segments();
format!("{:x}::{:x}:{:x}:XXXX:XXXX", s[0], s[4], s[5])
}
pub fn unique_local(addr: &Ipv6Addr) -> String {
let segments = addr.segments();
format!("{:x}::XXXX", segments[0])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mac_redaction_upper_boundary() {
let mac = MacAddr::new(0xff, 0xff, 0xff, 0x00, 0x11, 0x22);
assert_eq!(mac_addr(&mac), "ff:ff:ff:XX:XX:XX");
}
#[test]
fn gua_redaction_standard() {
let ip = Ipv6Addr::new(0x2001, 0xdb8, 0x0, 0x0, 0x8a2e, 0x370, 0x7334, 0x1234);
assert_eq!(global_unicast(&ip), "2001::XXXX");
}
#[test]
fn gua_redaction_short_prefix() {
let ip = Ipv6Addr::new(0x2001, 0, 0, 0, 0, 0, 0, 0x1);
assert_eq!(global_unicast(&ip), "2001::XXXX");
}
#[test]
fn lla_redaction_standard() {
let ip = "fe80::ca52:61ff:fec7:594".parse::<Ipv6Addr>().unwrap();
assert_eq!(link_local(&ip), "fe80::ca52:61ff:XXXX:XXXX");
}
#[test]
fn lla_redaction_zero_segments() {
let ip = "fe80::ff:fe00:1".parse::<Ipv6Addr>().unwrap();
assert_eq!(link_local(&ip), "fe80::0:ff:XXXX:XXXX");
}
#[test]
fn ula_redaction_standard() {
let ip = Ipv6Addr::new(0xfd12, 0x3456, 0x789a, 0x1, 0xa8b2, 0xc3d4, 0xe5f6, 0x1234);
assert_eq!(unique_local(&ip), "fd12::XXXX");
}
#[test]
fn ula_redaction_zero_compression() {
let ip = "fd00::1".parse::<Ipv6Addr>().unwrap();
assert_eq!(unique_local(&ip), "fd00::XXXX");
}
#[test]
fn ula_redaction_hides_global_id() {
let ip1 = "fd00:1111:1111::1".parse::<Ipv6Addr>().unwrap();
let ip2 = "fd00:2222:2222::1".parse::<Ipv6Addr>().unwrap();
assert_eq!(unique_local(&ip1), unique_local(&ip2));
assert_eq!(unique_local(&ip1), "fd00::XXXX");
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest::proptest! {
#[test]
fn hostname_privacy_preserving(name in "[a-zA-Z0-9.-]{5,64}") {
let redacted = hostname(&name);
prop_assert!(redacted.contains("XXXXX"));
prop_assert!(redacted.starts_with(&name[..2]));
prop_assert!(redacted.ends_with(&name[name.len()-2..]));
}
#[test]
fn short_hostname_fully_masked(name in "[a-zA-Z0-9.-]{0,4}") {
let redacted = hostname(&name);
prop_assert_eq!(redacted, "XXXXX");
}
#[test]
fn mac_redaction_preserves_oui(
o1 in 0..=255u8, o2 in 0..=255u8, o3 in 0..=255u8,
o4 in 0..=255u8, o5 in 0..=255u8, o6 in 0..=255u8
) {
let mac = MacAddr::new(o1, o2, o3, o4, o5, o6);
let redacted = mac_addr(&mac);
let expected_prefix = format!("{:02x}:{:02x}:{:02x}", o1, o2, o3);
prop_assert!(redacted.starts_with(&expected_prefix));
prop_assert!(redacted.ends_with("XX:XX:XX"));
}
#[test]
fn ula_masking_consistency(
s0 in 0xfc00..=0xfdffu16, s1 in 0..u16::MAX, s2 in 0..u16::MAX, s3 in 0..u16::MAX,
s4 in 0..u16::MAX, s5 in 0..u16::MAX, s6 in 0..u16::MAX, s7 in 0..u16::MAX
) {
let ip = Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7);
let redacted = unique_local(&ip);
let expected_prefix = format!("{:x}", s0);
prop_assert!(redacted.starts_with(&expected_prefix));
prop_assert!(redacted.ends_with("::XXXX"));
}
}
}