1use std::{
4 fmt::{self, Display, Formatter},
5 net::{IpAddr, Ipv4Addr, Ipv6Addr},
6 str::FromStr,
7};
8
9mod constants {
10 pub const MAXIMUM_IPV4_MASK: u8 = 32;
11 pub const MAXIMUM_IPV6_MASK: u8 = 128;
12}
13
14pub struct Cidr {
16 pub addr: IpAddr,
18
19 pub mask: u8,
21}
22
23impl FromStr for Cidr {
24 type Err = Error;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 let (addr, mask) = match s.split_once("/") {
28 Some(res) => res,
29 None => return Err(Error::NoSlash),
30 };
31
32 let is_ipv4 = Ipv4Addr::from_str(addr);
33 let is_ipv6 = Ipv6Addr::from_str(addr);
34
35 if is_ipv4.is_err() && is_ipv6.is_err() {
36 return Err(Error::NotAddr(addr.to_owned()));
37 }
38
39 if let Ok(v4) = is_ipv4 {
40 let mask = match mask.parse::<u8>() {
41 Ok(res) => res,
42 Err(_) => return Err(Error::NotMask(mask.to_string())),
43 };
44
45 if mask > constants::MAXIMUM_IPV4_MASK {
46 return Err(Error::NotMask(mask.to_string()));
47 }
48
49 return Ok(Cidr {
50 addr: IpAddr::V4(v4),
51 mask,
52 });
53 }
54
55 let v6 = unsafe { is_ipv6.unwrap_unchecked() };
56
57 let mask = match mask.parse::<u8>() {
58 Ok(res) => res,
59 Err(_) => return Err(Error::NotMask(mask.to_owned())),
60 };
61
62 if mask > constants::MAXIMUM_IPV6_MASK {
63 return Err(Error::NotMask(mask.to_string()));
64 }
65
66 Ok(Cidr {
67 addr: IpAddr::V6(v6),
68 mask,
69 })
70 }
71}
72
73#[derive(Debug)]
75pub enum Error {
76 NoSlash,
78
79 NotAddr(String),
81
82 NotMask(String),
84}
85
86impl Display for Error {
87 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
88 match self {
89 Error::NoSlash => write!(f, "no forward slash found"),
90 Error::NotAddr(addr) => write!(f, "{} is not a valid ip address", addr),
91 Error::NotMask(mask) => write!(f, "{} is not a valid subnet mask", mask),
92 }
93 }
94}
95
96impl std::error::Error for Error {}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_cidr() {
104 let iplist = [
105 "0.0.0.0/8",
106 "10.0.0.0/8",
107 "100.64.0.0/10",
108 "127.0.0.0/8",
109 "169.254.0.0/16",
110 "172.16.0.0/12",
111 "192.0.0.0/24",
112 "192.0.2.0/24",
113 "192.88.99.0/24",
114 "192.168.0.0/16",
115 "198.18.0.0/15",
116 "198.51.100.0/24",
117 "203.0.113.0/24",
118 "220.160.0.0/11",
119 "224.0.0.0/4",
120 "240.0.0.0/4",
121 "255.255.255.255/32",
122 "::1/128",
123 "::ffff:127.0.0.1/104",
124 "fc00::/7",
125 "fe80::/10",
126 "2001:b28:f23d:f001::e/128",
127 ];
128
129 for ip in iplist {
130 let _: Cidr = ip.parse().unwrap();
131 }
132 }
133
134 #[test]
135 fn test_error() {
136 assert!("127.0.0.1".parse::<Cidr>().is_err());
137 assert!("127.0.0./12".parse::<Cidr>().is_err());
138 assert!("127.0.0/12".parse::<Cidr>().is_err());
139
140 assert!(":1:/12".parse::<Cidr>().is_err());
141 assert!("122ff:/12".parse::<Cidr>().is_err());
142 assert!("122z:/12".parse::<Cidr>().is_err());
143
144 assert!("127.0.0.1/33".parse::<Cidr>().is_err());
145 assert!("127.0.0.1/99999999".parse::<Cidr>().is_err());
146
147 assert!("::1/129".parse::<Cidr>().is_err());
148 assert!("1222::1/999999999999".parse::<Cidr>().is_err());
149 }
150}