1use core::fmt;
7use core::net::Ipv4Addr;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[non_exhaustive]
17pub enum Ipv4RangeError {
18 InvalidFormat,
23 InvalidIpAddr,
27 InvalidRange,
31}
32
33impl fmt::Display for Ipv4RangeError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::InvalidFormat => write!(f, "invalid IPv4 range format"),
37 Self::InvalidIpAddr => write!(f, "invalid IP address"),
38 Self::InvalidRange => write!(f, "invalid range: start > end"),
39 }
40 }
41}
42
43#[cfg(feature = "std")]
44impl std::error::Error for Ipv4RangeError {}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub struct Ipv4Range {
66 start: Ipv4Addr,
68 end: Ipv4Addr,
70}
71
72impl Ipv4Range {
73 #[inline]
91 pub fn new(start: Ipv4Addr, end: Ipv4Addr) -> Result<Self, Ipv4RangeError> {
92 let start_u32 = u32::from(start);
93 let end_u32 = u32::from(end);
94
95 if start_u32 > end_u32 {
96 return Err(Ipv4RangeError::InvalidRange);
97 }
98
99 Ok(Self { start, end })
100 }
101
102 #[inline]
114 #[must_use]
115 pub const fn start(&self) -> Ipv4Addr {
116 self.start
117 }
118
119 #[inline]
131 #[must_use]
132 pub const fn end(&self) -> Ipv4Addr {
133 self.end
134 }
135
136 #[inline]
149 #[must_use]
150 pub fn contains(&self, ip: &Ipv4Addr) -> bool {
151 let ip_u32 = u32::from(*ip);
152 let start_u32 = u32::from(self.start);
153 let end_u32 = u32::from(self.end);
154 ip_u32 >= start_u32 && ip_u32 <= end_u32
155 }
156
157 #[inline]
168 #[must_use]
169 pub fn num_addresses(&self) -> u32 {
170 let start_u32 = u32::from(self.start);
171 let end_u32 = u32::from(self.end);
172 end_u32 - start_u32 + 1
173 }
174
175 #[inline]
187 #[must_use]
188 pub fn overlaps(&self, other: &Self) -> bool {
189 let self_start = u32::from(self.start);
190 let self_end = u32::from(self.end);
191 let other_start = u32::from(other.start);
192 let other_end = u32::from(other.end);
193
194 self_start <= other_end && self_end >= other_start
195 }
196
197 #[inline]
210 #[must_use]
211 pub fn contains_range(&self, other: &Self) -> bool {
212 let self_start = u32::from(self.start);
213 let self_end = u32::from(self.end);
214 let other_start = u32::from(other.start);
215 let other_end = u32::from(other.end);
216
217 self_start <= other_start && self_end >= other_end
218 }
219
220 #[inline]
235 #[must_use]
236 pub fn is_adjacent_to(&self, other: &Self) -> bool {
237 let self_end = u32::from(self.end);
238 let other_start = u32::from(other.start);
239 let other_end = u32::from(other.end);
240 let self_start = u32::from(self.start);
241
242 self_end + 1 == other_start || other_end + 1 == self_start
244 }
245
246 #[inline]
263 pub fn merge(&self, other: &Self) -> Option<Self> {
264 if self.overlaps(other) || self.is_adjacent_to(other) {
265 let self_start = u32::from(self.start);
266 let self_end = u32::from(self.end);
267 let other_start = u32::from(other.start);
268 let other_end = u32::from(other.end);
269
270 let new_start = self_start.min(other_start);
271 let new_end = self_end.max(other_end);
272
273 Some(Self {
274 start: Ipv4Addr::from(new_start),
275 end: Ipv4Addr::from(new_end),
276 })
277 } else {
278 None
279 }
280 }
281
282 #[inline]
300 #[must_use]
301 pub fn is_cidr_compatible(&self) -> bool {
302 let count = self.num_addresses();
303 if count == 0 {
304 return false;
305 }
306
307 if count & (count - 1) != 0 {
309 return false;
310 }
311
312 let start_u32 = u32::from(self.start);
314 start_u32 & (count - 1) == 0
315 }
316
317 #[inline]
328 #[must_use]
329 pub fn cidr_prefix_len(&self) -> Option<u8> {
330 if self.is_cidr_compatible() {
331 let count = self.num_addresses();
332 let prefix_len = (count.leading_zeros() + 1) as u8;
336 Some(prefix_len)
337 } else {
338 None
339 }
340 }
341}
342
343impl FromStr for Ipv4Range {
344 type Err = Ipv4RangeError;
345
346 fn from_str(s: &str) -> Result<Self, Self::Err> {
347 let Some((start_str, end_str)) = s.split_once('-') else {
348 return Err(Ipv4RangeError::InvalidFormat);
349 };
350
351 let start: Ipv4Addr = start_str
352 .parse()
353 .map_err(|_| Ipv4RangeError::InvalidIpAddr)?;
354
355 let end: Ipv4Addr = end_str.parse().map_err(|_| Ipv4RangeError::InvalidIpAddr)?;
356
357 Self::new(start, end)
358 }
359}
360
361impl fmt::Display for Ipv4Range {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 write!(f, "{}-{}", self.start, self.end)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_new_valid() {
373 let range = Ipv4Range::new(
374 Ipv4Addr::new(192, 168, 1, 0),
375 Ipv4Addr::new(192, 168, 1, 255),
376 )
377 .unwrap();
378 assert_eq!(range.start(), Ipv4Addr::new(192, 168, 1, 0));
379 assert_eq!(range.end(), Ipv4Addr::new(192, 168, 1, 255));
380 }
381
382 #[test]
383 fn test_new_invalid_range() {
384 assert!(
385 Ipv4Range::new(
386 Ipv4Addr::new(192, 168, 2, 0),
387 Ipv4Addr::new(192, 168, 1, 255)
388 )
389 .is_err()
390 );
391 }
392
393 #[test]
394 fn test_parse() {
395 let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
396 assert_eq!(range.start(), Ipv4Addr::new(192, 168, 1, 0));
397 assert_eq!(range.end(), Ipv4Addr::new(192, 168, 1, 255));
398 }
399
400 #[test]
401 fn test_parse_invalid_format() {
402 assert!("192.168.1.0".parse::<Ipv4Range>().is_err());
403 assert!("192.168.1.0/24".parse::<Ipv4Range>().is_err());
404 }
405
406 #[test]
407 fn test_contains() {
408 let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
409 assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 0)));
410 assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 255)));
411 assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 128)));
412 assert!(!range.contains(&Ipv4Addr::new(192, 168, 2, 0)));
413 }
414
415 #[test]
416 fn test_num_addresses() {
417 let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
418 assert_eq!(range.num_addresses(), 256);
419
420 let single: Ipv4Range = "192.168.1.100-192.168.1.100".parse().unwrap();
421 assert_eq!(single.num_addresses(), 1);
422 }
423
424 #[test]
425 fn test_overlaps() {
426 let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
427 let range2: Ipv4Range = "192.168.1.100-192.168.2.50".parse().unwrap();
428 let range3: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
429
430 assert!(range1.overlaps(&range2));
431 assert!(range2.overlaps(&range1));
432 assert!(!range1.overlaps(&range3));
433 }
434
435 #[test]
436 fn test_display() {
437 let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
438 assert_eq!(format!("{}", range), "192.168.1.0-192.168.1.255");
439 }
440
441 #[test]
442 fn test_contains_range() {
443 let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
444 let range2: Ipv4Range = "192.168.1.100-192.168.1.200".parse().unwrap();
445 let range3: Ipv4Range = "192.168.1.0-192.168.2.255".parse().unwrap();
446
447 assert!(range1.contains_range(&range2));
448 assert!(!range2.contains_range(&range1));
449 assert!(!range1.contains_range(&range3));
450 }
451
452 #[test]
453 fn test_is_adjacent_to() {
454 let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
455 let range2: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
456 let range3: Ipv4Range = "192.168.3.0-192.168.3.255".parse().unwrap();
457
458 assert!(range1.is_adjacent_to(&range2));
459 assert!(range2.is_adjacent_to(&range1));
460 assert!(!range1.is_adjacent_to(&range3));
461 }
462
463 #[test]
464 fn test_merge() {
465 let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
467 let range2: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
468 let merged = range1.merge(&range2).unwrap();
469 assert_eq!(merged.start(), Ipv4Addr::new(192, 168, 1, 0));
470 assert_eq!(merged.end(), Ipv4Addr::new(192, 168, 2, 255));
471
472 let range3: Ipv4Range = "192.168.1.100-192.168.2.50".parse().unwrap();
474 let merged2 = range1.merge(&range3).unwrap();
475 assert_eq!(merged2.start(), Ipv4Addr::new(192, 168, 1, 0));
476 assert_eq!(merged2.end(), Ipv4Addr::new(192, 168, 2, 50));
477
478 let range4: Ipv4Range = "192.168.5.0-192.168.5.255".parse().unwrap();
480 assert!(range1.merge(&range4).is_none());
481 }
482
483 #[test]
484 fn test_is_cidr_compatible() {
485 let cidr: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
487 assert!(cidr.is_cidr_compatible());
488
489 let cidr2: Ipv4Range = "192.168.0.0-192.168.255.255".parse().unwrap();
491 assert!(cidr2.is_cidr_compatible());
492
493 let non_cidr: Ipv4Range = "192.168.1.0-192.168.1.100".parse().unwrap();
495 assert!(!non_cidr.is_cidr_compatible());
496 }
497
498 #[test]
499 fn test_cidr_prefix_len() {
500 let cidr: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
501 assert_eq!(cidr.cidr_prefix_len(), Some(24));
502
503 let cidr2: Ipv4Range = "192.168.0.0-192.168.255.255".parse().unwrap();
504 assert_eq!(cidr2.cidr_prefix_len(), Some(16));
505
506 let cidr3: Ipv4Range = "192.168.1.100-192.168.1.100".parse().unwrap();
507 assert_eq!(cidr3.cidr_prefix_len(), Some(32));
508
509 let non_cidr: Ipv4Range = "192.168.1.0-192.168.1.100".parse().unwrap();
510 assert_eq!(non_cidr.cidr_prefix_len(), None);
511 }
512}