use std::fs::File;
use std::io::Read;
pub fn generate_random_mac() -> String {
let mut bytes = [0u8; 3];
if File::open("/dev/urandom")
.and_then(|mut f| f.read_exact(&mut bytes))
.is_err()
{
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
bytes[0] = (nanos & 0xff) as u8;
bytes[1] = ((nanos >> 8) & 0xff) as u8;
bytes[2] = ((nanos >> 16) & 0xff) as u8;
}
format!("52:54:00:{:02x}:{:02x}:{:02x}", bytes[0], bytes[1], bytes[2])
}
pub fn is_valid_mac(s: &str) -> bool {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 6 {
return false;
}
parts.iter().all(|p| {
p.len() == 2 && p.chars().all(|c| c.is_ascii_hexdigit())
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn random_mac_has_qemu_oui_and_correct_format() {
for _ in 0..100 {
let mac = generate_random_mac();
assert!(mac.starts_with("52:54:00:"), "wrong OUI: {}", mac);
assert!(is_valid_mac(&mac), "not valid MAC: {}", mac);
assert_eq!(mac.len(), 17);
}
}
#[test]
fn random_macs_are_distinct() {
let macs: HashSet<String> = (0..200).map(|_| generate_random_mac()).collect();
assert!(macs.len() > 195, "too many collisions: {}", macs.len());
}
#[test]
fn validator_accepts_canonical_macs() {
assert!(is_valid_mac("52:54:00:12:34:56"));
assert!(is_valid_mac("AA:BB:CC:DD:EE:FF"));
assert!(is_valid_mac("aa:bb:cc:dd:ee:ff"));
assert!(is_valid_mac("00:00:00:00:00:00"));
}
#[test]
fn validator_rejects_malformed_macs() {
assert!(!is_valid_mac(""));
assert!(!is_valid_mac("52-54-00-12-34-56"));
assert!(!is_valid_mac("52:54:00:12:34"));
assert!(!is_valid_mac("52:54:00:12:34:56:78"));
assert!(!is_valid_mac("ZZ:54:00:12:34:56"));
assert!(!is_valid_mac("525:54:00:12:34:56"));
assert!(!is_valid_mac("5:54:00:12:34:56"));
}
}