11 Commits

Author SHA1 Message Date
c3af07d69a Impl From<Ipv4Addr, Ipv6Addr> for IpNetwork (#218) 2025-03-22 15:30:10 -05:00
35977adc81 Iterator size hints (#217)
* feat: iterator size hints

* fix: add size_hint to IpNetworkIterator
2025-02-28 10:00:56 -06:00
0deb2abd8b 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`
2025-01-12 22:04:28 -06:00
170fc4ca6b Add rust-version to Cargo.toml and set MSRV to 1.80.0 (#215) 2025-01-12 22:03:05 -06:00
aa65f088b1 Make IpNetwork::is_ipv4 and IpNetwork::is_ipv6 const (#213) 2025-01-09 13:06:22 -06:00
5f929258ef Make IpNetwork::ip const (#212) 2025-01-08 17:38:34 -06:00
a21f5df04e Make IpNetwork::broadcast const (#211) 2025-01-08 17:38:16 -06:00
876f882523 Make IpNetwork::network const (#210) 2025-01-08 17:37:32 -06:00
49f07a4838 Make IpNetwork::prefix const (#209) 2025-01-08 17:36:53 -06:00
2f219a2ada Make IpNetwork::contains const (#208)
* Make `Ipv6Network::contains` const

* Make `Ipv4Network::contains` const

* Make `IpNetwork::contains` const
2025-01-08 10:22:33 -06:00
9ae719637d Make IpNetwork::mask const (#207)
* Make `Ipv4Network::mask` const

* Make `Ipv6Network::mask` const

* Make `IpNetwork::mask` const
2025-01-07 21:03:10 -06:00
4 changed files with 165 additions and 55 deletions

View File

@ -9,6 +9,7 @@ keywords = ["network", "ip", "address", "cidr"]
readme = "README.md"
categories = ["network-programming", "parser-implementations"]
edition = "2021"
rust-version = "1.80.0"
[dependencies]
serde = { version = "1.0.200", optional = true }

View File

@ -140,11 +140,11 @@ impl Ipv4Network {
}
}
pub fn ip(self) -> Ipv4Addr {
pub const fn ip(self) -> Ipv4Addr {
self.addr
}
pub fn prefix(self) -> u8 {
pub const fn prefix(self) -> u8 {
self.prefix
}
@ -180,13 +180,13 @@ impl Ipv4Network {
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
/// ```
pub fn mask(&self) -> Ipv4Addr {
pub const fn mask(&self) -> Ipv4Addr {
debug_assert!(self.prefix <= 32);
if self.prefix == 0 {
return Ipv4Addr::new(0, 0, 0, 0);
}
let mask = u32::MAX << (IPV4_BITS - self.prefix);
Ipv4Addr::from(mask)
Ipv4Addr::from_bits(mask)
}
/// Returns the address of the network denoted by this `Ipv4Network`.
@ -201,10 +201,10 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
/// ```
pub fn network(&self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let ip = u32::from(self.addr) & mask;
Ipv4Addr::from(ip)
pub const fn network(&self) -> Ipv4Addr {
let mask = self.mask().to_bits();
let ip = self.addr.to_bits() & mask;
Ipv4Addr::from_bits(ip)
}
/// Returns the broadcasting address of this `Ipv4Network`.
@ -219,10 +219,10 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
/// ```
pub fn broadcast(&self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let broadcast = u32::from(self.addr) | !mask;
Ipv4Addr::from(broadcast)
pub const fn broadcast(&self) -> Ipv4Addr {
let mask = self.mask().to_bits();
let broadcast = self.addr.to_bits() | !mask;
Ipv4Addr::from_bits(broadcast)
}
/// Checks if a given `Ipv4Addr` is in this `Ipv4Network`
@ -238,12 +238,12 @@ impl Ipv4Network {
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
/// ```
#[inline]
pub fn contains(&self, ip: Ipv4Addr) -> bool {
pub const fn contains(&self, ip: Ipv4Addr) -> bool {
debug_assert!(self.prefix <= IPV4_BITS);
let mask = !(0xffff_ffff_u64 >> self.prefix) as u32;
let net = u32::from(self.addr) & mask;
(u32::from(ip) & mask) == net
let net = self.addr.to_bits() & mask;
(ip.to_bits() & mask) == net
}
/// Returns number of possible host addresses in this `Ipv4Network`.
@ -368,6 +368,15 @@ impl Iterator for Ipv4NetworkIterator {
};
Some(next.into())
}
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(n) = self.next {
let elms = (self.end - n + 1) as usize;
(elms, Some(elms))
} else {
(0, None)
}
}
}
impl IntoIterator for &'_ Ipv4Network {
@ -382,13 +391,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<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;
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)
}
}
@ -571,6 +591,25 @@ mod test {
assert_eq!(None, iter.next());
}
#[test]
fn iterator_v4_size_hint() {
let cidr: Ipv4Network = "192.168.0.0/24".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!((256, Some(256)), iter.size_hint());
iter.next();
assert_eq!((255, Some(255)), iter.size_hint());
let cidr: Ipv4Network = "192.168.0.0/32".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!((1, Some(1)), iter.size_hint());
iter.next();
assert_eq!((0, None), iter.size_hint());
let cidr: Ipv4Network = "192.168.0.0/0".parse().unwrap();
let iter = cidr.iter();
assert_eq!((4294967295, Some(4294967295)), iter.size_hint());
}
#[test]
fn v4_mask_to_prefix() {
let mask = Ipv4Addr::new(255, 255, 255, 128);

View File

@ -160,11 +160,11 @@ impl Ipv6Network {
}
}
pub fn ip(&self) -> Ipv6Addr {
pub const fn ip(&self) -> Ipv6Addr {
self.addr
}
pub fn prefix(&self) -> u8 {
pub const fn prefix(&self) -> u8 {
self.prefix
}
@ -200,14 +200,14 @@ impl Ipv6Network {
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
/// ```
pub fn mask(&self) -> Ipv6Addr {
pub const fn mask(&self) -> Ipv6Addr {
debug_assert!(self.prefix <= IPV6_BITS);
if self.prefix == 0 {
return Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
}
let mask = u128::MAX << (IPV6_BITS - self.prefix);
Ipv6Addr::from(mask)
Ipv6Addr::from_bits(mask)
}
/// Returns the address of the network denoted by this `Ipv6Network`.
@ -222,10 +222,10 @@ impl Ipv6Network {
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
/// ```
pub fn network(&self) -> Ipv6Addr {
let mask = u128::from(self.mask());
let network = u128::from(self.addr) & mask;
Ipv6Addr::from(network)
pub const fn network(&self) -> Ipv6Addr {
let mask = self.mask().to_bits();
let network = self.addr.to_bits() & mask;
Ipv6Addr::from_bits(network)
}
/// Returns the broadcast address of this `Ipv6Network`.
@ -240,10 +240,10 @@ impl Ipv6Network {
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xffff, 0xffff));
/// ```
pub fn broadcast(&self) -> Ipv6Addr {
let mask = u128::from(self.mask());
let broadcast = u128::from(self.addr) | !mask;
Ipv6Addr::from(broadcast)
pub const fn broadcast(&self) -> Ipv6Addr {
let mask = self.mask().to_bits();
let broadcast = self.addr.to_bits() | !mask;
Ipv6Addr::from_bits(broadcast)
}
/// Checks if a given `Ipv6Addr` is in this `Ipv6Network`
@ -259,10 +259,10 @@ impl Ipv6Network {
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
/// ```
#[inline]
pub fn contains(&self, ip: Ipv6Addr) -> bool {
let ip = u128::from(ip);
let net = u128::from(self.network());
let mask = u128::from(self.mask());
pub const fn contains(&self, ip: Ipv6Addr) -> bool {
let ip = ip.to_bits();
let net = self.network().to_bits();
let mask = self.mask().to_bits();
(ip & mask) == net
}
@ -372,6 +372,15 @@ impl Iterator for Ipv6NetworkIterator {
};
Some(next.into())
}
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(n) = self.next {
let elms = (self.end - n + 1) as usize;
(elms, Some(elms))
} else {
(0, None)
}
}
}
impl IntoIterator for &'_ Ipv6Network {
@ -391,12 +400,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<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 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 +428,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
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 +436,15 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
}
// 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)]
@ -597,6 +621,18 @@ mod test {
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2), iter.next().unwrap());
}
#[test]
fn iterator_v6_size_hint() {
let cidr: Ipv6Network = "2001:db8::/128".parse().unwrap();
let mut iter = cidr.iter();
assert_eq!((1, Some(1)), iter.size_hint());
assert_eq!(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
iter.next().unwrap()
);
assert_eq!((0, None), iter.size_hint());
}
#[test]
fn network_v6() {
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();

View File

@ -8,7 +8,12 @@
unused_import_braces
)]
use std::{convert::TryFrom, fmt, net::IpAddr, str::FromStr};
use std::{
convert::TryFrom,
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
mod error;
mod ipv4;
@ -16,11 +21,11 @@ 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::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:
@ -141,7 +146,7 @@ impl IpNetwork {
}
/// Returns the IP part of a given `IpNetwork`
pub fn ip(&self) -> IpAddr {
pub const fn ip(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.ip()),
IpNetwork::V6(ref a) => IpAddr::V6(a.ip()),
@ -160,7 +165,7 @@ impl IpNetwork {
/// assert_eq!(IpNetwork::V6("ff01::0".parse().unwrap()).prefix(), 128u8);
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).prefix(), 32u8);
/// ```
pub fn prefix(&self) -> u8 {
pub const fn prefix(&self) -> u8 {
match *self {
IpNetwork::V4(ref a) => a.prefix(),
IpNetwork::V6(ref a) => a.prefix(),
@ -181,7 +186,7 @@ impl IpNetwork {
/// let net: IpNetwork = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
/// ```
pub fn network(&self) -> IpAddr {
pub const fn network(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.network()),
IpNetwork::V6(ref a) => IpAddr::V6(a.network()),
@ -200,7 +205,7 @@ impl IpNetwork {
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
/// ```
pub fn broadcast(&self) -> IpAddr {
pub const fn broadcast(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.broadcast()),
IpNetwork::V6(ref a) => IpAddr::V6(a.broadcast()),
@ -226,7 +231,7 @@ impl IpNetwork {
/// let v6_net: IpNetwork = "ff01::0/32".parse().unwrap();
/// assert_eq!(v6_net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
/// ```
pub fn mask(&self) -> IpAddr {
pub const fn mask(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.mask()),
IpNetwork::V6(ref a) => IpAddr::V6(a.mask()),
@ -245,7 +250,7 @@ impl IpNetwork {
/// assert_eq!(v4.is_ipv4(), true);
/// assert_eq!(v4.is_ipv6(), false);
///```
pub fn is_ipv4(&self) -> bool {
pub const fn is_ipv4(&self) -> bool {
match *self {
IpNetwork::V4(_) => true,
IpNetwork::V6(_) => false,
@ -264,7 +269,7 @@ impl IpNetwork {
/// assert_eq!(v6.is_ipv6(), true);
/// assert_eq!(v6.is_ipv4(), false);
///```
pub fn is_ipv6(&self) -> bool {
pub const fn is_ipv6(&self) -> bool {
match *self {
IpNetwork::V4(_) => false,
IpNetwork::V6(_) => true,
@ -292,7 +297,7 @@ impl IpNetwork {
/// assert!(!net.contains(ip4));
/// ```
#[inline]
pub fn contains(&self, ip: IpAddr) -> bool {
pub const fn contains(&self, ip: IpAddr) -> bool {
match (*self, ip) {
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
(IpNetwork::V6(net), IpAddr::V6(ip)) => net.contains(ip),
@ -377,11 +382,23 @@ impl From<Ipv6Network> for IpNetwork {
}
}
impl From<Ipv4Addr> for IpNetwork {
fn from(addr: Ipv4Addr) -> IpNetwork {
IpNetwork::V4(Ipv4Network::from(addr))
}
}
impl From<Ipv6Addr> for IpNetwork {
fn from(addr: Ipv6Addr) -> IpNetwork {
IpNetwork::V6(Ipv6Network::from(addr))
}
}
impl From<IpAddr> for IpNetwork {
fn from(addr: IpAddr) -> IpNetwork {
match addr {
IpAddr::V4(a) => IpNetwork::V4(Ipv4Network::from(a)),
IpAddr::V6(a) => IpNetwork::V6(Ipv6Network::from(a)),
IpAddr::V4(a) => IpNetwork::from(a),
IpAddr::V6(a) => IpNetwork::from(a),
}
}
}
@ -414,6 +431,12 @@ impl Iterator for IpNetworkIterator {
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.inner {
IpNetworkIteratorInner::V4(iter) => iter.size_hint(),
IpNetworkIteratorInner::V6(iter) => iter.size_hint(),
}
}
}
impl IntoIterator for &'_ IpNetwork {
@ -433,6 +456,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)]
mod test {
#[test]