From 439a0deeb722b48ce8cf3381380a3177a2ccd9c0 Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Wed, 6 Feb 2019 23:12:47 +0000 Subject: [PATCH 1/2] Implement is_subnet_of and is_supernet_of --- src/ipv4.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/ipv6.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 ++++ 3 files changed, 87 insertions(+) diff --git a/src/ipv4.rs b/src/ipv4.rs index ec4fa27..bd8822c 100644 --- a/src/ipv4.rs +++ b/src/ipv4.rs @@ -62,6 +62,16 @@ impl Ipv4Network { self.prefix } + /// Checks if the given `Ipv4Network` is a subnet of the other. + pub fn is_subnet_of(self, other: Ipv4Network) -> bool { + other.ip() <= self.ip() && other.broadcast() >= self.broadcast() + } + + /// Checks if the given `Ipv4Network` is a supernet of the other. + pub fn is_supernet_of(self, other: Ipv4Network) -> bool { + other.is_subnet_of(self) + } + /// Returns the mask for this `Ipv4Network`. /// That means the `prefix` most significant bits will be 1 and the rest 0 /// @@ -454,4 +464,35 @@ mod test { fn assert_sync() {} assert_sync::(); } + + // Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py + #[test] + fn test_is_subnet_of() { + let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new(); + + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false); + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.0.0/24".parse().unwrap()), true); + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false); + test_cases.insert(("10.0.1.0/24".parse().unwrap(), "10.0.0.0/30".parse().unwrap()), false); + + for (key, val) in test_cases.iter() { + let (src, dest) = (key.0, key.1); + assert_eq!(src.is_subnet_of(dest), *val, "testing with {} and {}", src, dest); + } + } + + #[test] + fn test_is_supernet_of() { + let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new(); + + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false); + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.0.0/24".parse().unwrap()), false); + test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false); + test_cases.insert(("10.0.0.0/24".parse().unwrap(), "10.0.0.0/30".parse().unwrap()), true); + + for (key, val) in test_cases.iter() { + let (src, dest) = (key.0, key.1); + assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest); + } + } } diff --git a/src/ipv6.rs b/src/ipv6.rs index 6276790..cca6c40 100644 --- a/src/ipv6.rs +++ b/src/ipv6.rs @@ -111,6 +111,16 @@ impl Ipv6Network { self.prefix } + /// Checks if the given `Ipv6Network` is a subnet of the other. + pub fn is_subnet_of(self, other: Ipv6Network) -> bool { + other.ip() <= self.ip() && other.broadcast() >= self.broadcast() + } + + /// Checks if the given `Ipv6Network` is a supernet of the other. + pub fn is_supernet_of(self, other: Ipv6Network) -> bool { + other.is_subnet_of(self) + } + /// Returns the mask for this `Ipv6Network`. /// That means the `prefix` most significant bits will be 1 and the rest 0 /// @@ -265,6 +275,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { #[cfg(test)] mod test { use super::*; + use std::collections::HashMap; use std::net::Ipv6Addr; #[test] @@ -436,4 +447,35 @@ mod test { fn assert_sync() {} assert_sync::(); } + + // Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py + #[test] + fn test_is_subnet_of() { + let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new(); + + test_cases.insert(("2000:999::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false); + test_cases.insert(("2000:aaa::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), true); + test_cases.insert(("2000:bbb::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false); + test_cases.insert(("2000:aaa::/48".parse().unwrap(), "2000:aaa::/56".parse().unwrap()), false); + + for (key, val) in test_cases.iter() { + let (src, dest) = (key.0, key.1); + assert_eq!(src.is_subnet_of(dest), *val, "testing with {} and {}", src, dest); + } + } + + #[test] + fn test_is_supernet_of() { + let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new(); + + test_cases.insert(("2000:999::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false); + test_cases.insert(("2000:aaa::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false); + test_cases.insert(("2000:bbb::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false); + test_cases.insert(("2000:aaa::/48".parse().unwrap(), "2000:aaa::/56".parse().unwrap()), true); + + for (key, val) in test_cases.iter() { + let (src, dest) = (key.0, key.1); + assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 9dddd51..65fb253 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,6 +200,10 @@ impl IpNetwork { } } + // TODO(abhishek) when TryFrom is stable, implement it for IpNetwork to + // variant conversions. Then use that to implement a generic is_subnet_of + // and is_supernet_of + /// Checks if a given `IpAddr` is in this `IpNetwork` /// /// # Examples From e7c85b5b815479d84818d50253ff016b1cfb5e2f Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Wed, 6 Feb 2019 23:29:46 +0000 Subject: [PATCH 2/2] Implement overlaps for both types --- src/ipv4.rs | 23 +++++++++++++++++++++++ src/ipv6.rs | 19 +++++++++++++++++++ src/lib.rs | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/ipv4.rs b/src/ipv4.rs index bd8822c..88a4549 100644 --- a/src/ipv4.rs +++ b/src/ipv4.rs @@ -72,6 +72,17 @@ impl Ipv4Network { other.is_subnet_of(self) } + /// Checks if the given `Ipv4Network` is partly contained in other. + pub fn overlaps(self, other: Ipv4Network) -> bool { + other.contains(self.ip()) || ( + other.contains(self.broadcast()) || ( + self.contains(other.ip()) || ( + self.contains(other.broadcast()) + ) + ) + ) + } + /// Returns the mask for this `Ipv4Network`. /// That means the `prefix` most significant bits will be 1 and the rest 0 /// @@ -495,4 +506,16 @@ mod test { assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest); } } + + #[test] + fn test_overlaps() { + let other: Ipv4Network = "1.2.3.0/30".parse().unwrap(); + let other2: Ipv4Network = "1.2.2.0/24".parse().unwrap(); + let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap(); + + let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap(); + assert_eq!(skynet.overlaps(other), true); + assert_eq!(skynet.overlaps(other2), false); + assert_eq!(other2.overlaps(other3), true); + } } diff --git a/src/ipv6.rs b/src/ipv6.rs index cca6c40..a25915f 100644 --- a/src/ipv6.rs +++ b/src/ipv6.rs @@ -121,6 +121,17 @@ impl Ipv6Network { other.is_subnet_of(self) } + /// Checks if the given `Ipv6Network` is partly contained in other. + pub fn overlaps(self, other: Ipv6Network) -> bool { + other.contains(self.ip()) || ( + other.contains(self.broadcast()) || ( + self.contains(other.ip()) || ( + self.contains(other.broadcast()) + ) + ) + ) + } + /// Returns the mask for this `Ipv6Network`. /// That means the `prefix` most significant bits will be 1 and the rest 0 /// @@ -478,4 +489,12 @@ mod test { assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest); } } + + #[test] + fn test_overlaps() { + let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap(); + let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap(); + + assert_eq!(other2.overlaps(other), true); + } } diff --git a/src/lib.rs b/src/lib.rs index 65fb253..3bcad78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,7 @@ impl IpNetwork { // TODO(abhishek) when TryFrom is stable, implement it for IpNetwork to // variant conversions. Then use that to implement a generic is_subnet_of - // and is_supernet_of + // is_supernet_of, overlaps /// Checks if a given `IpAddr` is in this `IpNetwork` ///