1use std::{
20 net::{IpAddr, Ipv4Addr, Ipv6Addr},
21 str::FromStr,
22};
23use thiserror::Error;
24
25#[derive(Debug, Error, PartialEq)]
27pub enum IpError {
28 #[error("Invalid range: start address {0} is greater than end address {1}")]
30 InvalidRange(IpAddr, IpAddr),
31
32 #[error("Invalid CIDR prefix: {0}")]
34 InvalidPrefix(u8),
35
36 #[error("Network error: {0}")]
38 NetworkError(String),
39
40 #[error("Failed to parse IP address: {0}")]
42 AddrParse(#[from] std::net::AddrParseError),
43
44 #[error("Invalid IP range format: {0}")]
46 InvalidFormat(String),
47
48 #[error("Invalid prefix number format: {0}")]
50 PrefixParse(#[from] std::num::ParseIntError),
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub struct Ipv4Range {
62 pub start_addr: Ipv4Addr,
64 pub end_addr: Ipv4Addr,
66}
67
68impl Ipv4Range {
69 pub fn new(start: Ipv4Addr, end: Ipv4Addr) -> Result<Self, IpError> {
75 if u32::from(start) <= u32::from(end) {
76 Ok(Self {
77 start_addr: start,
78 end_addr: end,
79 })
80 } else {
81 Err(IpError::InvalidRange(IpAddr::V4(start), IpAddr::V4(end)))
82 }
83 }
84
85 pub fn to_iter(&self) -> impl Iterator<Item = IpAddr> {
92 let start: u32 = self.start_addr.into();
93 let end: u32 = self.end_addr.into();
94 (start..=end).map(|ip| IpAddr::V4(Ipv4Addr::from(ip)))
95 }
96
97 pub fn contains(&self, ip: &Ipv4Addr) -> bool {
99 let start: u32 = self.start_addr.into();
100 let end: u32 = self.end_addr.into();
101 let ip_u32: u32 = (*ip).into();
102 ip_u32 >= start && ip_u32 <= end
103 }
104
105 pub fn len(&self) -> u64 {
107 let s_u32: u64 = u32::from(self.start_addr) as u64;
108 let e_u32: u64 = u32::from(self.end_addr) as u64;
109 (e_u32 - s_u32) + 1
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.len() == 0
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub struct Ipv6Range {
127 pub start_addr: Ipv6Addr,
129 pub end_addr: Ipv6Addr,
131}
132
133impl Ipv6Range {
134 pub fn new(start: Ipv6Addr, end: Ipv6Addr) -> Result<Self, IpError> {
140 if u128::from(start) <= u128::from(end) {
141 Ok(Self {
142 start_addr: start,
143 end_addr: end,
144 })
145 } else {
146 Err(IpError::InvalidRange(IpAddr::V6(start), IpAddr::V6(end)))
147 }
148 }
149
150 pub fn to_iter(&self) -> impl Iterator<Item = IpAddr> {
158 let start: u128 = self.start_addr.into();
159 let end: u128 = self.end_addr.into();
160 (start..=end).map(|ip| IpAddr::V6(Ipv6Addr::from(ip)))
161 }
162
163 pub fn contains(&self, ip: &Ipv6Addr) -> bool {
165 let start: u128 = self.start_addr.into();
166 let end: u128 = self.end_addr.into();
167 let ip_u128: u128 = (*ip).into();
168 ip_u128 >= start && ip_u128 <= end
169 }
170
171 pub fn len(&self) -> u128 {
173 let s_u128: u128 = u128::from(self.start_addr);
174 let e_u128: u128 = u128::from(self.end_addr);
175 (e_u128 - s_u128) + 1
176 }
177
178 pub fn is_empty(&self) -> bool {
180 self.len() == 0
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
193pub enum IpRange {
194 V4(Ipv4Range),
196 V6(Ipv6Range),
198}
199
200impl IpRange {
201 pub fn start_addr(&self) -> IpAddr {
203 match self {
204 IpRange::V4(r) => IpAddr::V4(r.start_addr),
205 IpRange::V6(r) => IpAddr::V6(r.start_addr),
206 }
207 }
208
209 pub fn end_addr(&self) -> IpAddr {
211 match self {
212 IpRange::V4(r) => IpAddr::V4(r.end_addr),
213 IpRange::V6(r) => IpAddr::V6(r.end_addr),
214 }
215 }
216
217 pub fn contains(&self, ip: &IpAddr) -> bool {
222 match (self, ip) {
223 (IpRange::V4(r), IpAddr::V4(ip)) => r.contains(ip),
224 (IpRange::V6(r), IpAddr::V6(ip)) => r.contains(ip),
225 _ => false,
226 }
227 }
228
229 pub fn len(&self) -> u128 {
231 match self {
232 IpRange::V4(r) => r.len() as u128,
233 IpRange::V6(r) => r.len(),
234 }
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.len() == 0
240 }
241}
242
243impl FromStr for IpRange {
244 type Err = IpError;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
253 let s = s.trim();
254
255 if let Some(pos) = s.find('/') {
257 let ip = s[..pos].parse::<IpAddr>()?;
258 let prefix = s[pos + 1..].parse::<u8>()?;
259 return cidr_range(ip, prefix);
260 }
261
262 if let Some(pos) = s.find('-') {
264 let start_str = s[..pos].trim();
265 let end_str = s[pos + 1..].trim();
266
267 if let Ok(start) = start_str.parse::<Ipv4Addr>() {
268 let end = end_str.parse::<Ipv4Addr>()?;
269 return Ok(IpRange::V4(Ipv4Range::new(start, end)?));
270 } else if let Ok(start) = start_str.parse::<Ipv6Addr>() {
271 let end = end_str.parse::<Ipv6Addr>()?;
272 return Ok(IpRange::V6(Ipv6Range::new(start, end)?));
273 }
274 return Err(IpError::InvalidFormat(s.to_string()));
275 }
276
277 let ip = s.parse::<IpAddr>()?;
279 match ip {
280 IpAddr::V4(v4) => Ok(IpRange::V4(Ipv4Range::new(v4, v4).unwrap())),
281 IpAddr::V6(v6) => Ok(IpRange::V6(Ipv6Range::new(v6, v6).unwrap())),
282 }
283 }
284}
285
286pub fn cidr_range(ip: IpAddr, prefix: u8) -> Result<IpRange, IpError> {
298 match ip {
299 IpAddr::V4(v4) => {
300 if prefix > 32 {
301 return Err(IpError::InvalidPrefix(prefix));
302 }
303
304 let ip_u32 = u32::from(v4);
305 let mask = if prefix == 0 {
306 0
307 } else {
308 !u32::MAX.checked_shr(prefix as u32).unwrap_or(0)
309 };
310
311 let network = ip_u32 & mask;
312 let broadcast = ip_u32 | !mask;
313
314 Ok(IpRange::V4(
315 Ipv4Range::new(Ipv4Addr::from(network), Ipv4Addr::from(broadcast)).unwrap(),
316 ))
317 }
318 IpAddr::V6(v6) => {
319 if prefix > 128 {
320 return Err(IpError::InvalidPrefix(prefix));
321 }
322
323 let ip_u128 = u128::from(v6);
324 let mask = if prefix == 0 {
325 0
326 } else {
327 !u128::MAX.checked_shr(prefix as u32).unwrap_or(0)
328 };
329
330 let network = ip_u128 & mask;
331 let broadcast = ip_u128 | !mask;
332
333 Ok(IpRange::V6(
334 Ipv6Range::new(Ipv6Addr::from(network), Ipv6Addr::from(broadcast)).unwrap(),
335 ))
336 }
337 }
338}
339
340#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
356 fn new_valid_v4() {
357 let start = Ipv4Addr::new(192, 168, 1, 1);
358 let end = Ipv4Addr::new(192, 168, 1, 10);
359 let range = Ipv4Range::new(start, end).unwrap();
360 assert_eq!(range.start_addr, start);
361 assert_eq!(range.end_addr, end);
362 }
363
364 #[test]
365 fn len_calculations_v4() {
366 let cases = vec![
367 (Ipv4Addr::new(10, 0, 0, 0), Ipv4Addr::new(10, 0, 0, 0), 1),
368 (Ipv4Addr::new(10, 0, 0, 0), Ipv4Addr::new(10, 0, 0, 255), 256),
369 (Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(0, 0, 0, 10), 11),
370 ];
371
372 for (start, end, expected_len) in cases {
373 let range = Ipv4Range::new(start, end).unwrap();
374 assert_eq!(range.len(), expected_len);
375 }
376 }
377
378 #[test]
379 fn contains_logic_v4() {
380 let range = Ipv4Range::new(Ipv4Addr::new(172, 16, 0, 10), Ipv4Addr::new(172, 16, 0, 20)).unwrap();
381 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 10)));
382 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 15)));
383 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 20)));
384 assert!(!range.contains(&Ipv4Addr::new(172, 16, 0, 9)));
385 assert!(!range.contains(&Ipv4Addr::new(172, 16, 0, 21)));
386 }
387
388 #[test]
389 fn iteration_values_v4() {
390 let range = Ipv4Range::new(Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(1, 1, 1, 3)).unwrap();
391 let ips: Vec<IpAddr> = range.to_iter().collect();
392 assert_eq!(ips, vec![
393 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
394 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2)),
395 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
396 ]);
397 }
398
399 #[test]
400 fn max_u32_range_boundaries() {
401 let start = Ipv4Addr::new(255, 255, 255, 254);
402 let end = Ipv4Addr::new(255, 255, 255, 255);
403 let range = Ipv4Range::new(start, end).unwrap();
404 assert_eq!(range.len(), 2);
405 }
406
407 #[test]
410 fn ipv6_range_basics() {
411 let start = Ipv6Addr::from(100);
412 let end = Ipv6Addr::from(200);
413 let range = Ipv6Range::new(start, end).unwrap();
414 assert_eq!(range.len(), 101);
415 assert!(range.contains(&Ipv6Addr::from(150)));
416 assert!(!range.contains(&Ipv6Addr::from(201)));
417 }
418
419 #[test]
420 fn ipv6_large_len() {
421 let range = cidr_range(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 64).unwrap();
422 assert_eq!(range.len(), 1u128 << 64);
423 }
424
425 #[test]
426 fn iteration_ipv6_small() {
427 let range = Ipv6Range::new(Ipv6Addr::from(1), Ipv6Addr::from(3)).unwrap();
428 let ips: Vec<_> = range.to_iter().collect();
429 assert_eq!(ips.len(), 3);
430 assert_eq!(ips[0], IpAddr::V6(Ipv6Addr::from(1)));
431 }
432
433 #[test]
436 fn from_str_comprehensive() {
437 assert_eq!("10.0.0.0/24".parse::<IpRange>().unwrap().len(), 256);
438 assert_eq!("192.168.1.0/24".parse::<IpRange>().unwrap().len(), 256);
439 assert_eq!("::1/120".parse::<IpRange>().unwrap().len(), 256);
440 assert_eq!("1.1.1.1-1.1.1.5".parse::<IpRange>().unwrap().len(), 5);
441 assert_eq!("8.8.8.8".parse::<IpRange>().unwrap().len(), 1);
442 }
443
444 #[test]
445 fn invalid_range_order() {
446 let v4_err = Ipv4Range::new(Ipv4Addr::new(1, 1, 1, 2), Ipv4Addr::new(1, 1, 1, 1));
447 assert!(matches!(v4_err, Err(IpError::InvalidRange(_, _))));
448
449 let v6_err = Ipv6Range::new(Ipv6Addr::from(2), Ipv6Addr::from(1));
450 assert!(matches!(v6_err, Err(IpError::InvalidRange(_, _))));
451 }
452
453 #[test]
454 fn error_formatting() {
455 let prefix_err = IpError::InvalidPrefix(40);
456 assert_eq!(format!("{prefix_err}"), "Invalid CIDR prefix: 40");
457
458 let range_err = IpError::InvalidRange(
459 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2)),
460 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
461 );
462 assert!(format!("{range_err}").contains("is greater than"));
463 }
464}
465
466#[cfg(test)]
467mod property_tests {
468 use super::*;
469 use proptest::prelude::*;
470
471 fn any_ipv4() -> impl Strategy<Value = Ipv4Addr> {
472 proptest::prelude::any::<u32>().prop_map(Ipv4Addr::from)
473 }
474
475 fn any_ipv6() -> impl Strategy<Value = Ipv6Addr> {
476 proptest::prelude::any::<u128>().prop_map(Ipv6Addr::from)
477 }
478
479 fn any_ipv4_range() -> impl Strategy<Value = Ipv4Range> {
480 (any_ipv4(), 0..5000u32).prop_map(|(start, len)| {
481 let start_u32 = u32::from(start);
482 let end_u32 = start_u32.saturating_add(len);
483 Ipv4Range::new(start, Ipv4Addr::from(end_u32)).unwrap()
484 })
485 }
486
487 fn any_ipv6_range() -> impl Strategy<Value = Ipv6Range> {
488 (any_ipv6(), 0..5000u128).prop_map(|(start, len)| {
489 let start_u128 = u128::from(start);
490 let end_u128 = start_u128.saturating_add(len);
491 Ipv6Range::new(start, Ipv6Addr::from(end_u128)).unwrap()
492 })
493 }
494
495 proptest::proptest! {
496 #[test]
497 fn ipv4_range_invariant(a in any_ipv4(), b in any_ipv4()) {
498 let start = std::cmp::min(a, b);
499 let end = std::cmp::max(a, b);
500 let range = Ipv4Range::new(start, end).unwrap();
501 prop_assert!(range.contains(&start));
502 prop_assert!(range.contains(&end));
503 prop_assert_eq!(range.len(), (u32::from(end) - u32::from(start)) as u64 + 1);
504 }
505
506 #[test]
507 fn ipv6_range_invariant(a in any_ipv6(), b in any_ipv6()) {
508 let start = std::cmp::min(a, b);
509 let end = std::cmp::max(a, b);
510 let range = Ipv6Range::new(start, end).unwrap();
511 prop_assert!(range.contains(&start));
512 prop_assert!(range.contains(&end));
513 prop_assert_eq!(range.len(), (u128::from(end) - u128::from(start)) + 1);
514 }
515
516 #[test]
517 fn ipv4_iterator_consistency(range in any_ipv4_range()) {
518 prop_assert_eq!(range.to_iter().count() as u64, range.len());
519 }
520
521 #[test]
522 fn ipv6_iterator_consistency(range in any_ipv6_range()) {
523 prop_assert_eq!(range.to_iter().count() as u128, range.len());
524 }
525
526 #[test]
527 fn cidr_v4_roundtrip(v4 in any_ipv4(), prefix in 1..=32u8) {
528 let range = cidr_range(IpAddr::V4(v4), prefix).unwrap();
529 prop_assert_eq!(range.len() as u128, 1u128 << (32 - prefix));
530 }
531
532 #[test]
533 fn cidr_v6_roundtrip(v6 in any_ipv6(), prefix in 1..=128u8) {
534 let range = cidr_range(IpAddr::V6(v6), prefix).unwrap();
535 prop_assert_eq!(range.len(), 1u128 << (128 - prefix));
536 }
537 }
538}