Skip to content

Commit

Permalink
Add function to turn AddressRange into set of prefixes (#306)
Browse files Browse the repository at this point in the history
This PR makes the `AddressRange` type public and adds methods to convert a
range into a set of prefixes.
  • Loading branch information
Koenvh1 authored Nov 29, 2024
1 parent eff4cf2 commit 31431df
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 4 deletions.
105 changes: 104 additions & 1 deletion src/repository/resources/ipres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -1037,6 +1038,84 @@ impl AddressRange {
}
}

/// Convert a range into a set of V6 prefixes
pub fn to_v6_prefixes(self) -> impl Iterator<Item = Prefix> {
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 <https://github.com/arineng/cidr-calc>
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<Item = Prefix> {
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();
Expand Down Expand Up @@ -2207,7 +2286,7 @@ impl From<OverclaimedIpv6Resources> for VerificationError {
//============ Tests =========================================================

#[cfg(test)]
mod test {
mod tests {
use bcder::encode::Values;
use super::*;

Expand Down Expand Up @@ -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"))]
Expand Down
6 changes: 3 additions & 3 deletions src/repository/resources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down

0 comments on commit 31431df

Please sign in to comment.