1use std::net::{IpAddr, Ipv4Addr};
8
9pub fn normalize_ip(s: &str) -> String {
12 let s = s.trim();
13 let ip_part = s.split('/').next().unwrap_or(s);
15 match ip_part.parse::<IpAddr>() {
16 Ok(addr) => addr.to_string(),
17 Err(_) => String::new(),
18 }
19}
20
21pub fn ip_to_i64(s: &str) -> Option<i64> {
25 let s = s.trim();
26 let ip_part = s.split('/').next().unwrap_or(s);
27 match ip_part.parse::<IpAddr>() {
28 Ok(IpAddr::V4(v4)) => Some(u32::from(v4) as i64),
29 Ok(IpAddr::V6(v6)) => {
30 if let Some(v4) = v6.to_ipv4_mapped() {
32 Some(u32::from(v4) as i64)
33 } else {
34 None
35 }
36 }
37 Err(_) => None,
38 }
39}
40
41pub fn cidr_to_range(s: &str) -> Option<(i64, i64)> {
44 let parts: Vec<&str> = s.trim().split('/').collect();
45 if parts.len() != 2 {
46 return None;
47 }
48 let ip: Ipv4Addr = parts[0].parse().ok()?;
49 let prefix_len: u32 = parts[1].parse().ok()?;
50 if prefix_len > 32 {
51 return None;
52 }
53 let ip_num = u32::from(ip);
54 let mask = if prefix_len == 0 {
55 0u32
56 } else {
57 !0u32 << (32 - prefix_len)
58 };
59 let start = ip_num & mask;
60 let end = start | !mask;
61 Some((start as i64, end as i64))
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn normalize_ipv4() {
70 assert_eq!(normalize_ip("192.168.1.1"), "192.168.1.1");
71 assert_eq!(normalize_ip(" 10.0.0.1 "), "10.0.0.1");
72 }
73
74 #[test]
75 fn normalize_ipv6() {
76 assert_eq!(normalize_ip("::1"), "::1");
77 assert_eq!(normalize_ip("2001:db8::1"), "2001:db8::1");
78 }
79
80 #[test]
81 fn normalize_cidr_strips_prefix() {
82 assert_eq!(normalize_ip("192.168.0.0/16"), "192.168.0.0");
83 }
84
85 #[test]
86 fn normalize_invalid() {
87 assert_eq!(normalize_ip("not_an_ip"), "");
88 }
89
90 #[test]
91 fn ipv4_to_i64() {
92 assert_eq!(ip_to_i64("0.0.0.0"), Some(0));
93 assert_eq!(ip_to_i64("0.0.0.1"), Some(1));
94 assert_eq!(ip_to_i64("192.168.1.1"), Some(0xC0A80101));
95 assert_eq!(ip_to_i64("255.255.255.255"), Some(0xFFFFFFFF));
96 }
97
98 #[test]
99 fn ipv6_returns_none() {
100 assert_eq!(ip_to_i64("2001:db8::1"), None);
101 }
102
103 #[test]
104 fn ipv4_mapped_ipv6() {
105 assert_eq!(ip_to_i64("::ffff:192.168.1.1"), Some(0xC0A80101));
106 }
107
108 #[test]
109 fn cidr_range() {
110 let (start, end) = cidr_to_range("192.168.0.0/24").unwrap();
111 assert_eq!(start, ip_to_i64("192.168.0.0").unwrap());
112 assert_eq!(end, ip_to_i64("192.168.0.255").unwrap());
113 }
114
115 #[test]
116 fn cidr_16() {
117 let (start, end) = cidr_to_range("10.0.0.0/16").unwrap();
118 assert_eq!(start, ip_to_i64("10.0.0.0").unwrap());
119 assert_eq!(end, ip_to_i64("10.0.255.255").unwrap());
120 }
121
122 #[test]
123 fn cidr_32() {
124 let (start, end) = cidr_to_range("1.2.3.4/32").unwrap();
125 assert_eq!(start, end);
126 assert_eq!(start, ip_to_i64("1.2.3.4").unwrap());
127 }
128
129 #[test]
130 fn cidr_invalid() {
131 assert!(cidr_to_range("not_cidr").is_none());
132 assert!(cidr_to_range("192.168.0.0").is_none()); assert!(cidr_to_range("192.168.0.0/33").is_none()); }
135}