Add ip_mask_to_prefix_checked (#214)

* Add `ipv4_mask_to_prefix_checked`

* Add `ipv6_mask_to_prefix_checked`

* Add `ip_mask_to_prefix_checked`
This commit is contained in:
Markus Pettersson
2025-01-13 05:04:28 +01:00
committed by GitHub
parent 170fc4ca6b
commit 0deb2abd8b
3 changed files with 50 additions and 13 deletions

View File

@ -382,13 +382,24 @@ impl IntoIterator for &'_ Ipv4Network {
/// ///
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`. /// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> { pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
let mask = u32::from(mask); match ipv4_mask_to_prefix_checked(mask) {
Some(prefix) => Ok(prefix),
None => Err(IpNetworkError::InvalidPrefix),
}
}
/// Converts a `Ipv4Addr` network mask into a prefix.
///
/// If the mask is invalid this will return `None`. This is useful in const contexts where
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
pub const fn ipv4_mask_to_prefix_checked(mask: Ipv4Addr) -> Option<u8> {
let mask = mask.to_bits();
let prefix = (!mask).leading_zeros() as u8; let prefix = (!mask).leading_zeros() as u8;
if (u64::from(mask) << prefix) & 0xffff_ffff != 0 { if ((mask as u64) << prefix) & 0xffff_ffff != 0 {
Err(IpNetworkError::InvalidPrefix) None
} else { } else {
Ok(prefix) Some(prefix)
} }
} }

View File

@ -391,12 +391,25 @@ impl fmt::Display for Ipv6Network {
/// Converts a `Ipv6Addr` network mask into a prefix. /// Converts a `Ipv6Addr` network mask into a prefix.
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`. /// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> { pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
match ipv6_mask_to_prefix_checked(mask) {
Some(prefix) => Ok(prefix),
None => Err(IpNetworkError::InvalidPrefix),
}
}
/// Converts a `Ipv6Addr` network mask into a prefix.
///
/// If the mask is invalid this will return `None`. This is useful in const contexts where
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
pub const fn ipv6_mask_to_prefix_checked(mask: Ipv6Addr) -> Option<u8> {
let mask = mask.segments(); let mask = mask.segments();
let mut mask_iter = mask.iter();
// Count the number of set bits from the start of the address // Count the number of set bits from the start of the address
let mut prefix = 0; let mut prefix = 0;
for &segment in &mut mask_iter { let mut i = 0;
while i < mask.len() {
let segment = mask[i];
i += 1;
if segment == 0xffff { if segment == 0xffff {
prefix += IPV6_SEGMENT_BITS; prefix += IPV6_SEGMENT_BITS;
} else if segment == 0 { } else if segment == 0 {
@ -406,7 +419,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
let prefix_bits = (!segment).leading_zeros() as u8; let prefix_bits = (!segment).leading_zeros() as u8;
// Check that the remainder of the bits are all unset // Check that the remainder of the bits are all unset
if segment << prefix_bits != 0 { if segment << prefix_bits != 0 {
return Err(IpNetworkError::InvalidPrefix); return None;
} }
prefix += prefix_bits; prefix += prefix_bits;
break; break;
@ -414,13 +427,15 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
} }
// Now check all the remaining bits are unset // Now check all the remaining bits are unset
for &segment in mask_iter { while i < mask.len() {
let segment = mask[i];
i += 1;
if segment != 0 { if segment != 0 {
return Err(IpNetworkError::InvalidPrefix); return None;
} }
} }
Ok(prefix) Some(prefix)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -16,11 +16,11 @@ mod ipv6;
mod parse; mod parse;
mod size; mod size;
pub use crate::error::{NetworkSizeError, IpNetworkError}; pub use crate::error::{IpNetworkError, NetworkSizeError};
pub use crate::ipv4::Ipv4NetworkIterator; pub use crate::ipv4::Ipv4NetworkIterator;
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network}; pub use crate::ipv4::{ipv4_mask_to_prefix, ipv4_mask_to_prefix_checked, Ipv4Network};
pub use crate::ipv6::Ipv6NetworkIterator; pub use crate::ipv6::Ipv6NetworkIterator;
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network}; pub use crate::ipv6::{ipv6_mask_to_prefix, ipv6_mask_to_prefix_checked, Ipv6Network};
pub use crate::size::NetworkSize; pub use crate::size::NetworkSize;
/// Represents a generic network range. This type can have two variants: /// Represents a generic network range. This type can have two variants:
@ -433,6 +433,17 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
} }
} }
/// Converts a `IpAddr` network mask into a prefix.
///
/// If the mask is invalid this will return `None`. This is useful in const contexts where
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
pub const fn ip_mask_to_prefix_checked(mask: IpAddr) -> Option<u8> {
match mask {
IpAddr::V4(mask) => ipv4_mask_to_prefix_checked(mask),
IpAddr::V6(mask) => ipv6_mask_to_prefix_checked(mask),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
#[test] #[test]