From 6eda7d9f99a8bc4a524d5c41a256ad82c15a1c2b Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 8 Jan 2025 10:15:09 +0100 Subject: [PATCH 1/3] Add `ipv4_mask_to_prefix_checked` --- src/ipv4.rs | 19 +++++++++++++++---- src/lib.rs | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ipv4.rs b/src/ipv4.rs index 945813d..c977f69 100644 --- a/src/ipv4.rs +++ b/src/ipv4.rs @@ -382,13 +382,24 @@ impl IntoIterator for &'_ Ipv4Network { /// /// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`. pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result { - 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 { + let mask = mask.to_bits(); let prefix = (!mask).leading_zeros() as u8; - if (u64::from(mask) << prefix) & 0xffff_ffff != 0 { - Err(IpNetworkError::InvalidPrefix) + if ((mask as u64) << prefix) & 0xffff_ffff != 0 { + None } else { - Ok(prefix) + Some(prefix) } } diff --git a/src/lib.rs b/src/lib.rs index 86abf42..507649c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,9 @@ mod ipv6; mod parse; mod size; -pub use crate::error::{NetworkSizeError, IpNetworkError}; +pub use crate::error::{IpNetworkError, NetworkSizeError}; 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::{ipv6_mask_to_prefix, Ipv6Network}; pub use crate::size::NetworkSize; From 2f9e0acc5ef219ab598dc228ea4b7d5e4e3c2215 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 8 Jan 2025 10:26:55 +0100 Subject: [PATCH 2/3] Add `ipv6_mask_to_prefix_checked` --- src/ipv6.rs | 27 +++++++++++++++++++++------ src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/ipv6.rs b/src/ipv6.rs index 5a28f65..de07288 100644 --- a/src/ipv6.rs +++ b/src/ipv6.rs @@ -391,12 +391,25 @@ impl fmt::Display for Ipv6Network { /// Converts a `Ipv6Addr` network mask into a prefix. /// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`. pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { + 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 { let mask = mask.segments(); - let mut mask_iter = mask.iter(); // Count the number of set bits from the start of the address 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 { prefix += IPV6_SEGMENT_BITS; } else if segment == 0 { @@ -406,7 +419,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { let prefix_bits = (!segment).leading_zeros() as u8; // Check that the remainder of the bits are all unset if segment << prefix_bits != 0 { - return Err(IpNetworkError::InvalidPrefix); + return None; } prefix += prefix_bits; break; @@ -414,13 +427,15 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { } // 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 { - return Err(IpNetworkError::InvalidPrefix); + return None; } } - Ok(prefix) + Some(prefix) } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 507649c..183a541 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub use crate::error::{IpNetworkError, NetworkSizeError}; pub use crate::ipv4::Ipv4NetworkIterator; pub use crate::ipv4::{ipv4_mask_to_prefix, ipv4_mask_to_prefix_checked, Ipv4Network}; 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; /// Represents a generic network range. This type can have two variants: From 887d62e39d77b8c30fc9127593b3ff9e7349a0bd Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 8 Jan 2025 10:28:26 +0100 Subject: [PATCH 3/3] Add `ip_mask_to_prefix_checked` --- src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 183a541..1b8702a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -433,6 +433,17 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result { } } +/// 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 { + match mask { + IpAddr::V4(mask) => ipv4_mask_to_prefix_checked(mask), + IpAddr::V6(mask) => ipv6_mask_to_prefix_checked(mask), + } +} + #[cfg(test)] mod test { #[test]