cidr/parsers/
ipv4_short.rs

1use core::str::FromStr;
2use std::net::{
3	IpAddr,
4	Ipv4Addr,
5};
6
7use crate::{
8	errors::NetworkParseError,
9	AnyIpCidr,
10	IpCidr,
11	Ipv4Cidr,
12};
13
14// parse normal IPv4 addresses as host cidr, and short forms
15// with a cidr that indicates how many octets were present.
16fn _parse_short_ipv4_address_as_cidr(s: &str) -> Option<Ipv4Cidr> {
17	let mut octets = [0u8; 4];
18	let mut last = 0;
19	for (ndx, os) in s.split('.').enumerate() {
20		if ndx >= 4 {
21			// too many octets
22			return None;
23		}
24		// abort on invalid octet
25		octets[ndx] = os.parse().ok()?;
26		last = ndx;
27	}
28	let bits = (last as u8 + 1) * 8;
29	Some(Ipv4Cidr::new(Ipv4Addr::from(octets), bits).expect("host bits are zero"))
30}
31
32/// Parse "short" IPv4 addresses as networks with octet-aligned length
33///
34/// * parse `"10"` as `10.0.0.0/8`
35/// * parse `"192.168"` as `192.168.0.0/16`
36/// * parse `"192.0.2"` as `192.0.2.0/24`
37/// * parse `"127"` as `127.0.0.0/8`
38/// * parse `"127.0.0.1"` as `127.0.0.1/32`
39///
40/// The returned prefix length indicates how many octets were present.
41///
42/// This is very different from [`inet_addr`][`super::inet_addr`] which would
43/// interpret `"192.168"` as `192.0.0.168`!
44///
45/// This function doesn't accept normal CIDR notations, so you will probably
46/// need to combine it with other functions.
47pub fn parse_short_ipv4_address_as_cidr(s: &str) -> Result<Ipv4Cidr, NetworkParseError> {
48	match _parse_short_ipv4_address_as_cidr(s) {
49		Some(n) => Ok(n),
50		None => {
51			// address parser should fail here (to generate proper error result)
52			// (but if it works the result should actually be correct)
53			Ok(Ipv4Cidr::new_host(s.parse()?))
54		},
55	}
56}
57
58/// Parses normal IPv4 CIDR notations or short forms via [`parse_short_ipv4_address_as_cidr`]
59pub fn parse_short_ipv4_cidr(s: &str) -> Result<Ipv4Cidr, NetworkParseError> {
60	super::parse_cidr_full(s, FromStr::from_str, parse_short_ipv4_address_as_cidr)
61}
62
63/// Parses normal IP addresses as host addresses, and short IPv4 addresses via [`parse_short_ipv4_address_as_cidr`]
64///
65/// This function doesn't accept normal CIDR notations, so you will probably
66/// need to combine it with other functions.
67pub fn parse_short_ip_address_as_cidr(s: &str) -> Result<IpCidr, NetworkParseError> {
68	match s.parse::<IpAddr>() {
69		Ok(a) => Ok(IpCidr::new_host(a)),
70		// only try short IPv4 as fallback
71		Err(e) => match _parse_short_ipv4_address_as_cidr(s) {
72			Some(n) => Ok(n.into()),
73			None => Err(e.into()),
74		},
75	}
76}
77
78/// Parses normal IP CIDR notations or short IPv4 forms via [`parse_short_ipv4_address_as_cidr`]
79pub fn parse_short_ip_cidr(s: &str) -> Result<IpCidr, NetworkParseError> {
80	super::parse_cidr_full(s, FromStr::from_str, parse_short_ip_address_as_cidr)
81}
82
83/// Parses normal IP CIDR notations, `"any"` or short IPv4 forms via [`parse_short_ipv4_address_as_cidr`]
84pub fn parse_short_any_ip_cidr(s: &str) -> Result<AnyIpCidr, NetworkParseError> {
85	super::parse_any_cidr_full(s, FromStr::from_str, parse_short_ip_address_as_cidr)
86}
87
88#[cfg(test)]
89mod tests {
90	use super::{
91		parse_short_ip_cidr,
92		parse_short_ipv4_cidr,
93	};
94	use crate::{
95		IpCidr,
96		Ipv4Cidr,
97	};
98
99	fn test(s: &str, expect: &str) {
100		// check against standard parser of canonical form
101		let expect_v4 = expect.parse::<Ipv4Cidr>().unwrap();
102		let expect = expect.parse::<IpCidr>().unwrap();
103
104		assert_eq!(parse_short_ipv4_cidr(s).unwrap(), expect_v4);
105
106		assert_eq!(parse_short_ip_cidr(s).unwrap(), expect);
107	}
108
109	#[test]
110	fn invalid_short() {
111		assert!(parse_short_ipv4_cidr("").is_err());
112		assert!(parse_short_ip_cidr("").is_err());
113	}
114
115	#[test]
116	fn short_10() {
117		test("10.0.0.0/8", "10.0.0.0/8");
118		test("10", "10.0.0.0/8");
119		test("10.42.0.0/16", "10.42.0.0/16");
120		test("10.42", "10.42.0.0/16");
121		test("10.0.42.0/24", "10.0.42.0/24");
122		test("10.0.42", "10.0.42.0/24");
123		test("10.0.0.42/32", "10.0.0.42/32");
124		test("10.0.0.42", "10.0.0.42/32");
125	}
126
127	#[test]
128	fn short_192_168() {
129		test("192.168.0.0/16", "192.168.0.0/16");
130		test("192.168", "192.168.0.0/16");
131		test("192.168.42.0/24", "192.168.42.0/24");
132		test("192.168.42", "192.168.42.0/24");
133		test("192.168.0.42/32", "192.168.0.42/32");
134		test("192.168.0.42", "192.168.0.42/32");
135	}
136
137	#[test]
138	fn short_192_0_2() {
139		test("192.0.2.0/24", "192.0.2.0/24");
140		test("192.0.2", "192.0.2.0/24");
141		test("192.0.2.42/32", "192.0.2.42/32");
142		test("192.0.2.42", "192.0.2.42/32");
143	}
144}