use ipnetwork::IpNetwork; use std::net::IpAddr; /// Supported formats: /// CIDR: "192.168.1.0/24" /// Range: "192.168.1.1-192.168.1.254" /// Single IP: "192.168.1.1" /// Comma-sep: "192.168.1.1,192.168.1.2,10.0.0.1" /// Mixed: "192.168.1.0/24,10.0.0.1" pub fn parse_ip_range(input: &str) -> Result, String> { let input = input.trim(); if input.is_empty() { return Err("Empty Input".to_string()); } let mut all_ips = Vec::new(); for segment in input.split(',') { let segment = segment.trim(); if segment.is_empty() { continue; } match segment { s if s.contains('/') => parse_cidr(s, &mut all_ips)?, s if s.contains('-') => parse_range(s, &mut all_ips)?, s => parse_single(s, &mut all_ips)?, } } if all_ips.is_empty() { return Err("No Valid IPs parsed".to_string()); } Ok(all_ips) } fn parse_cidr(segment: &str, ips: &mut Vec) -> Result<(), String> { let network: IpNetwork = segment .parse() .map_err(|e| format!("Invalid CIDR '{}': {}", segment, e))?; match network { IpNetwork::V4(net) if net.prefix() < 31 => { let network_addr = net.network(); let broadcast_addr = net.broadcast(); for ip in net.iter() { if ip != network_addr && ip != broadcast_addr { ips.push(IpAddr::V4(ip)); } } } _ => { for ip in network.iter() { ips.push(ip); } } } Ok(()) } fn parse_range(segment: &str, ips: &mut Vec) -> Result<(), String> { let parts: Vec<&str> = segment.splitn(2, '-').collect(); let start: IpAddr = parts[0] .trim() .parse() .map_err(|e| format!("Invalid start IP '{}': {}", parts[0], e))?; let end: IpAddr = parts[1] .trim() .parse() .map_err(|e| format!("Invalid end IP '{}': {}", parts[1], e))?; match (start, end) { (IpAddr::V4(s), IpAddr::V4(e)) => { let s_u32 = u32::from(s); let e_u32 = u32::from(e); if s_u32 > e_u32 { return Err(format!("Start IP {} is greater than end IP {}", s, e,)); } for addr in s_u32..=e_u32 { ips.push(IpAddr::V4(addr.into())); } } _ => { return Err("Range notation only supports IPv4 currently".to_string()); } } Ok(()) } fn parse_single(segment: &str, ips: &mut Vec) -> Result<(), String> { let ip: IpAddr = segment .parse() .map_err(|e| format!("Invalid IP '{}': {}", segment, e))?; ips.push(ip); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_single_ip() { let result = parse_ip_range("192.168.1.1").unwrap(); assert_eq!(result.len(), 1); } #[test] fn test_cidr_24() { let result = parse_ip_range("192.168.1.0/24").unwrap(); assert_eq!(result.len(), 256); } #[test] fn test_range() { let result = parse_ip_range("192.168.1.1-192.168.1.10").unwrap(); assert_eq!(result.len(), 10); } #[test] fn test_comma_separated() { let result = parse_ip_range("192.168.1.1,192.168.1.2,10.0.0.1").unwrap(); assert_eq!(result.len(), 3); } #[test] fn test_mixed() { let result = parse_ip_range("192.168.1.0/30,10.0.0.1").unwrap(); assert_eq!(result.len(), 5); } #[test] fn test_empty_input() { assert!(parse_ip_range("").is_err()); } #[test] fn test_invalid_cidr() { assert!(parse_ip_range("not.an.ip/24").is_err()); } #[test] fn test_reversed_range() { assert!(parse_ip_range("192.168.1.10-192.168.1.1").is_err()); } }