Initial Commit

This commit is contained in:
2026-09-23 09:16:09 -04:00
commit 0cd2a516bb
49 changed files with 10134 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
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());
}
}