From 31431df1b665e1de283786c175357b8e23045a0b Mon Sep 17 00:00:00 2001 From: Koen van Hove Date: Fri, 29 Nov 2024 14:58:34 +0100 Subject: [PATCH] Add function to turn AddressRange into set of prefixes (#306) This PR makes the `AddressRange` type public and adds methods to convert a range into a set of prefixes. --- src/repository/resources/ipres.rs | 105 +++++++++++++++++++++++++++++- src/repository/resources/mod.rs | 6 +- 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/repository/resources/ipres.rs b/src/repository/resources/ipres.rs index ef73cad..5821251 100644 --- a/src/repository/resources/ipres.rs +++ b/src/repository/resources/ipres.rs @@ -12,6 +12,7 @@ use std::fmt::Display; use std::net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr}; use std::num::ParseIntError; use std::str::FromStr; +use std::cmp; use bcder::{decode, encode}; use bcder::{BitString, Mode, OctetString, Tag}; use bcder::decode::{ContentError, DecodeError}; @@ -1037,6 +1038,84 @@ impl AddressRange { } } + /// Convert a range into a set of V6 prefixes + pub fn to_v6_prefixes(self) -> impl Iterator { + let mut start = self.min.to_bits(); + let end = self.max.to_bits(); + + std::iter::from_fn(move || { + // The idea is to take the largest prefix possible from the start + // then move the start to the address after the last address in + // that prefix, and do it again until there are no addresses left. + + // Based loosely on + if start > end { + return None; + } + + // Determine how many of the last bits of the address are prefixable + // e.g. for 2001:DB8:: that would be 99 + let addr_host_bits = start.trailing_zeros(); + + // Determine how many of the first bits are shared between the + // start and the end, to determine an upper bound for the prefix + // e.g. 2001:DB8:: and 2001:DB8::8000 share 112 bits, so the max + // is 16 + let mut max_allowed = 128 - (start ^ end).leading_zeros(); + if end.trailing_ones() < max_allowed { + // Prevent overshooting the prefix + // e.g. for 2001:DB8::8000 the trailing_ones = 0, so the max + // is now 15 to prevent covering space after 2001:DB8::8000 + max_allowed -= 1; + } + + // Obtain the bits at the end that are the same, which is the + // shortest of either the amount of 0 bits at the current address + // or the amount of bits not shared at the start + let same_bits = cmp::min(addr_host_bits, max_allowed); + let prefix_len = 128 - same_bits; + + debug_assert!(prefix_len <= 128); + let prefix = Prefix::new(Addr::from_bits(start), prefix_len as u8); + + start += 2_u128.pow(same_bits); + + Some(prefix) + }) + } + + /// Convert a range into a set of V4 prefixes + pub fn to_v4_prefixes(self) -> impl Iterator { + let mut start = (self.min.to_bits() >> 96) as u32; + let end = (self.max.to_bits() >> 96) as u32; + + std::iter::from_fn(move || { + // This works the same as `to_v6_prefixes` above + if start > end { + return None; + } + + let addr_host_bits = start.trailing_zeros(); + let mut max_allowed = 32 - (start ^ end).leading_zeros(); + if end.trailing_ones() < max_allowed { + max_allowed -= 1; + } + + let same_bits = cmp::min(addr_host_bits, max_allowed); + let prefix_len = 32 - same_bits; + + debug_assert!(prefix_len <= 32); + let prefix = Prefix::new( + Addr::from(Ipv4Addr::from(start)), + prefix_len as u8 + ); + + start += 2u32.pow(same_bits); + + Some(prefix) + }) + } + /// Formats the range as an IPv4 range. pub fn fmt_v4(self, f: &mut fmt::Formatter) -> fmt::Result { let min = self.min.to_v4(); @@ -2207,7 +2286,7 @@ impl From for VerificationError { //============ Tests ========================================================= #[cfg(test)] -mod test { +mod tests { use bcder::encode::Values; use super::*; @@ -2658,6 +2737,30 @@ mod test { 0x123f_ffff_ffff_ffff_ffff_ffff_ffff_ffff ); } + + #[test] + fn to_prefixes() { + { + let range = AddressRange::new( + Addr::from(Ipv6Addr::from_str("::1").unwrap()), + Addr::from(Ipv6Addr::from_str("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe").unwrap()) + ); + + let prefixes = range.to_v6_prefixes(); + + assert_eq!(254, prefixes.count()); + } + { + let range = AddressRange::new( + Addr::from(Ipv4Addr::from_str("192.168.0.0").unwrap()), + Addr::from(Ipv4Addr::from_str("192.168.2.255").unwrap()) + ); + + let prefixes = range.to_v4_prefixes(); + + assert_eq!(2, prefixes.count()); + } + } } #[cfg(all(test, feature="compat"))] diff --git a/src/repository/resources/mod.rs b/src/repository/resources/mod.rs index 62a7d9b..5d5f909 100644 --- a/src/repository/resources/mod.rs +++ b/src/repository/resources/mod.rs @@ -18,9 +18,9 @@ pub use self::asres::{ }; pub use self::choice::ResourcesChoice; pub use self::ipres::{ - Addr, AddressFamily, InheritedIpResources, IpBlock, IpBlocks, Ipv4Block, - Ipv4Blocks, Ipv6Block, Ipv6Blocks, IpBlocksBuilder, IpBlocksForFamily, - IpResources, IpResourcesBuilder, OverclaimedIpResources, + Addr, AddressFamily, AddressRange, InheritedIpResources, IpBlock, IpBlocks, + Ipv4Block, Ipv4Blocks, Ipv6Block, Ipv6Blocks, IpBlocksBuilder, + IpBlocksForFamily, IpResources, IpResourcesBuilder, OverclaimedIpResources, OverclaimedIpv4Resources, OverclaimedIpv6Resources, Prefix }; pub use self::set::{