From 658158b74f4042bf04544b55c79e08fce50a7f71 Mon Sep 17 00:00:00 2001 From: omrzk <193513049+omrzk@users.noreply.github.com> Date: Wed, 28 Oct 2026 09:08:43 -0500 Subject: [PATCH] Support double-ended network iteration --- src/ipv4.rs | 15 ++++++- src/ipv6.rs | 15 ++++++- src/lib.rs | 18 ++++++++ tests/reverse_iterators.rs | 87 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/reverse_iterators.rs diff --git a/src/ipv4.rs b/src/ipv4.rs index 838c6e8..5adf014 100644 --- a/src/ipv4.rs +++ b/src/ipv4.rs @@ -128,7 +128,7 @@ impl Ipv4Network { /// Returns an iterator over `Ipv4Network`. Each call to `next` will return the next /// `Ipv4Addr` in the given network. `None` will be returned when there are no more - /// addresses. + /// addresses. Use `rev()` to iterate in descending order. pub fn iter(self) -> Ipv4NetworkIterator { let mask = ipv4_mask_bits(self.prefix); let start = self.addr.to_bits() & mask; @@ -373,6 +373,19 @@ impl Iterator for Ipv4NetworkIterator { } } +impl DoubleEndedIterator for Ipv4NetworkIterator { + fn next_back(&mut self) -> Option { + let next = self.next?; + let end = self.end; + if next == end { + self.next = None; + } else { + self.end -= 1; + } + Some(end.into()) + } +} + impl IntoIterator for &'_ Ipv4Network { type IntoIter = Ipv4NetworkIterator; type Item = Ipv4Addr; diff --git a/src/ipv6.rs b/src/ipv6.rs index 50081f3..82d8ef1 100644 --- a/src/ipv6.rs +++ b/src/ipv6.rs @@ -137,7 +137,7 @@ impl Ipv6Network { /// Returns an iterator over `Ipv6Network`. Each call to `next` will return the next /// `Ipv6Addr` in the given network. `None` will be returned when there are no more - /// addresses. + /// addresses. Use `rev()` to iterate in descending order. /// /// # Warning /// @@ -373,6 +373,19 @@ impl Iterator for Ipv6NetworkIterator { } } +impl DoubleEndedIterator for Ipv6NetworkIterator { + fn next_back(&mut self) -> Option { + let next = self.next?; + let end = self.end; + if next == end { + self.next = None; + } else { + self.end -= 1; + } + Some(end.into()) + } +} + impl IntoIterator for &'_ Ipv6Network { type IntoIter = Ipv6NetworkIterator; type Item = Ipv6Addr; diff --git a/src/lib.rs b/src/lib.rs index 010124b..80726af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -281,6 +281,15 @@ impl IpNetwork { /// Returns an iterator over the addresses contained in the network. /// /// This lists all the addresses in the network range, in ascending order. + /// Use `rev()` to iterate in descending order: + /// + /// ``` + /// use ipnetwork::IpNetwork; + /// use std::net::IpAddr; + /// + /// let net: IpNetwork = "192.0.2.0/30".parse().unwrap(); + /// assert_eq!(net.iter().rev().next(), Some("192.0.2.3".parse::().unwrap())); + /// ``` pub fn iter(&self) -> IpNetworkIterator { let inner = match self { IpNetwork::V4(ip) => IpNetworkIteratorInner::V4(ip.iter()), @@ -394,6 +403,15 @@ impl Iterator for IpNetworkIterator { } } +impl DoubleEndedIterator for IpNetworkIterator { + fn next_back(&mut self) -> Option { + match &mut self.inner { + IpNetworkIteratorInner::V4(iter) => iter.next_back().map(IpAddr::V4), + IpNetworkIteratorInner::V6(iter) => iter.next_back().map(IpAddr::V6), + } + } +} + impl IntoIterator for &'_ IpNetwork { type IntoIter = IpNetworkIterator; type Item = IpAddr; diff --git a/tests/reverse_iterators.rs b/tests/reverse_iterators.rs new file mode 100644 index 0000000..4ff87b3 --- /dev/null +++ b/tests/reverse_iterators.rs @@ -0,0 +1,87 @@ +use std::collections::VecDeque; +use std::fmt::Debug; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; + +fn check_both_ends(iter: I) +where + I: DoubleEndedIterator + Clone, + I::Item: Eq + Debug, +{ + let expected: Vec<_> = iter.clone().collect(); + assert_eq!( + iter.clone().rev().collect::>(), + expected.into_iter().rev().collect::>() + ); + + // Exercise every front/back consumption pattern for this four-address network. + for pattern in 0..16 { + let mut remaining: VecDeque<_> = iter.clone().collect(); + let mut actual = iter.clone(); + for bit in 0..4 { + assert_eq!(actual.size_hint(), (remaining.len(), Some(remaining.len()))); + let (got, want) = if pattern & (1 << bit) == 0 { + (actual.next(), remaining.pop_front()) + } else { + (actual.next_back(), remaining.pop_back()) + }; + assert_eq!(got, want); + } + assert_eq!(actual.next(), None); + assert_eq!(actual.next_back(), None); + assert_eq!(actual.next(), None); + } +} + +#[test] +fn reverse_and_interleave_ipv4() { + let net: Ipv4Network = "192.0.2.1/30".parse().unwrap(); + check_both_ends(net.iter()); +} + +#[test] +fn reverse_and_interleave_ipv6() { + let net: Ipv6Network = "2001:db8::1/126".parse().unwrap(); + check_both_ends(net.iter()); +} + +#[test] +fn reverse_and_interleave_ip_network() { + for cidr in ["192.0.2.1/30", "2001:db8::1/126"] { + let net: IpNetwork = cidr.parse().unwrap(); + check_both_ends(net.iter()); + } +} + +#[test] +fn singleton_networks_do_not_underflow_or_repeat() { + for addr in [Ipv4Addr::UNSPECIFIED, Ipv4Addr::BROADCAST] { + let mut iter = Ipv4Network::from(addr).iter(); + assert_eq!(iter.next_back(), Some(addr)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next(), None); + } + for addr in [Ipv6Addr::UNSPECIFIED, Ipv6Addr::from(u128::MAX)] { + let mut iter = Ipv6Network::from(addr).iter(); + assert_eq!(iter.next_back(), Some(addr)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next(), None); + } +} + +#[test] +fn reverse_from_highest_address() { + let net: Ipv4Network = "255.255.255.255/31".parse().unwrap(); + let mut iter = net.iter(); + assert_eq!(iter.next_back(), Some(Ipv4Addr::BROADCAST)); + assert_eq!(iter.next(), Some(Ipv4Addr::from(u32::MAX - 1))); + assert_eq!(iter.next_back(), None); + + let net: Ipv6Network = "::/0".parse().unwrap(); + let mut iter = net.iter(); + assert_eq!(iter.next_back(), Some(Ipv6Addr::from(u128::MAX))); + assert_eq!(iter.next(), Some(Ipv6Addr::UNSPECIFIED)); + assert_eq!(iter.next_back(), Some(Ipv6Addr::from(u128::MAX - 1))); + assert_eq!(iter.next(), Some(Ipv6Addr::LOCALHOST)); +}