From 47f52e0aef3f6d5e1ded84255733065e104cfd45 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Fri, 14 Feb 2020 00:17:27 +0000 Subject: [PATCH 01/12] Create `Ipv4NetworkSubSet` helper iterator This is a helper type that is created with an initial IPv4 network, represented as a `u32` representation of its IP address and a `u8` with its prefix, and the prefix of a larger IPv4 network which contains the initial IPv4 network. Since one is contained in the other, the IP address shares most bits, so it only needs to keep track of it once. However, proper care must be taken so that the initial network prefix is larger than the larger network prefix. The iterator will produce `Ipv4Network` items representing increasing network ranges that are still inside the larger network but do not include the initial network. --- src/lib.rs | 1 + src/sub.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/sub.rs diff --git a/src/lib.rs b/src/lib.rs index effbef9..6b4bbc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use std::{fmt, net::IpAddr, str::FromStr}; mod common; mod ipv4; mod ipv6; +mod sub; pub use crate::common::IpNetworkError; pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network}; diff --git a/src/sub.rs b/src/sub.rs new file mode 100644 index 0000000..9597e06 --- /dev/null +++ b/src/sub.rs @@ -0,0 +1,27 @@ +use crate::Ipv4Network; + +#[derive(Clone, Copy, Debug)] +pub struct Ipv4NetworkSubSet { + network: u32, + bit_position: u8, + max_bit_position: u8, +} + +impl Iterator for Ipv4NetworkSubSet { + type Item = Ipv4Network; + + fn next(&mut self) -> Option { + if self.bit_position < self.max_bit_position { + let bit_mask = 1 << self.bit_position; + let prefix_mask = !(bit_mask - 1); + let address = (self.network ^ bit_mask) & prefix_mask; + let prefix = 32 - self.bit_position; + + self.bit_position += 1; + + Some(Ipv4Network::new(address.into(), prefix).expect("Invalid IPv4 network prefix")) + } else { + None + } + } +} From 3bd9cb9db800e5ee5796e4e86a3bc12bc05e4a0b Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 14:20:05 +0000 Subject: [PATCH 02/12] Create `Ipv4NetworkSubResult` helper type This is also an iterator, and it wraps around the `Ipv4NetworkSubSet` type. However, it also includes entries for when the minuend and the subtrahend don't overlap and for when the subtrahend completly ovelaps the minuend. In the former case, there is no overlap of the networks. Therefore, nothing is subtracted from the original network (the minuend), so the result is just the original network. In the latter case, since the subtrahend completly overlaps the minuend, it is in effect completely removing the original network. Therefore, the result is no networks at all, and the resulting iterator is empty. --- src/sub.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index 9597e06..bf46e8d 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -1,5 +1,34 @@ use crate::Ipv4Network; +#[derive(Clone, Copy, Debug)] +pub enum Ipv4NetworkSubResult { + Empty, + SingleNetwork(Ipv4Network), + MultipleNetworks(Ipv4NetworkSubSet), +} + +impl Iterator for Ipv4NetworkSubResult { + type Item = Ipv4Network; + + fn next(&mut self) -> Option { + match self { + Ipv4NetworkSubResult::Empty => None, + &mut Ipv4NetworkSubResult::SingleNetwork(network) => { + *self = Ipv4NetworkSubResult::Empty; + Some(network) + } + Ipv4NetworkSubResult::MultipleNetworks(range) => { + if let Some(item) = range.next() { + Some(item) + } else { + *self = Ipv4NetworkSubResult::Empty; + None + } + } + } + } +} + #[derive(Clone, Copy, Debug)] pub struct Ipv4NetworkSubSet { network: u32, From bf081f68e8add31db459be8a902e27190a248fc9 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 14:23:39 +0000 Subject: [PATCH 03/12] Implement `Sub` for `Ipv4Network` The resulting set is returned through a `Ipv4NetworkSubResult` helper iterator. --- src/sub.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index bf46e8d..2c3e894 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -1,4 +1,34 @@ use crate::Ipv4Network; +use std::ops::Sub; + +impl Sub for Ipv4Network { + type Output = Ipv4NetworkSubResult; + + fn sub(self, other: Self) -> Self::Output { + let subtrahend: u32 = self.network().into(); + let minuend: u32 = other.network().into(); + let mask: u32 = self.mask().into(); + + if minuend & mask == subtrahend { + let max_bit_position = 32 - self.prefix(); + let bit_position = 32 - other.prefix(); + + Ipv4NetworkSubResult::MultipleNetworks(Ipv4NetworkSubSet { + network: minuend, + bit_position, + max_bit_position, + }) + } else { + let other_mask: u32 = other.mask().into(); + + if subtrahend & other_mask == minuend { + Ipv4NetworkSubResult::Empty + } else { + Ipv4NetworkSubResult::SingleNetwork(self) + } + } + } +} #[derive(Clone, Copy, Debug)] pub enum Ipv4NetworkSubResult { From d98ff92d6403042072145a424fb4fda4ae1e43d5 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:10:32 +0000 Subject: [PATCH 04/12] Implement subtraction of multiple `Ipv4Network`s This allows one to subtract multiple `Ipv4Network`s from a single `Ipv4Network`. This works by having a subtraction chain, where the original minuend `Ipv4Network` is subtracted by the first `Ipv4Network` subtrahend, and the result is then subtracted by the second `Ipv4Network` subtrahend, and so on. The implementation uses dynamic dispatch because it is impossible to know how many subtrahends there will be. This is a simple initial implementation, and tere might be other solutions, such as creating a custom iterator and looping around all subtrahends to provide the results, but it quickly becomes more complicated. This might be doable as future work. Another possible future work is to remove the dynamic dispatch for known sizes of subtrahend arrays, but this would probably require const generics to be stabilized. --- src/sub.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/sub.rs b/src/sub.rs index 2c3e894..389951f 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -1,5 +1,5 @@ use crate::Ipv4Network; -use std::ops::Sub; +use std::{iter, ops::Sub}; impl Sub for Ipv4Network { type Output = Ipv4NetworkSubResult; @@ -84,3 +84,20 @@ impl Iterator for Ipv4NetworkSubSet { } } } + +impl Sub for Ipv4Network +where + T: IntoIterator, +{ + type Output = Box>; + + fn sub(self, minuends: T) -> Self::Output { + let mut result: Box> = Box::new(iter::once(self)); + + for minuend in minuends { + result = Box::new(result.flat_map(move |partial_result| partial_result - minuend)); + } + + result + } +} From 9340cc5a09d40bc5364c1f9c3b09c42699bc18c3 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:12:50 +0000 Subject: [PATCH 05/12] Create `Ipv6NetworkSubSet` helper iterator This is the IPv6 equivalent of the `Ipv4NetworkSubSet`. It is a helper type that is created with an initial IPv6 network, represented as a `u128` representation of its IP address and a `u8` with its prefix, and the prefix of a larger IPv6 network which contains the initial IPv6 network. Since one is contained in the other, the IP address shares most bits, so it only needs to keep track of it once. However, proper care must be taken so that the initial network prefix is larger than the larger network prefix. The iterator will produce `Ipv6Network` items representing increasing network ranges that are still inside the larger network but do not include the initial network. --- src/sub.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/sub.rs b/src/sub.rs index 389951f..d5d30ab 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -1,4 +1,4 @@ -use crate::Ipv4Network; +use crate::{Ipv4Network, Ipv6Network}; use std::{iter, ops::Sub}; impl Sub for Ipv4Network { @@ -85,6 +85,32 @@ impl Iterator for Ipv4NetworkSubSet { } } +#[derive(Clone, Copy, Debug)] +pub struct Ipv6NetworkSubSet { + network: u128, + bit_position: u8, + max_bit_position: u8, +} + +impl Iterator for Ipv6NetworkSubSet { + type Item = Ipv6Network; + + fn next(&mut self) -> Option { + if self.bit_position < self.max_bit_position { + let bit_mask = 1 << self.bit_position; + let prefix_mask = !(bit_mask - 1); + let address = (self.network ^ bit_mask) & prefix_mask; + let prefix = 128 - self.bit_position; + + self.bit_position += 1; + + Some(Ipv6Network::new(address.into(), prefix).expect("Invalid IPv6 network prefix")) + } else { + None + } + } +} + impl Sub for Ipv4Network where T: IntoIterator, From 3f1955f2f4e6ef387cc052a8349319a39b3b7ad3 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:18:23 +0000 Subject: [PATCH 06/12] Create `Ipv6NetworkSubResult` helper type This is the IPv6 equivalent to the `Ipv4NetworkSubResult` type. This is also an iterator, and it wraps around the `Ipv6NetworkSubSet` type. However, it also includes entries for when the minuend and the subtrahend don't overlap and for when the subtrahend completly ovelaps the minuend. In the former case, there is no overlap of the networks. Therefore, nothing is subtracted from the original network (the minuend), so the result is just the original network. In the latter case, since the subtrahend completly overlaps the minuend, it is in effect completely removing the original network. Therefore, the result is no networks at all, and the resulting iterator is empty. --- src/sub.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index d5d30ab..e24a11a 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -59,6 +59,35 @@ impl Iterator for Ipv4NetworkSubResult { } } +#[derive(Clone, Copy, Debug)] +pub enum Ipv6NetworkSubResult { + Empty, + SingleNetwork(Ipv6Network), + MultipleNetworks(Ipv6NetworkSubSet), +} + +impl Iterator for Ipv6NetworkSubResult { + type Item = Ipv6Network; + + fn next(&mut self) -> Option { + match self { + Ipv6NetworkSubResult::Empty => None, + &mut Ipv6NetworkSubResult::SingleNetwork(network) => { + *self = Ipv6NetworkSubResult::Empty; + Some(network) + } + Ipv6NetworkSubResult::MultipleNetworks(range) => { + if let Some(item) = range.next() { + Some(item) + } else { + *self = Ipv6NetworkSubResult::Empty; + None + } + } + } + } +} + #[derive(Clone, Copy, Debug)] pub struct Ipv4NetworkSubSet { network: u32, From 01b3bfb956554f300a7929a25a22c522e0f8196e Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:44:42 +0000 Subject: [PATCH 07/12] Implement `Sub` for `Ipv6Network` The resulting set is returned through a `Ipv6NetworkSubResult` helper iterator. --- src/sub.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index e24a11a..526c085 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -30,6 +30,35 @@ impl Sub for Ipv4Network { } } +impl Sub for Ipv6Network { + type Output = Ipv6NetworkSubResult; + + fn sub(self, other: Self) -> Self::Output { + let subtrahend: u128 = self.network().into(); + let minuend: u128 = other.network().into(); + let mask: u128 = self.mask().into(); + + if minuend & mask == subtrahend { + let max_bit_position = 32 - self.prefix(); + let bit_position = 32 - other.prefix(); + + Ipv6NetworkSubResult::MultipleNetworks(Ipv6NetworkSubSet { + network: minuend, + bit_position, + max_bit_position, + }) + } else { + let other_mask: u128 = other.mask().into(); + + if subtrahend & other_mask == minuend { + Ipv6NetworkSubResult::Empty + } else { + Ipv6NetworkSubResult::SingleNetwork(self) + } + } + } +} + #[derive(Clone, Copy, Debug)] pub enum Ipv4NetworkSubResult { Empty, From c0e40836474569e9cebdfffcfb1ad554c0a61150 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:46:32 +0000 Subject: [PATCH 08/12] Implement subtraction of multiple `Ipv6Network`s This allows one to subtract multiple `Ipv6Network`s from a single `Ipv6Network`. This works by having a subtraction chain, where the original minuend `Ipv6Network` is subtracted by the first `Ipv6Network` subtrahend, and the result is then subtracted by the second `Ipv6Network` subtrahend, and so on. The implementation uses dynamic dispatch because it is impossible to know how many subtrahends there will be. This is a simple initial implementation, and tere might be other solutions, such as creating a custom iterator and looping around all subtrahends to provide the results, but it quickly becomes more complicated. This might be doable as future work. Another possible future work is to remove the dynamic dispatch for known sizes of subtrahend arrays, but this would probably require const generics to be stabilized. --- src/sub.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index 526c085..682d680 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -185,3 +185,20 @@ where result } } + +impl Sub for Ipv6Network +where + T: IntoIterator, +{ + type Output = Box>; + + fn sub(self, minuends: T) -> Self::Output { + let mut result: Box> = Box::new(iter::once(self)); + + for minuend in minuends { + result = Box::new(result.flat_map(move |partial_result| partial_result - minuend)); + } + + result + } +} From da67e84d283bb42e19c77618956398526de1f3f6 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:49:20 +0000 Subject: [PATCH 09/12] Create `IpNetworkSubResult` wrapper type This type represents the result of the subtraction of one `IpNetwork` from another. The implementation just wraps the resulting subtraction of the protocol specific (IPv4 or IPv6) subtraction result. Note that this means that it is not currently possible to subtract `IpNetwork`s of different protocols (i.e., either an IPv4 network from an IPv6 network or vice-versa). --- src/sub.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/sub.rs b/src/sub.rs index 682d680..fcafe37 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -1,4 +1,4 @@ -use crate::{Ipv4Network, Ipv6Network}; +use crate::{IpNetwork, Ipv4Network, Ipv6Network}; use std::{iter, ops::Sub}; impl Sub for Ipv4Network { @@ -117,6 +117,23 @@ impl Iterator for Ipv6NetworkSubResult { } } +#[derive(Clone, Copy, Debug)] +pub enum IpNetworkSubResult { + V4(Ipv4NetworkSubResult), + V6(Ipv6NetworkSubResult), +} + +impl Iterator for IpNetworkSubResult { + type Item = IpNetwork; + + fn next(&mut self) -> Option { + match self { + IpNetworkSubResult::V4(result) => result.next().map(IpNetwork::from), + IpNetworkSubResult::V6(result) => result.next().map(IpNetwork::from), + } + } +} + #[derive(Clone, Copy, Debug)] pub struct Ipv4NetworkSubSet { network: u32, From 686c0607410d7d703bc30f3683118f4b36a5379e Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:52:48 +0000 Subject: [PATCH 10/12] Implement `Sub` for `IpNetwork` The resulting set is returned through a `IpNetworkSubResult` helper iterator. Note that it is not currently possible to subtract `IpNetwork`s of different protocols (i.e., either an IPv4 network from an IPv6 network or vice-versa). --- src/sub.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index fcafe37..10837c9 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -59,6 +59,27 @@ impl Sub for Ipv6Network { } } +impl Sub for IpNetwork { + type Output = IpNetworkSubResult; + + fn sub(self, other: Self) -> Self::Output { + match (self, other) { + (IpNetwork::V4(subtrahend), IpNetwork::V4(minuend)) => { + IpNetworkSubResult::V4(subtrahend - minuend) + } + (IpNetwork::V6(subtrahend), IpNetwork::V6(minuend)) => { + IpNetworkSubResult::V6(subtrahend - minuend) + } + (IpNetwork::V4(_), IpNetwork::V6(_)) => { + panic!("Can't subtract IPv6 network from IPv4 network") + } + (IpNetwork::V6(_), IpNetwork::V4(_)) => { + panic!("Can't subtract IPv4 network from IPv6 network") + } + } + } +} + #[derive(Clone, Copy, Debug)] pub enum Ipv4NetworkSubResult { Empty, From 1473c5d10bc881ea7c19bfc6e5e1336ce93fb2c8 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:56:31 +0000 Subject: [PATCH 11/12] Implement subtraction of multiple `IpNetwork`s This allows one to subtract multiple `IpNetwork`s from a single `IpNetwork`. This works by having a subtraction chain, where the original minuend `IpNetwork` is subtracted by the first `IpNetwork` subtrahend, and the result is then subtracted by the second `IpNetwork` subtrahend, and so on. Since it isn't currently possible to subtract `IpNetworks` of different protocols, all operands must be of the same protocol (either IPv4 or IPv6). Having one operand of a different protocol will cause the code to panic. The implementation uses dynamic dispatch because it is impossible to know how many subtrahends there will be. This is a simple initial implementation, and tere might be other solutions, such as creating a custom iterator and looping around all subtrahends to provide the results, but it quickly becomes more complicated. This might be doable as future work. Another possible future work is to remove the dynamic dispatch for known sizes of subtrahend arrays, but this would probably require const generics to be stabilized. --- src/sub.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index 10837c9..c48d2ef 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -240,3 +240,20 @@ where result } } + +impl Sub for IpNetwork +where + T: IntoIterator, +{ + type Output = Box>; + + fn sub(self, minuends: T) -> Self::Output { + let mut result: Box> = Box::new(iter::once(self)); + + for minuend in minuends { + result = Box::new(result.flat_map(move |partial_result| partial_result - minuend)); + } + + result + } +} From 9031d4da02f40bc93a517c4577bc035ed9d59fb6 Mon Sep 17 00:00:00 2001 From: Janito Vaqueiro Ferreira Filho Date: Sat, 15 Feb 2020 15:56:55 +0000 Subject: [PATCH 12/12] Implement tests for IPv4 network subtraction --- src/sub.rs | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/src/sub.rs b/src/sub.rs index c48d2ef..fe03598 100644 --- a/src/sub.rs +++ b/src/sub.rs @@ -257,3 +257,229 @@ where result } } + +#[cfg(test)] +mod test { + use super::*; + use std::{ + collections::HashSet, + net::{IpAddr, Ipv4Addr}, + }; + + #[test] + fn subtract_out_of_range() { + let minuend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(25, 0, 0, 0)), 8).unwrap(); + let subtrahend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(125, 92, 4, 0)), 24).unwrap(); + + let difference: Vec<_> = minuend.sub(subtrahend).collect(); + + let expected = vec![minuend]; + + assert_eq!(difference, expected); + } + + #[test] + fn subtract_whole_range() { + let minuend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(25, 0, 0, 0)), 8).unwrap(); + let subtrahend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(16, 0, 0, 0)), 4).unwrap(); + + let difference: Vec<_> = minuend.sub(subtrahend).collect(); + + assert!(difference.is_empty()); + } + + #[test] + fn subtract_inner_range() { + let minuend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8).unwrap(); + let subtrahend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 10, 10, 0)), 24).unwrap(); + + let difference: HashSet<_> = minuend.sub(subtrahend).collect(); + + let expected = vec![ + ([10, 0, 0, 0], 13), + ([10, 8, 0, 0], 15), + ([10, 10, 0, 0], 21), + ([10, 10, 8, 0], 23), + ([10, 10, 11, 0], 24), + ([10, 10, 12, 0], 22), + ([10, 10, 16, 0], 20), + ([10, 10, 32, 0], 19), + ([10, 10, 64, 0], 18), + ([10, 10, 128, 0], 17), + ([10, 11, 0, 0], 16), + ([10, 12, 0, 0], 14), + ([10, 16, 0, 0], 12), + ([10, 32, 0, 0], 11), + ([10, 64, 0, 0], 10), + ([10, 128, 0, 0], 9), + ]; + + let expected: HashSet<_> = expected + .into_iter() + .map(|(octets, prefix)| IpNetwork::new(IpAddr::V4(octets.into()), prefix).unwrap()) + .collect(); + + assert_eq!(difference, expected); + } + + #[test] + fn subtract_single_address() { + let minuend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 64, 0, 0)), 10).unwrap(); + let subtrahend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 64, 0, 0)), 32).unwrap(); + + let difference: HashSet<_> = minuend.sub(subtrahend).collect(); + + let expected = vec![ + ([10, 64, 0, 1], 32), + ([10, 64, 0, 2], 31), + ([10, 64, 0, 4], 30), + ([10, 64, 0, 8], 29), + ([10, 64, 0, 16], 28), + ([10, 64, 0, 32], 27), + ([10, 64, 0, 64], 26), + ([10, 64, 0, 128], 25), + ([10, 64, 1, 0], 24), + ([10, 64, 2, 0], 23), + ([10, 64, 4, 0], 22), + ([10, 64, 8, 0], 21), + ([10, 64, 16, 0], 20), + ([10, 64, 32, 0], 19), + ([10, 64, 64, 0], 18), + ([10, 64, 128, 0], 17), + ([10, 65, 0, 0], 16), + ([10, 66, 0, 0], 15), + ([10, 68, 0, 0], 14), + ([10, 72, 0, 0], 13), + ([10, 80, 0, 0], 12), + ([10, 96, 0, 0], 11), + ]; + + let expected: HashSet<_> = expected + .into_iter() + .map(|(octets, prefix)| IpNetwork::new(IpAddr::V4(octets.into()), prefix).unwrap()) + .collect(); + + assert_eq!(difference, expected); + } + + #[test] + fn subtract_multiple() { + let minuend = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0).unwrap(); + let subtrahend_1 = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8).unwrap(); + let subtrahend_2 = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(172, 16, 0, 0)), 12).unwrap(); + let subtrahend_3 = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 16).unwrap(); + let subtrahend_4 = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(169, 254, 0, 0)), 16).unwrap(); + let subtrahend_5 = IpNetwork::new(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 0)), 24).unwrap(); + let subtrahend_6 = + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)), 32).unwrap(); + let subtrahend_7 = + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 251)), 32).unwrap(); + + let difference: HashSet<_> = (minuend + - vec![ + subtrahend_1, + subtrahend_2, + subtrahend_3, + subtrahend_4, + subtrahend_5, + subtrahend_6, + subtrahend_7, + ]) + .collect(); + + let expected = vec![ + ([0, 0, 0, 0], 5), + ([8, 0, 0, 0], 7), + ([11, 0, 0, 0], 8), + ([12, 0, 0, 0], 6), + ([16, 0, 0, 0], 4), + ([32, 0, 0, 0], 3), + ([64, 0, 0, 0], 2), + ([128, 0, 0, 0], 3), + ([160, 0, 0, 0], 5), + ([168, 0, 0, 0], 8), + ([169, 0, 0, 0], 9), + ([169, 128, 0, 0], 10), + ([169, 192, 0, 0], 11), + ([169, 224, 0, 0], 12), + ([169, 240, 0, 0], 13), + ([169, 248, 0, 0], 14), + ([169, 252, 0, 0], 15), + ([169, 255, 0, 0], 16), + ([170, 0, 0, 0], 7), + ([172, 0, 0, 0], 12), + ([172, 32, 0, 0], 11), + ([172, 64, 0, 0], 10), + ([172, 128, 0, 0], 9), + ([173, 0, 0, 0], 8), + ([174, 0, 0, 0], 7), + ([176, 0, 0, 0], 4), + ([192, 0, 0, 0], 9), + ([192, 128, 0, 0], 11), + ([192, 160, 0, 0], 13), + ([192, 169, 0, 0], 16), + ([192, 170, 0, 0], 15), + ([192, 172, 0, 0], 14), + ([192, 176, 0, 0], 12), + ([192, 192, 0, 0], 10), + ([193, 0, 0, 0], 8), + ([194, 0, 0, 0], 7), + ([196, 0, 0, 0], 6), + ([200, 0, 0, 0], 5), + ([208, 0, 0, 0], 4), + ([224, 0, 1, 0], 24), + ([224, 0, 2, 0], 23), + ([224, 0, 4, 0], 22), + ([224, 0, 8, 0], 21), + ([224, 0, 16, 0], 20), + ([224, 0, 32, 0], 19), + ([224, 0, 64, 0], 18), + ([224, 0, 128, 0], 17), + ([224, 1, 0, 0], 16), + ([224, 2, 0, 0], 15), + ([224, 4, 0, 0], 14), + ([224, 8, 0, 0], 13), + ([224, 16, 0, 0], 12), + ([224, 32, 0, 0], 11), + ([224, 64, 0, 0], 10), + ([224, 128, 0, 0], 9), + ([225, 0, 0, 0], 8), + ([226, 0, 0, 0], 7), + ([228, 0, 0, 0], 6), + ([232, 0, 0, 0], 6), + ([236, 0, 0, 0], 7), + ([238, 0, 0, 0], 8), + ([239, 0, 0, 0], 9), + ([239, 128, 0, 0], 10), + ([239, 192, 0, 0], 11), + ([239, 224, 0, 0], 12), + ([239, 240, 0, 0], 13), + ([239, 248, 0, 0], 14), + ([239, 252, 0, 0], 15), + ([239, 254, 0, 0], 16), + ([239, 255, 0, 0], 17), + ([239, 255, 128, 0], 18), + ([239, 255, 192, 0], 19), + ([239, 255, 224, 0], 20), + ([239, 255, 240, 0], 21), + ([239, 255, 248, 0], 22), + ([239, 255, 252, 0], 23), + ([239, 255, 254, 0], 24), + ([239, 255, 255, 0], 25), + ([239, 255, 255, 128], 26), + ([239, 255, 255, 192], 27), + ([239, 255, 255, 224], 28), + ([239, 255, 255, 240], 29), + ([239, 255, 255, 248], 31), + ([239, 255, 255, 252], 30), + ([240, 0, 0, 0], 4), + ]; + + let expected: HashSet<_> = expected + .into_iter() + .map(|(octets, prefix)| IpNetwork::new(IpAddr::V4(octets.into()), prefix).unwrap()) + .collect(); + + assert_eq!(difference, expected); + } +}