use core::fmt;
use core::net::Ipv4Addr;
use core::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum Ipv4RangeError {
InvalidFormat,
InvalidIpAddr,
InvalidRange,
}
impl fmt::Display for Ipv4RangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => write!(f, "invalid IPv4 range format"),
Self::InvalidIpAddr => write!(f, "invalid IP address"),
Self::InvalidRange => write!(f, "invalid range: start > end"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Ipv4RangeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Ipv4Range {
start: Ipv4Addr,
end: Ipv4Addr,
}
impl Ipv4Range {
#[inline]
pub fn new(start: Ipv4Addr, end: Ipv4Addr) -> Result<Self, Ipv4RangeError> {
let start_u32 = u32::from(start);
let end_u32 = u32::from(end);
if start_u32 > end_u32 {
return Err(Ipv4RangeError::InvalidRange);
}
Ok(Self { start, end })
}
#[inline]
#[must_use]
pub const fn start(&self) -> Ipv4Addr {
self.start
}
#[inline]
#[must_use]
pub const fn end(&self) -> Ipv4Addr {
self.end
}
#[inline]
#[must_use]
pub fn contains(&self, ip: &Ipv4Addr) -> bool {
let ip_u32 = u32::from(*ip);
let start_u32 = u32::from(self.start);
let end_u32 = u32::from(self.end);
ip_u32 >= start_u32 && ip_u32 <= end_u32
}
#[inline]
#[must_use]
pub fn num_addresses(&self) -> u32 {
let start_u32 = u32::from(self.start);
let end_u32 = u32::from(self.end);
end_u32 - start_u32 + 1
}
#[inline]
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
let self_start = u32::from(self.start);
let self_end = u32::from(self.end);
let other_start = u32::from(other.start);
let other_end = u32::from(other.end);
self_start <= other_end && self_end >= other_start
}
#[inline]
#[must_use]
pub fn contains_range(&self, other: &Self) -> bool {
let self_start = u32::from(self.start);
let self_end = u32::from(self.end);
let other_start = u32::from(other.start);
let other_end = u32::from(other.end);
self_start <= other_start && self_end >= other_end
}
#[inline]
#[must_use]
pub fn is_adjacent_to(&self, other: &Self) -> bool {
let self_end = u32::from(self.end);
let other_start = u32::from(other.start);
let other_end = u32::from(other.end);
let self_start = u32::from(self.start);
self_end + 1 == other_start || other_end + 1 == self_start
}
#[inline]
pub fn merge(&self, other: &Self) -> Option<Self> {
if self.overlaps(other) || self.is_adjacent_to(other) {
let self_start = u32::from(self.start);
let self_end = u32::from(self.end);
let other_start = u32::from(other.start);
let other_end = u32::from(other.end);
let new_start = self_start.min(other_start);
let new_end = self_end.max(other_end);
Some(Self {
start: Ipv4Addr::from(new_start),
end: Ipv4Addr::from(new_end),
})
} else {
None
}
}
#[inline]
#[must_use]
pub fn is_cidr_compatible(&self) -> bool {
let count = self.num_addresses();
if count == 0 {
return false;
}
if count & (count - 1) != 0 {
return false;
}
let start_u32 = u32::from(self.start);
start_u32 & (count - 1) == 0
}
#[inline]
#[must_use]
pub fn cidr_prefix_len(&self) -> Option<u8> {
if self.is_cidr_compatible() {
let count = self.num_addresses();
let prefix_len = (count.leading_zeros() + 1) as u8;
Some(prefix_len)
} else {
None
}
}
}
impl FromStr for Ipv4Range {
type Err = Ipv4RangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((start_str, end_str)) = s.split_once('-') else {
return Err(Ipv4RangeError::InvalidFormat);
};
let start: Ipv4Addr = start_str
.parse()
.map_err(|_| Ipv4RangeError::InvalidIpAddr)?;
let end: Ipv4Addr = end_str.parse().map_err(|_| Ipv4RangeError::InvalidIpAddr)?;
Self::new(start, end)
}
}
impl fmt::Display for Ipv4Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid() {
let range = Ipv4Range::new(
Ipv4Addr::new(192, 168, 1, 0),
Ipv4Addr::new(192, 168, 1, 255),
)
.unwrap();
assert_eq!(range.start(), Ipv4Addr::new(192, 168, 1, 0));
assert_eq!(range.end(), Ipv4Addr::new(192, 168, 1, 255));
}
#[test]
fn test_new_invalid_range() {
assert!(
Ipv4Range::new(
Ipv4Addr::new(192, 168, 2, 0),
Ipv4Addr::new(192, 168, 1, 255)
)
.is_err()
);
}
#[test]
fn test_parse() {
let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert_eq!(range.start(), Ipv4Addr::new(192, 168, 1, 0));
assert_eq!(range.end(), Ipv4Addr::new(192, 168, 1, 255));
}
#[test]
fn test_parse_invalid_format() {
assert!("192.168.1.0".parse::<Ipv4Range>().is_err());
assert!("192.168.1.0/24".parse::<Ipv4Range>().is_err());
}
#[test]
fn test_contains() {
let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 0)));
assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 255)));
assert!(range.contains(&Ipv4Addr::new(192, 168, 1, 128)));
assert!(!range.contains(&Ipv4Addr::new(192, 168, 2, 0)));
}
#[test]
fn test_num_addresses() {
let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert_eq!(range.num_addresses(), 256);
let single: Ipv4Range = "192.168.1.100-192.168.1.100".parse().unwrap();
assert_eq!(single.num_addresses(), 1);
}
#[test]
fn test_overlaps() {
let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
let range2: Ipv4Range = "192.168.1.100-192.168.2.50".parse().unwrap();
let range3: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
assert!(range1.overlaps(&range2));
assert!(range2.overlaps(&range1));
assert!(!range1.overlaps(&range3));
}
#[test]
fn test_display() {
let range: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert_eq!(format!("{}", range), "192.168.1.0-192.168.1.255");
}
#[test]
fn test_contains_range() {
let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
let range2: Ipv4Range = "192.168.1.100-192.168.1.200".parse().unwrap();
let range3: Ipv4Range = "192.168.1.0-192.168.2.255".parse().unwrap();
assert!(range1.contains_range(&range2));
assert!(!range2.contains_range(&range1));
assert!(!range1.contains_range(&range3));
}
#[test]
fn test_is_adjacent_to() {
let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
let range2: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
let range3: Ipv4Range = "192.168.3.0-192.168.3.255".parse().unwrap();
assert!(range1.is_adjacent_to(&range2));
assert!(range2.is_adjacent_to(&range1));
assert!(!range1.is_adjacent_to(&range3));
}
#[test]
fn test_merge() {
let range1: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
let range2: Ipv4Range = "192.168.2.0-192.168.2.255".parse().unwrap();
let merged = range1.merge(&range2).unwrap();
assert_eq!(merged.start(), Ipv4Addr::new(192, 168, 1, 0));
assert_eq!(merged.end(), Ipv4Addr::new(192, 168, 2, 255));
let range3: Ipv4Range = "192.168.1.100-192.168.2.50".parse().unwrap();
let merged2 = range1.merge(&range3).unwrap();
assert_eq!(merged2.start(), Ipv4Addr::new(192, 168, 1, 0));
assert_eq!(merged2.end(), Ipv4Addr::new(192, 168, 2, 50));
let range4: Ipv4Range = "192.168.5.0-192.168.5.255".parse().unwrap();
assert!(range1.merge(&range4).is_none());
}
#[test]
fn test_is_cidr_compatible() {
let cidr: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert!(cidr.is_cidr_compatible());
let cidr2: Ipv4Range = "192.168.0.0-192.168.255.255".parse().unwrap();
assert!(cidr2.is_cidr_compatible());
let non_cidr: Ipv4Range = "192.168.1.0-192.168.1.100".parse().unwrap();
assert!(!non_cidr.is_cidr_compatible());
}
#[test]
fn test_cidr_prefix_len() {
let cidr: Ipv4Range = "192.168.1.0-192.168.1.255".parse().unwrap();
assert_eq!(cidr.cidr_prefix_len(), Some(24));
let cidr2: Ipv4Range = "192.168.0.0-192.168.255.255".parse().unwrap();
assert_eq!(cidr2.cidr_prefix_len(), Some(16));
let cidr3: Ipv4Range = "192.168.1.100-192.168.1.100".parse().unwrap();
assert_eq!(cidr3.cidr_prefix_len(), Some(32));
let non_cidr: Ipv4Range = "192.168.1.0-192.168.1.100".parse().unwrap();
assert_eq!(non_cidr.cidr_prefix_len(), None);
}
}