144 lines
3.8 KiB
Rust
144 lines
3.8 KiB
Rust
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<Vec<IpAddr>, 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<IpAddr>) -> 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<IpAddr>) -> 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<IpAddr>) -> 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());
|
|
}
|
|
}
|