1use std::fmt;
10use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
11use std::str::FromStr;
12
13use crate::Error;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct Network {
19 addr: IpAddr,
20 prefix: u8,
21}
22
23impl Network {
24 pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, Error> {
31 let (addr, prefix) = match addr {
32 IpAddr::V6(a) => match a.to_ipv4_mapped() {
33 Some(_) if prefix < 96 => {
34 return Err(Error::invalid(
35 "network",
36 "IPv4-mapped networks need a prefix of at least 96",
37 ));
38 }
39 Some(v4) => (IpAddr::V4(v4), prefix - 96),
40 None => (addr, prefix),
41 },
42 IpAddr::V4(_) => (addr, prefix),
43 };
44 if prefix > max_prefix(addr) {
45 return Err(Error::invalid("network", "prefix is longer than the address"));
46 }
47 Ok(Self::masked(addr, prefix))
48 }
49
50 pub fn enclosing(addr: IpAddr) -> impl Iterator<Item = Self> {
53 let addr = addr.to_canonical();
54 (0..=max_prefix(addr)).map(move |prefix| Self::masked(addr, prefix))
55 }
56
57 pub fn host(addr: IpAddr) -> Self {
59 let addr = addr.to_canonical();
60 Self::masked(addr, max_prefix(addr))
61 }
62
63 fn masked(addr: IpAddr, prefix: u8) -> Self {
66 let addr = match addr {
67 IpAddr::V4(a) => IpAddr::V4(Ipv4Addr::from_bits(a.to_bits() & mask(prefix, 32) as u32)),
69 IpAddr::V6(a) => IpAddr::V6(Ipv6Addr::from_bits(a.to_bits() & mask(prefix, 128))),
70 };
71 Self { addr, prefix }
72 }
73}
74
75impl fmt::Display for Network {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}/{}", self.addr, self.prefix)
78 }
79}
80
81impl FromStr for Network {
82 type Err = Error;
83
84 fn from_str(s: &str) -> Result<Self, Error> {
86 let invalid = || Error::invalid("network", "expected an address or addr/prefix");
87 match s.split_once('/') {
88 Some((addr, prefix)) => Self::new(
89 addr.parse().map_err(|_| invalid())?,
90 prefix.parse().map_err(|_| invalid())?,
91 ),
92 None => Ok(Self::host(s.parse().map_err(|_| invalid())?)),
93 }
94 }
95}
96
97fn max_prefix(addr: IpAddr) -> u8 {
98 if addr.is_ipv4() { 32 } else { 128 }
99}
100
101fn mask(prefix: u8, width: u32) -> u128 {
103 match u32::from(prefix) {
104 0 => 0,
105 p => (u128::MAX << (128 - p)) >> (128 - width),
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 fn net(s: &str) -> String {
114 s.parse::<Network>().unwrap().to_string()
115 }
116
117 #[test]
118 fn canonicalizes_host_bits() {
119 assert_eq!(net("10.1.2.3/8"), "10.0.0.0/8");
120 assert_eq!(net("2001:db8:1::1/32"), "2001:db8::/32");
121 assert_eq!(net("192.0.2.7"), "192.0.2.7/32");
122 assert_eq!(net("0.0.0.0/0"), "0.0.0.0/0");
123 assert_eq!(net("::ffff:192.0.2.7/120"), "192.0.2.0/24");
124 assert_eq!(net("::ffff:192.0.2.7"), "192.0.2.7/32");
125 assert_eq!(net("2001:db8::1"), "2001:db8::1/128");
126 }
127
128 #[test]
129 fn rejects_invalid_networks() {
130 for s in [
131 "10.0.0.0/33",
132 "::/129",
133 "::ffff:192.0.2.7/24",
134 "::ffff:192.0.2.7/129",
135 "10.0.0/8",
136 "10.0.0.0/",
137 "host",
138 "",
139 ] {
140 assert!(s.parse::<Network>().is_err(), "{s}");
141 }
142 }
143
144 #[test]
145 fn enumerates_enclosing_networks() {
146 let v4: Vec<_> = Network::enclosing("192.0.2.7".parse().unwrap())
147 .map(|n| n.to_string())
148 .collect();
149 assert_eq!(v4.len(), 33);
150 assert_eq!(v4[0], "0.0.0.0/0");
151 assert_eq!(v4[24], "192.0.2.0/24");
152 assert_eq!(v4[32], "192.0.2.7/32");
153
154 let mapped = Network::enclosing("::ffff:192.0.2.7".parse().unwrap()).count();
155 assert_eq!(mapped, 33);
156 assert_eq!(Network::enclosing("2001:db8::1".parse().unwrap()).count(), 129);
157 }
158}