use core::fmt;
use core::net::Ipv6Addr;
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 Ipv6RangeError {
InvalidFormat,
InvalidIpAddr,
InvalidRange,
}
impl fmt::Display for Ipv6RangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => write!(f, "invalid IPv6 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 Ipv6RangeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Ipv6Range {
start: Ipv6Addr,
end: Ipv6Addr,
}
impl Ipv6Range {
#[inline]
pub fn new(start: Ipv6Addr, end: Ipv6Addr) -> Result<Self, Ipv6RangeError> {
let start_u128 = u128::from(start);
let end_u128 = u128::from(end);
if start_u128 > end_u128 {
return Err(Ipv6RangeError::InvalidRange);
}
Ok(Self { start, end })
}
#[inline]
#[must_use]
pub const fn start(&self) -> Ipv6Addr {
self.start
}
#[inline]
#[must_use]
pub const fn end(&self) -> Ipv6Addr {
self.end
}
#[inline]
#[must_use]
pub fn contains(&self, ip: &Ipv6Addr) -> bool {
let ip_u128 = u128::from(*ip);
let start_u128 = u128::from(self.start);
let end_u128 = u128::from(self.end);
ip_u128 >= start_u128 && ip_u128 <= end_u128
}
#[inline]
#[must_use]
pub fn num_addresses(&self) -> u128 {
let start_u128 = u128::from(self.start);
let end_u128 = u128::from(self.end);
end_u128 - start_u128 + 1
}
#[inline]
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
let self_start = u128::from(self.start);
let self_end = u128::from(self.end);
let other_start = u128::from(other.start);
let other_end = u128::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 = u128::from(self.start);
let self_end = u128::from(self.end);
let other_start = u128::from(other.start);
let other_end = u128::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 = u128::from(self.end);
let other_start = u128::from(other.start);
let other_end = u128::from(other.end);
let self_start = u128::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 = u128::from(self.start);
let self_end = u128::from(self.end);
let other_start = u128::from(other.start);
let other_end = u128::from(other.end);
let new_start = self_start.min(other_start);
let new_end = self_end.max(other_end);
Some(Self {
start: Ipv6Addr::from(new_start),
end: Ipv6Addr::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_u128 = u128::from(self.start);
let align_mask = count - 1;
(start_u128 & align_mask) == 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 Ipv6Range {
type Err = Ipv6RangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((start_str, end_str)) = s.split_once('-') else {
return Err(Ipv6RangeError::InvalidFormat);
};
let start: Ipv6Addr = start_str
.parse()
.map_err(|_| Ipv6RangeError::InvalidIpAddr)?;
let end: Ipv6Addr = end_str.parse().map_err(|_| Ipv6RangeError::InvalidIpAddr)?;
Self::new(start, end)
}
}
impl fmt::Display for Ipv6Range {
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 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
)
.unwrap();
assert_eq!(
range.start(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)
);
assert_eq!(
range.end(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)
);
}
#[test]
fn test_new_invalid_range() {
assert!(
Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db9, 0, 0, 0, 0, 0, 1),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
)
.is_err()
);
}
#[test]
fn test_parse() {
let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert_eq!(
range.start(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)
);
assert_eq!(
range.end(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)
);
}
#[test]
fn test_parse_invalid_format() {
assert!("2001:db8::1".parse::<Ipv6Range>().is_err());
assert!("2001:db8::1/64".parse::<Ipv6Range>().is_err());
}
#[test]
fn test_contains() {
let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)));
assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)));
assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x100)));
assert!(!range.contains(&Ipv6Addr::new(0x2001, 0x0db9, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn test_num_addresses() {
let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert_eq!(range.num_addresses(), 0xffff);
let single: Ipv6Range = "2001:db8::1-2001:db8::1".parse().unwrap();
assert_eq!(single.num_addresses(), 1);
}
#[test]
fn test_overlaps() {
let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
let range2: Ipv6Range = "2001:db8::100-2001:db9::1".parse().unwrap();
let range3: Ipv6Range = "2001:db9::1-2001:db9::ffff".parse().unwrap();
assert!(range1.overlaps(&range2));
assert!(range2.overlaps(&range1));
assert!(!range1.overlaps(&range3));
}
#[test]
fn test_display() {
let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert_eq!(format!("{}", range), "2001:db8::1-2001:db8::ffff");
}
#[test]
fn test_contains_range() {
let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
let range2: Ipv6Range = "2001:db8::100-2001:db8::200".parse().unwrap();
let range3: Ipv6Range = "2001:db8::1-2001:db9::ffff".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 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
)
.unwrap();
let range2 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
)
.unwrap();
let range3 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 30),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 40),
)
.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 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
)
.unwrap();
let range2 = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
)
.unwrap();
let merged = range1.merge(&range2).unwrap();
assert_eq!(
merged.start(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0)
);
assert_eq!(
merged.end(),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20)
);
}
#[test]
fn test_is_cidr_compatible() {
let cidr = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
)
.unwrap();
assert!(cidr.is_cidr_compatible());
let non_cidr: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert!(!non_cidr.is_cidr_compatible());
}
#[test]
fn test_cidr_prefix_len() {
let cidr = Ipv6Range::new(
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
)
.unwrap();
assert_eq!(cidr.cidr_prefix_len(), Some(112));
let single: Ipv6Range = "2001:db8::1-2001:db8::1".parse().unwrap();
assert_eq!(single.cidr_prefix_len(), Some(128));
let non_cidr: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
assert_eq!(non_cidr.cidr_prefix_len(), None);
}
}