55 Commits

Author SHA1 Message Date
a21cb4d081 Update schemars requirement from 0.8.17 to 0.9.0
Updates the requirements on [schemars](https://github.com/GREsau/schemars) to permit the latest version.
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v0.8.17...v0.9.0)

---
updated-dependencies:
- dependency-name: schemars
  dependency-version: 0.9.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-08 04:39:00 +00:00
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
c2812a91a8 chore: release v0.21.1 (#206)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-07 12:55:55 -06:00
e1d3ac6de1 Fix for 0::/0 network (#205) 2025-01-06 22:13:12 -06:00
551d1a62b1 chore: release v0.21.0 (#204)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-05 21:59:39 -06:00
285cf9d409 Update rust.yml 2025-01-05 21:41:07 -06:00
ea467bb744 Update .gitignore 2025-01-05 21:30:56 -06:00
209d5432a6 Update publish.yml 2025-01-05 21:28:23 -06:00
16095cb510 Update publish.yml 2025-01-05 21:23:27 -06:00
4f7d898a86 Replace Ipv{4,6}Network::new_unchecked with Ipv{4,6}Network::new_checked (#203)
* Replace `Ipv4Network::new_unchecked` with `Ipv4Network::new_checked`

* Replace `Ipv6Network::new_unchecked` with `Ipv6Network::new_checked`

* Update doc test for `Ipv{4,6}Network::new_checked` to use `should_panic`

* Better demonstrate use of `Ipv{4,6}Network::new_checked` in const contexts

* Add back `unsafe_code` lint at the deny level
2025-01-05 21:19:04 -06:00
d665bb6beb Make the serde feature opt-in instead of opt-out (#200)
Most people add dependencies without checking their features
or optional dependencies too carefully. This leads to them pulling in
transitive dependencies they likely don't need.
2024-09-24 10:40:42 -05:00
ad46759a55 fix for 0.0.0.0/0 network (#199) 2024-09-07 14:22:12 -05:00
dsx
cbf8d6b29a Fix typo (#198) 2024-08-09 20:53:42 -05:00
7e0519cd35 fix(deps): update rust crate serde to 1.0.200 (#196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 12:44:19 -05:00
83a62c9605 Update publish.yml (#195) 2024-05-01 09:12:06 -05:00
c840f27855 chore(deps): update rust crate serde_json to 1.0.116 (#193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 09:10:53 -05:00
4b16661585 fix(deps): update rust crate serde to 1.0.199 (#194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 09:10:23 -05:00
a0831a77b9 chore: setup release plz (#192) 2024-04-30 21:59:49 -05:00
f0fd0521e1 fix: use associated constants (#191) 2024-04-30 21:49:56 -05:00
bb51dee73a Update Rust crate criterion to 0.5.1 (#172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 21:43:09 -05:00
b5f5b79d23 Update actions/checkout action to v4 (#182)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 21:42:39 -05:00
70d1f74e29 ipv6: rewrite core ipv6 methods to operate on u128s (#187)
* errors: adds `From<AddrParseError>` for `IpNetworkError`

* ipv4: adds safety comment to `new_unchecked` and debug assertions to verify compliance

* ipv6: rewrite core ipv6 methods to operate on `u128`s
2024-04-30 21:41:17 -05:00
068959e2c4 github: move to dtolnay/rust-toolchain and run clippy (#189) 2024-04-29 22:25:29 -05:00
241b1dcdf8 netsize: Hash implementation to match PartialEq (#186)
* netsize: hash implementation to match PartialEq

* netsize: Prefer `From` over `Into`, add documentation, and add Hash and PartialEq tests
2024-04-29 22:23:28 -05:00
6e32dd2e88 Update Rust crate schemars to 0.8.17 (#184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-28 13:42:11 -05:00
a1966d4089 Add const unsafe new_unchecked to Ipv4Network & Ipv6Network (#185)
* Add const unsafe `new_unchecked` to `Ipv4Network` & `Ipv6Network`

* New tests added for `new_unchecked` functions
2023-12-21 09:03:04 -06:00
817b5babed Update Rust crate schemars to 0.8.15 (#183)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 12:04:15 -05:00
9bf407e634 Update Rust crate schemars to 0.8.13 (#181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-28 19:09:51 -05:00
1bd937e90c Add Ipv6Network::nth to get the nth address (take two) (#176)
* Add `Ipv6Network::nth` to get the nth address

Works the same as `Ipv4Network::nth`, but using a `u128` index instead
of `u32`.

* add IpNetwork v6 nth test

---------

Co-authored-by: Josh Triplett <josh@joshtriplett.org>
2023-06-13 21:17:39 -05:00
d19ac33071 Added needed traits to NetworkSize (#175)
* Add conversion and better equality traits to NetworkSize

* Add tests for NetworkSize conversion/equality

* impl Display for NetworkSize
2023-06-13 21:14:02 -05:00
19aafee3c8 Update criterion requirement from 0.4.0 to 0.5.0
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.4.0...0.5.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-24 20:33:33 -05:00
a01d32ede8 Update katyo/publish-crates action to v2 2023-04-23 19:53:43 -05:00
b4c0554d11 Update actions/checkout action to v3 2023-04-23 19:53:26 -05:00
3a2d1da053 Update Rust crate schemars to 0.8.12 2023-04-23 19:53:07 -05:00
98914649d6 Add renovate.json 2023-04-23 15:04:05 -05:00
0bb1ea8fd4 Replace assert_eq with assert for bool comparison 2023-04-07 14:36:02 -05:00
b624487f72 Use cargo clippy --fix to autofix code 2023-04-07 14:36:02 -05:00
dff59ea0a5 Add a reference where missing 2023-04-07 12:00:03 -05:00
432f7342e3 Cleanup mask for Ipv4Addr 2023-04-07 12:00:03 -05:00
b15eb54695 Shrink the enumerate call on mask 2023-04-07 12:00:03 -05:00
ec79428c75 Cleanup both size functions 2023-04-07 12:00:03 -05:00
a96f30d64e Simplify FromStr for Ipv6Network 2023-04-07 12:00:03 -05:00
ef3f536d48 Make parse_prefix more idiomatic 2023-04-07 12:00:03 -05:00
a42e9d9593 Update criterion requirement from 0.3.4 to 0.4.0 (#162)
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Release notes](https://github.com/bheisler/criterion.rs/releases)
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.3.4...0.4.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-13 16:01:00 -05:00
2c9bf9b29b Update does-it-json requirement from 0.0.3 to 0.0.4 (#161)
Updates the requirements on [does-it-json](https://github.com/ahl/does-it-json) to permit the latest version.
- [Release notes](https://github.com/ahl/does-it-json/releases)
- [Commits](https://github.com/ahl/does-it-json/compare/v0.0.3...v0.0.4)

---
updated-dependencies:
- dependency-name: does-it-json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 08:58:10 -05:00
14 changed files with 848 additions and 231 deletions

View File

@ -1,19 +1,54 @@
name: Publish to crates.io
name: Release-plz
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
push:
tags:
- 'v.*.*'
branches:
- main
jobs:
publish:
# Release unpublished packages.
release-plz-release:
name: Release-plz release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- name: Checkout repository
uses: actions/checkout@v4
with:
toolchain: stable
override: true
- uses: katyo/publish-crates@v1
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: release-plz/action@v0.5
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
command: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
# Create a PR with the new versions and changelog, preparing the next release.
release-plz-pr:
name: Release-plz PR
runs-on: ubuntu-latest
concurrency:
group: release-plz-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: release-plz/action@v0.5
with:
command: release-pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@ -1,6 +1,7 @@
name: Rust
on:
workflow_dispatch:
pull_request:
branches:
- master
@ -12,9 +13,10 @@ jobs:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: hecrj/setup-rust-action@v1
- uses: dtolnay/rust-toolchain@stable
with:
rust-version: ${{ matrix.rust }}
components: clippy, rustfmt
- uses: actions/checkout@master
with:
ref: ${{ github.ref }}
@ -26,3 +28,5 @@ jobs:
run: cargo test --verbose --all-features
- name: Build docs
run: cargo doc --verbose
- name: Run clippy
run: cargo clippy --verbose --all-features

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
.gitignore
Cargo.lock
target
.vscode

61
CHANGELOG.md Normal file
View File

@ -0,0 +1,61 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.21.1](https://github.com/achanda/ipnetwork/compare/v0.21.0...v0.21.1) - 2025-01-07
### Other
- Fix for 0::/0 network ([#205](https://github.com/achanda/ipnetwork/pull/205))
## [0.21.0](https://github.com/achanda/ipnetwork/compare/v0.20.0...v0.21.0) - 2025-01-06
### Fixed
- fix for 0.0.0.0/0 network ([#199](https://github.com/achanda/ipnetwork/pull/199))
- *(deps)* update rust crate serde to 1.0.200 (#196)
- *(deps)* update rust crate serde to 1.0.199 (#194)
- use associated constants (#191)
### Other
- Update .gitignore
- Update publish.yml
- Update publish.yml
- Replace `Ipv{4,6}Network::new_unchecked` with `Ipv{4,6}Network::new_checked` ([#203](https://github.com/achanda/ipnetwork/pull/203))
- Make the serde feature opt-in instead of opt-out ([#200](https://github.com/achanda/ipnetwork/pull/200))
- Fix typo ([#198](https://github.com/achanda/ipnetwork/pull/198))
- Update publish.yml ([#195](https://github.com/achanda/ipnetwork/pull/195))
- *(deps)* update rust crate serde_json to 1.0.116 (#193)
- setup release plz (#192)
- Update Rust crate criterion to 0.5.1 ([#172](https://github.com/achanda/ipnetwork/pull/172))
- Update actions/checkout action to v4 ([#182](https://github.com/achanda/ipnetwork/pull/182))
- rewrite core ipv6 methods to operate on u128s (#187)
- move to dtolnay/rust-toolchain and run clippy (#189)
- Hash implementation to match PartialEq (#186)
- Update Rust crate schemars to 0.8.17 ([#184](https://github.com/achanda/ipnetwork/pull/184))
- Add const unsafe `new_unchecked` to `Ipv4Network` & `Ipv6Network` ([#185](https://github.com/achanda/ipnetwork/pull/185))
- Update Rust crate schemars to 0.8.15 ([#183](https://github.com/achanda/ipnetwork/pull/183))
- Update Rust crate schemars to 0.8.13 ([#181](https://github.com/achanda/ipnetwork/pull/181))
- Add `Ipv6Network::nth` to get the nth address (take two) ([#176](https://github.com/achanda/ipnetwork/pull/176))
- Added needed traits to `NetworkSize` ([#175](https://github.com/achanda/ipnetwork/pull/175))
- Update criterion requirement from 0.4.0 to 0.5.0
- Update katyo/publish-crates action to v2
- Update actions/checkout action to v3
- Update Rust crate schemars to 0.8.12
- Add renovate.json
- Replace assert_eq with assert for bool comparison
- Use cargo clippy --fix to autofix code
- Add a reference where missing
- Cleanup mask for Ipv4Addr
- Shrink the enumerate call on mask
- Cleanup both size functions
- Simplify FromStr for Ipv6Network
- Make parse_prefix more idiomatic
- Update criterion requirement from 0.3.4 to 0.4.0 ([#162](https://github.com/achanda/ipnetwork/pull/162))
- Update does-it-json requirement from 0.0.3 to 0.0.4 ([#161](https://github.com/achanda/ipnetwork/pull/161))

View File

@ -1,6 +1,6 @@
[package]
name = "ipnetwork"
version = "0.20.0"
version = "0.21.1"
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
description = "A library to work with IP CIDRs in Rust"
license = "MIT OR Apache-2.0"
@ -9,22 +9,26 @@ keywords = ["network", "ip", "address", "cidr"]
readme = "README.md"
categories = ["network-programming", "parser-implementations"]
edition = "2021"
rust-version = "1.80.0"
[dependencies]
serde = { version = "1", optional = true }
schemars = { version = "0.8.10", optional = true }
serde = { version = "1.0.200", optional = true }
schemars = { version = "0.9.0", optional = true }
[dev-dependencies]
serde_json = "1.0"
criterion = {version = "0.3.4", features= ["html_reports"]}
does-it-json = "0.0.3"
serde_json = "1.0.116"
criterion = {version = "0.5.1", features= ["html_reports"]}
does-it-json = "0.0.4"
[badges]
travis-ci = { repository = "achanda/ipnetwork" }
maintenance = { status = "passively-maintained" }
[features]
default = ["serde"]
# Keep optional dependencies out of the default features.
# Since most people add a dependency without `default-features = false` they involuntarily
# pull in unused dependencies.
default = []
[[bench]]
name = "parse_bench"

6
renovate.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View File

@ -1,62 +0,0 @@
use std::{error::Error, fmt};
/// Represents a bunch of errors that can occur while working with a `IpNetwork`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpNetworkError {
InvalidAddr(String),
InvalidPrefix,
InvalidCidrFormat(String),
}
impl fmt::Display for IpNetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(ref s) => write!(f, "invalid address: {}", s),
InvalidPrefix => write!(f, "invalid prefix"),
InvalidCidrFormat(ref s) => write!(f, "invalid cidr format: {}", s),
}
}
}
impl Error for IpNetworkError {
fn description(&self) -> &str {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(_) => "address is invalid",
InvalidPrefix => "prefix is invalid",
InvalidCidrFormat(_) => "cidr is invalid",
}
}
}
pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
// Try to find a single slash
if let Some(sep) = cidr.find('/') {
let (ip, prefix) = cidr.split_at(sep);
// Error if cidr has multiple slashes
if prefix[1..].find('/').is_some() {
Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {}",
cidr
)))
} else {
// Handle the case when cidr has exactly one slash
Ok((ip, Some(&prefix[1..])))
}
} else {
// Handle the case when cidr does not have a slash
Ok((cidr, None))
}
}
pub fn parse_prefix(prefix: &str, max: u8) -> Result<u8, IpNetworkError> {
let mask = prefix
.parse::<u8>()
.map_err(|_| IpNetworkError::InvalidPrefix)?;
if mask > max {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(mask)
}
}

56
src/error.rs Normal file
View File

@ -0,0 +1,56 @@
use std::{error::Error, fmt, net::AddrParseError};
use crate::error::IpNetworkError::*;
/// Represents a bunch of errors that can occur while working with a `IpNetwork`
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum IpNetworkError {
InvalidAddr(String),
InvalidPrefix,
InvalidCidrFormat(String),
NetworkSizeError(NetworkSizeError),
}
impl fmt::Display for IpNetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
InvalidAddr(ref s) => write!(f, "invalid address: {s}"),
InvalidPrefix => write!(f, "invalid prefix"),
InvalidCidrFormat(ref s) => write!(f, "invalid cidr format: {s}"),
NetworkSizeError(ref e) => write!(f, "network size error: {e}"),
}
}
}
impl Error for IpNetworkError {
fn description(&self) -> &str {
match *self {
InvalidAddr(_) => "address is invalid",
InvalidPrefix => "prefix is invalid",
InvalidCidrFormat(_) => "cidr is invalid",
NetworkSizeError(_) => "network size error",
}
}
}
impl From<AddrParseError> for IpNetworkError {
fn from(e: AddrParseError) -> Self {
InvalidAddr(e.to_string())
}
}
/// Cannot convert an IPv6 network size to a u32 as it is a 128-bit value.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NetworkSizeError {
NetworkIsTooLarge,
}
impl fmt::Display for NetworkSizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Network is too large to fit into an unsigned 32-bit integer!")
}
}
impl Error for NetworkSizeError {}

View File

@ -1,4 +1,5 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use crate::error::IpNetworkError;
use crate::parse::{cidr_parts, parse_prefix};
use std::{convert::TryFrom, fmt, net::Ipv4Addr, str::FromStr};
const IPV4_BITS: u8 = 32;
@ -66,10 +67,49 @@ impl Ipv4Network {
///
/// If the prefix is larger than 32 this will return an `IpNetworkError::InvalidPrefix`.
pub const fn new(addr: Ipv4Addr, prefix: u8) -> Result<Ipv4Network, IpNetworkError> {
match Ipv4Network::new_checked(addr, prefix) {
Some(a) => Ok(a),
None => Err(IpNetworkError::InvalidPrefix),
}
}
/// Constructs a new `Ipv4Network` from any `Ipv4Addr`, and a prefix denoting the network size.
///
/// If the prefix is larger than 32 this will return `None`. This is useful in const contexts,
/// where [`Option::unwrap`] may be called to trigger a compile-time error in case the prefix
/// is an unexpected value.
///
/// # Examples
///
/// ```
/// use std::net::Ipv4Addr;
/// use ipnetwork::Ipv4Network;
///
/// const PREFIX: u8 = 24;
/// const ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 1);
///
/// // Okay!
/// const NETWORK: Ipv4Network = Ipv4Network::new_checked(ADDR, PREFIX).unwrap();
/// assert_eq!(NETWORK.prefix(), PREFIX);
/// ```
///
/// ```should_panic
/// use std::net::Ipv4Addr;
/// use ipnetwork::Ipv4Network;
///
/// // Prefix is greater than 32.
/// const PREFIX: u8 = 32 + 1;
/// const ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 1);
///
/// // This fails!
/// const NETWORK: Option<Ipv4Network> = Ipv4Network::new_checked(ADDR, PREFIX);
/// assert_eq!(NETWORK.unwrap().prefix(), PREFIX);
/// ```
pub const fn new_checked(addr: Ipv4Addr, prefix: u8) -> Option<Ipv4Network> {
if prefix > IPV4_BITS {
Err(IpNetworkError::InvalidPrefix)
None
} else {
Ok(Ipv4Network { addr, prefix })
Some(Ipv4Network { addr, prefix })
}
}
@ -100,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
}
@ -121,8 +161,9 @@ impl Ipv4Network {
/// Checks if the given `Ipv4Network` is partly contained in other.
pub fn overlaps(self, other: Ipv4Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
|| other.contains(self.broadcast())
|| self.contains(other.ip())
|| self.contains(other.broadcast())
}
/// Returns the mask for this `Ipv4Network`.
@ -139,10 +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 {
let prefix = self.prefix;
let mask = !(0xffff_ffff as u64 >> prefix) as u32;
Ipv4Addr::from(mask)
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_bits(mask)
}
/// Returns the address of the network denoted by this `Ipv4Network`.
@ -157,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`.
@ -175,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`
@ -194,10 +238,12 @@ impl Ipv4Network {
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
/// ```
#[inline]
pub fn contains(self, ip: Ipv4Addr) -> bool {
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
let net = u32::from(self.addr) & mask;
(u32::from(ip) & mask) == net
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 = self.addr.to_bits() & mask;
(ip.to_bits() & mask) == net
}
/// Returns number of possible host addresses in this `Ipv4Network`.
@ -215,8 +261,11 @@ impl Ipv4Network {
/// assert_eq!(tinynet.size(), 1);
/// ```
pub fn size(self) -> u32 {
let host_bits = u32::from(IPV4_BITS - self.prefix);
(2 as u32).pow(host_bits)
debug_assert!(self.prefix <= 32);
if self.prefix == 0 {
return u32::MAX;
}
1 << (IPV4_BITS - self.prefix)
}
/// Returns the `n`:th address within this network.
@ -269,8 +318,7 @@ impl FromStr for Ipv4Network {
type Err = IpNetworkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (addr_str, prefix_str) = cidr_parts(s)?;
let addr = Ipv4Addr::from_str(addr_str)
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let addr = Ipv4Addr::from_str(addr_str)?;
let prefix = match prefix_str {
Some(v) => {
if let Ok(netmask) = Ipv4Addr::from_str(v) {
@ -320,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 {
@ -334,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)
}
}
@ -363,6 +431,18 @@ mod test {
assert!(net.is_err());
}
#[test]
fn create_checked_v4() {
let cidr = Ipv4Network::new_checked(Ipv4Addr::new(77, 88, 21, 11), 24).unwrap();
assert_eq!(cidr.prefix(), 24);
}
#[test]
#[should_panic]
fn try_create_invalid_checked_v4() {
Ipv4Network::new_checked(Ipv4Addr::new(0, 0, 0, 0), 33).unwrap();
}
#[test]
fn parse_v4_24bit() {
let cidr: Ipv4Network = "127.1.0.0/24".parse().unwrap();
@ -445,6 +525,7 @@ mod test {
}
#[test]
#[allow(dropping_copy_types)]
fn copy_compatibility_v4() {
let net = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 16).unwrap();
mem::drop(net);
@ -504,12 +585,31 @@ mod test {
fn iterator_v4_huge() {
let cidr: Ipv4Network = "0/0".parse().unwrap();
let mut iter = cidr.iter();
for i in 0..(u32::max_value() as u64 + 1) {
for i in 0..(u32::MAX as u64 + 1) {
assert_eq!(i as u32, u32::from(iter.next().unwrap()));
}
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);
@ -616,9 +716,7 @@ mod test {
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -661,9 +759,7 @@ mod test {
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -675,9 +771,9 @@ mod test {
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
assert_eq!(skynet.overlaps(other), true);
assert_eq!(skynet.overlaps(other2), false);
assert_eq!(other2.overlaps(other3), true);
assert!(skynet.overlaps(other));
assert!(!skynet.overlaps(other2));
assert!(other2.overlaps(other3));
}
#[test]

View File

@ -1,5 +1,6 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{cmp, convert::TryFrom, fmt, net::Ipv6Addr, str::FromStr};
use crate::error::IpNetworkError;
use crate::parse::{cidr_parts, parse_prefix};
use std::{convert::TryFrom, fmt, net::Ipv6Addr, str::FromStr};
const IPV6_BITS: u8 = 128;
const IPV6_SEGMENT_BITS: u8 = 16;
@ -77,10 +78,49 @@ impl Ipv6Network {
///
/// If the prefix is larger than 128 this will return an `IpNetworkError::InvalidPrefix`.
pub const fn new(addr: Ipv6Addr, prefix: u8) -> Result<Ipv6Network, IpNetworkError> {
match Ipv6Network::new_checked(addr, prefix) {
Some(a) => Ok(a),
None => Err(IpNetworkError::InvalidPrefix),
}
}
/// Constructs a new `Ipv6Network` from any `Ipv6Addr`, and a prefix denoting the network size.
///
/// If the prefix is larger than 128 this will return `None`. This is useful in const contexts,
/// where [`Option::unwrap`] may be called to trigger a compile-time error in case the prefix
/// is an unexpected value.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// const PREFIX: u8 = 64;
/// const ADDR: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
///
/// // Okay!
/// const NETWORK: Ipv6Network = Ipv6Network::new_checked(ADDR, PREFIX).unwrap();
/// assert_eq!(NETWORK.prefix(), PREFIX);
/// ```
///
/// ```should_panic
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// // Prefix is greater than 128.
/// const PREFIX: u8 = 128 + 1;
/// const ADDR: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
///
/// // This fails!
/// const NETWORK: Option<Ipv6Network> = Ipv6Network::new_checked(ADDR, PREFIX);
/// assert_eq!(NETWORK.unwrap().prefix(), PREFIX);
/// ```
pub const fn new_checked(addr: Ipv6Addr, prefix: u8) -> Option<Ipv6Network> {
if prefix > IPV6_BITS {
Err(IpNetworkError::InvalidPrefix)
None
} else {
Ok(Ipv6Network { addr, prefix })
Some(Ipv6Network { addr, prefix })
}
}
@ -99,9 +139,13 @@ impl Ipv6Network {
/// Returns an iterator over `Ipv6Network`. Each call to `next` will return the next
/// `Ipv6Addr` in the given network. `None` will be returned when there are no more
/// addresses.
///
/// # Warning
///
/// This can return up to 2^128 addresses, which will take a _long_ time to iterate over.
pub fn iter(&self) -> Ipv6NetworkIterator {
let dec = u128::from(self.addr);
let max = u128::max_value();
let max = u128::MAX;
let prefix = self.prefix;
let mask = max.checked_shl(u32::from(IPV6_BITS - prefix)).unwrap_or(0);
@ -116,47 +160,11 @@ impl Ipv6Network {
}
}
/// Returns the address of the network denoted by this `Ipv6Network`.
/// This means the lowest possible IPv6 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::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 ip = u128::from(self.addr) & mask;
Ipv6Addr::from(ip)
}
/// Returns the broadcast address of this `Ipv6Network`.
/// This means the highest possible IPv4 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::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 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
}
@ -173,8 +181,9 @@ impl Ipv6Network {
/// Checks if the given `Ipv6Network` is partly contained in other.
pub fn overlaps(self, other: Ipv6Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
|| other.contains(self.broadcast())
|| self.contains(other.ip())
|| self.contains(other.broadcast())
}
/// Returns the mask for this `Ipv6Network`.
@ -191,15 +200,50 @@ 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 {
// Ipv6Addr::from is only implemented for [u8; 16]
let mut segments = [0; 16];
for (i, segment) in segments.iter_mut().enumerate() {
let bits_remaining = self.prefix.saturating_sub(i as u8 * 8);
let set_bits = cmp::min(bits_remaining, 8);
*segment = !(0xff as u16 >> set_bits) as u8;
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);
}
Ipv6Addr::from(segments)
let mask = u128::MAX << (IPV6_BITS - self.prefix);
Ipv6Addr::from_bits(mask)
}
/// Returns the address of the network denoted by this `Ipv6Network`.
/// This means the lowest possible IPv6 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
/// ```
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`.
/// This means the highest possible IPv4 address inside of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xffff, 0xffff));
/// ```
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`
@ -215,15 +259,11 @@ impl Ipv6Network {
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
/// ```
#[inline]
pub fn contains(&self, ip: Ipv6Addr) -> bool {
let a = self.addr.segments();
let b = ip.segments();
let addrs = Iterator::zip(a.iter(), b.iter());
self.mask()
.segments()
.iter()
.zip(addrs)
.all(|(mask, (a, b))| a & mask == b & 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
}
/// Returns number of possible host addresses in this `Ipv6Network`.
@ -241,8 +281,36 @@ impl Ipv6Network {
/// assert_eq!(tinynet.size(), 1);
/// ```
pub fn size(&self) -> u128 {
let host_bits = u32::from(IPV6_BITS - self.prefix);
(2 as u128).pow(host_bits)
debug_assert!(self.prefix <= IPV6_BITS);
if self.prefix == 0 {
return u128::MAX;
}
1 << (IPV6_BITS - self.prefix)
}
/// Returns the `n`:th address within this network.
/// The addresses are indexed from 0 and `n` must be smaller than the size of the network.
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
/// use ipnetwork::Ipv6Network;
///
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
/// assert_eq!(net.nth(0).unwrap(), "ff01::0".parse::<Ipv6Addr>().unwrap());
/// assert_eq!(net.nth(255).unwrap(), "ff01::ff".parse::<Ipv6Addr>().unwrap());
/// assert_eq!(net.nth(65538).unwrap(), "ff01::1:2".parse::<Ipv6Addr>().unwrap());
/// assert!(net.nth(net.size()).is_none());
/// ```
pub fn nth(self, n: u128) -> Option<Ipv6Addr> {
if n < self.size() {
let net = u128::from(self.network());
Some(Ipv6Addr::from(net + n))
} else {
None
}
}
}
@ -263,12 +331,8 @@ impl FromStr for Ipv6Network {
type Err = IpNetworkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (addr_str, prefix_str) = cidr_parts(s)?;
let addr = Ipv6Addr::from_str(addr_str)
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let prefix = match prefix_str {
Some(v) => parse_prefix(v, IPV6_BITS)?,
None => IPV6_BITS,
};
let addr = Ipv6Addr::from_str(addr_str)?;
let prefix = parse_prefix(prefix_str.unwrap_or(&IPV6_BITS.to_string()), IPV6_BITS)?;
Ipv6Network::new(addr, prefix)
}
}
@ -308,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 {
@ -327,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 {
@ -342,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;
@ -350,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)]
@ -385,6 +473,18 @@ mod test {
assert!(cidr.is_err());
}
#[test]
fn create_checked_v6() {
let cidr = Ipv6Network::new_checked(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 24).unwrap();
assert_eq!(cidr.prefix(), 24);
}
#[test]
#[should_panic]
fn try_create_invalid_checked_v6() {
Ipv6Network::new_checked(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 129).unwrap();
}
#[test]
fn parse_v6() {
let cidr: Ipv6Network = "::1/0".parse().unwrap();
@ -521,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();
@ -601,9 +713,7 @@ mod test {
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -646,9 +756,7 @@ mod test {
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -658,7 +766,7 @@ mod test {
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap();
assert_eq!(other2.overlaps(other), true);
assert!(other2.overlaps(other));
}
#[test]
@ -673,4 +781,36 @@ mod test {
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
}
#[test]
fn test_nth_ipv6() {
let net = Ipv6Network::from_str("ff01::/32").unwrap();
assert_eq!(
net.nth(0).unwrap(),
Ipv6Addr::from_str("ff01:0:0:0:0:0:0:0").unwrap()
);
assert_eq!(
net.nth(255).unwrap(),
Ipv6Addr::from_str("ff01::ff").unwrap()
);
assert_eq!(
net.nth(65538).unwrap(),
Ipv6Addr::from_str("ff01::1:2").unwrap()
);
assert_eq!(net.nth(net.size()), None);
}
#[test]
fn test_mask_with_prefix_0() {
let network: Ipv6Network = "0::/0".parse().unwrap();
let mask = network.mask();
assert_eq!(mask, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
}
#[test]
fn test_size_with_prefix_0() {
let network: Ipv6Network = "0::/0".parse().unwrap();
assert_eq!(network.size(), u128::MAX);
}
}

View File

@ -8,17 +8,25 @@
unused_import_braces
)]
use std::{convert::TryFrom, fmt, net::IpAddr, str::FromStr};
use std::{
convert::TryFrom,
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
mod common;
mod error;
mod ipv4;
mod ipv6;
mod parse;
mod size;
pub use crate::common::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:
/// the v4 and the v6 case.
@ -117,13 +125,6 @@ impl schemars::JsonSchema for IpNetwork {
}
}
/// Represents a generic network size. For IPv4, the max size is a u32 and for IPv6, it is a u128
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum NetworkSize {
V4(u32),
V6(u128),
}
impl IpNetwork {
/// Constructs a new `IpNetwork` from a given `IpAddr` and a prefix denoting the
/// network size. If the prefix is larger than 32 (for IPv4) or 128 (for IPv6), this
@ -145,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()),
@ -164,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(),
@ -185,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()),
@ -204,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()),
@ -230,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()),
@ -249,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,
@ -268,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,
@ -296,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),
@ -381,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),
}
}
}
@ -418,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 {
@ -437,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]

33
src/parse.rs Normal file
View File

@ -0,0 +1,33 @@
use crate::error::IpNetworkError;
pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
// Try to find a single slash
if let Some(sep) = cidr.find('/') {
let (ip, prefix) = cidr.split_at(sep);
// Error if cidr has multiple slashes
if prefix[1..].find('/').is_some() {
Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {cidr}"
)))
} else {
// Handle the case when cidr has exactly one slash
Ok((ip, Some(&prefix[1..])))
}
} else {
// Handle the case when cidr does not have a slash
Ok((cidr, None))
}
}
pub fn parse_prefix(prefix: &str, max: u8) -> Result<u8, IpNetworkError> {
prefix
.parse()
.map_err(|_| IpNetworkError::InvalidPrefix)
.and_then(|mask| {
if mask > max {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(mask)
}
})
}

207
src/size.rs Normal file
View File

@ -0,0 +1,207 @@
use std::{
cmp::Ordering,
fmt::Display,
hash::{Hash, Hasher},
};
use crate::error::NetworkSizeError;
use NetworkSize::*;
/// Represents a generic network size.
///
/// IPv4 network sizes are represented as `u32` values, while IPv6 network sizes are represented as `u128` values.
///
/// # Comparisons
///
/// Network sizes are compared by _value_, not by type.
///
/// ```
/// use ipnetwork::NetworkSize;
///
/// let ns1 = NetworkSize::V4(100);
/// let ns2 = NetworkSize::V6(100);
///
/// assert_eq!(ns1, ns2);
/// ```
#[derive(Debug, Clone, Copy)]
pub enum NetworkSize {
V4(u32),
V6(u128),
}
impl NetworkSize {
/// Returns the size of the network as a `u128`
fn as_u128(&self) -> u128 {
match *self {
V4(a) => a as u128,
V6(a) => a,
}
}
}
impl From<u32> for NetworkSize {
fn from(value: u32) -> Self {
V4(value)
}
}
impl From<u128> for NetworkSize {
fn from(value: u128) -> Self {
V6(value)
}
}
impl TryFrom<NetworkSize> for u32 {
type Error = NetworkSizeError;
fn try_from(value: NetworkSize) -> Result<Self, Self::Error> {
match value {
V4(a) => Ok(a),
V6(_) => Err(NetworkSizeError::NetworkIsTooLarge),
}
}
}
impl From<NetworkSize> for u128 {
fn from(val: NetworkSize) -> Self {
val.as_u128()
}
}
impl PartialEq for NetworkSize {
fn eq(&self, other: &Self) -> bool {
let a = self.as_u128();
let b = other.as_u128();
a == b
}
}
impl Eq for NetworkSize {}
impl Hash for NetworkSize {
fn hash<H: Hasher>(&self, state: &mut H) {
let a = self.as_u128();
a.hash(state);
}
}
impl Ord for NetworkSize {
fn cmp(&self, other: &Self) -> Ordering {
let a = self.as_u128();
let b = other.as_u128();
a.cmp(&b)
}
}
impl PartialOrd for NetworkSize {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Display for NetworkSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_u128())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_u128() {
let value: u128 = 100;
let ns = NetworkSize::from(value);
assert_eq!(ns, V6(100));
}
#[test]
fn test_from_u32() {
let value: u32 = 100;
let ns = NetworkSize::from(value);
assert_eq!(ns, V4(100));
}
#[test]
fn test_try_into_u32() {
let value: u32 = 100;
let ns = V4(value);
let result: Result<u32, _> = ns.try_into();
assert!(result.is_ok());
assert_eq!(result.unwrap(), value);
}
#[test]
fn test_try_into_u32_error() {
let value: u128 = u32::MAX as u128 + 1;
let ns = V6(value);
let result: Result<u32, _> = ns.try_into();
assert!(result.is_err());
}
#[test]
fn test_into_u128() {
let value: u32 = 100;
let ns = V4(value);
let result: u128 = ns.into();
assert_eq!(result, value as u128);
}
#[test]
fn test_eq() {
let ns1 = V4(100);
let ns2 = V4(100);
assert_eq!(ns1, ns2);
let ns1 = V6(100);
let ns2 = V6(100);
assert_eq!(ns1, ns2);
let ns1 = V4(100);
let ns2 = V6(100);
assert_eq!(ns1, ns2);
}
#[test]
fn test_cmp() {
let ns1 = V4(100);
let ns2 = V4(200);
assert!(ns1 < ns2);
let ns1 = V6(200);
let ns2 = V6(100);
assert!(ns1 > ns2);
let ns1 = V4(100);
let ns2 = V6(200);
assert!(ns1 < ns2);
}
#[test]
fn test_display() {
let ns1 = V4(u32::MAX);
let ns2 = V6(ns1.into());
assert_eq!(ns1.to_string(), ns2.to_string());
}
// Verify that [`std::hash::Hash`] and [`std::cmp::PartialEq`] are consistent
#[test]
fn test_hash() {
let a = NetworkSize::V4(100);
let b = NetworkSize::V6(100);
// Calculate the hash of the two values
let mut hasher = std::hash::DefaultHasher::default();
a.hash(&mut hasher);
let hash_a = hasher.finish();
let mut hasher = std::hash::DefaultHasher::default();
b.hash(&mut hasher);
let hash_b = hasher.finish();
// a == b
assert_eq!(a, b);
// implies hash(a) == hash(b)
assert_eq!(hash_a, hash_b);
}
}

View File

@ -82,4 +82,12 @@ mod tests {
panic!("{}", s);
}
}
#[test]
fn test_ipnetwork_size_with_prefix_0() {
let network: Ipv4Network = "0.0.0.0/0".parse().unwrap();
let size = network.size();
assert_eq!(size, u32::MAX);
}
}