use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Network {
addr: IpAddr,
prefix: u8,
}
impl Network {
pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, Error> {
let (addr, prefix) = match addr {
IpAddr::V6(a) => match a.to_ipv4_mapped() {
Some(_) if prefix < 96 => {
return Err(Error::invalid(
"network",
"IPv4-mapped networks need a prefix of at least 96",
));
}
Some(v4) => (IpAddr::V4(v4), prefix - 96),
None => (addr, prefix),
},
IpAddr::V4(_) => (addr, prefix),
};
if prefix > max_prefix(addr) {
return Err(Error::invalid("network", "prefix is longer than the address"));
}
Ok(Self::masked(addr, prefix))
}
pub fn enclosing(addr: IpAddr) -> impl Iterator<Item = Self> {
let addr = addr.to_canonical();
(0..=max_prefix(addr)).map(move |prefix| Self::masked(addr, prefix))
}
pub fn host(addr: IpAddr) -> Self {
let addr = addr.to_canonical();
Self::masked(addr, max_prefix(addr))
}
fn masked(addr: IpAddr, prefix: u8) -> Self {
let addr = match addr {
IpAddr::V4(a) => IpAddr::V4(Ipv4Addr::from_bits(a.to_bits() & mask(prefix, 32) as u32)),
IpAddr::V6(a) => IpAddr::V6(Ipv6Addr::from_bits(a.to_bits() & mask(prefix, 128))),
};
Self { addr, prefix }
}
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.addr, self.prefix)
}
}
impl FromStr for Network {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let invalid = || Error::invalid("network", "expected an address or addr/prefix");
match s.split_once('/') {
Some((addr, prefix)) => Self::new(
addr.parse().map_err(|_| invalid())?,
prefix.parse().map_err(|_| invalid())?,
),
None => Ok(Self::host(s.parse().map_err(|_| invalid())?)),
}
}
}
fn max_prefix(addr: IpAddr) -> u8 {
if addr.is_ipv4() { 32 } else { 128 }
}
fn mask(prefix: u8, width: u32) -> u128 {
match u32::from(prefix) {
0 => 0,
p => (u128::MAX << (128 - p)) >> (128 - width),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn net(s: &str) -> String {
s.parse::<Network>().unwrap().to_string()
}
#[test]
fn canonicalizes_host_bits() {
assert_eq!(net("10.1.2.3/8"), "10.0.0.0/8");
assert_eq!(net("2001:db8:1::1/32"), "2001:db8::/32");
assert_eq!(net("192.0.2.7"), "192.0.2.7/32");
assert_eq!(net("0.0.0.0/0"), "0.0.0.0/0");
assert_eq!(net("::ffff:192.0.2.7/120"), "192.0.2.0/24");
assert_eq!(net("::ffff:192.0.2.7"), "192.0.2.7/32");
assert_eq!(net("2001:db8::1"), "2001:db8::1/128");
}
#[test]
fn rejects_invalid_networks() {
for s in [
"10.0.0.0/33",
"::/129",
"::ffff:192.0.2.7/24",
"::ffff:192.0.2.7/129",
"10.0.0/8",
"10.0.0.0/",
"host",
"",
] {
assert!(s.parse::<Network>().is_err(), "{s}");
}
}
#[test]
fn enumerates_enclosing_networks() {
let v4: Vec<_> = Network::enclosing("192.0.2.7".parse().unwrap())
.map(|n| n.to_string())
.collect();
assert_eq!(v4.len(), 33);
assert_eq!(v4[0], "0.0.0.0/0");
assert_eq!(v4[24], "192.0.2.0/24");
assert_eq!(v4[32], "192.0.2.7/32");
let mapped = Network::enclosing("::ffff:192.0.2.7".parse().unwrap()).count();
assert_eq!(mapped, 33);
assert_eq!(Network::enclosing("2001:db8::1".parse().unwrap()).count(), 129);
}
}