48 Commits

Author SHA1 Message Date
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
1b6904bfc7 Cut a new release for v0.20.0 (#158) 2022-06-30 22:00:50 -05:00
ab6f1012b3 Correct and validate JsonSchema for Ipv4Network, Ipv6Network, and IpNetwork (#157)
* show failed test cases

* add proper JSON schema
2022-06-28 21:27:13 -05:00
5146125c35 Merge pull request #155 from sancho20021/153-JsonSchema-for-IPNetwork
[#153] Add optional JSONSchema implementation for IpNetwork
2022-05-31 18:47:26 -05:00
86dbbd997a [#153] Add optional JSONSchema implementation for IpNetwork
Problem: No instance of `JSONSchema` is implemented for
`IpNetwork`.

Solution: Derive implementation via `schemars`. Add corresponding
feature to the crate.
2022-05-31 23:37:44 +03:00
372f9bdbb7 Add workflow_dispatch 2022-04-13 23:11:42 -05:00
c0e04d534a Merge pull request #151 from achanda/update-publish-job
Update publish.yml
2022-04-13 23:04:52 -05:00
7c9ff1bc30 Update publish.yml 2022-04-13 23:04:38 -05:00
14 changed files with 839 additions and 195 deletions

View File

@ -1,13 +1,54 @@
name: Release-plz
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
push:
tags:
- 'v.*.*'
branches:
- main
jobs:
# 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 }}
@ -23,6 +25,8 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
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

55
CHANGELOG.md Normal file
View File

@ -0,0 +1,55 @@
# 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.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.19.0"
version = "0.21.0"
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"
@ -11,19 +11,23 @@ categories = ["network-programming", "parser-implementations"]
edition = "2021"
[dependencies]
serde = { version = "1", optional = true }
serde = { version = "1.0.200", optional = true }
schemars = { version = "0.8.17", optional = true }
[dev-dependencies]
serde_json = "1.0"
serde_derive = "1"
criterion = {version = "0.3.4", features= ["html_reports"]}
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,5 +1,6 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{fmt, net::Ipv4Addr, str::FromStr, convert::TryFrom};
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;
@ -31,15 +32,84 @@ impl serde::Serialize for Ipv4Network {
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Ipv4Network {
fn schema_name() -> String {
"Ipv4Network".to_string()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(
concat!(
r#"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"#,
r#"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"#,
r#"\/(3[0-2]|[0-2]?[0-9])$"#,
)
.to_string(),
),
..Default::default()
})),
extensions: [("x-rust-type".to_string(), "ipnetwork::Ipv4Network".into())]
.iter()
.cloned()
.collect(),
..Default::default()
}
.into()
}
}
impl Ipv4Network {
/// Constructs a new `Ipv4Network` from any `Ipv4Addr` and a prefix denoting the network size.
///
/// 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 })
}
}
@ -91,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`.
@ -109,9 +180,12 @@ 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;
pub 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)
}
@ -127,7 +201,7 @@ 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 {
pub fn network(&self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let ip = u32::from(self.addr) & mask;
Ipv4Addr::from(ip)
@ -145,7 +219,7 @@ 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 {
pub fn broadcast(&self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let broadcast = u32::from(self.addr) | !mask;
Ipv4Addr::from(broadcast)
@ -164,8 +238,10 @@ 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;
pub 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
}
@ -185,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.
@ -239,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) {
@ -333,6 +411,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();
@ -415,6 +505,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);
@ -474,7 +565,7 @@ 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());
@ -586,9 +677,7 @@ mod test {
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -631,9 +720,7 @@ mod test {
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -645,9 +732,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, fmt, net::Ipv6Addr, str::FromStr, convert::TryFrom};
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;
@ -32,15 +33,94 @@ impl serde::Serialize for Ipv6Network {
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Ipv6Network {
fn schema_name() -> String {
"Ipv6Network".to_string()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(
concat!(
r#"^("#,
r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}"#,
r#"|([0-9a-fA-F]{1,4}:){1,7}:"#,
r#"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}"#,
r#"|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}"#,
r#"|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}"#,
r#"|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}"#,
r#"|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"#,
r#"|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})"#,
r#"|:((:[0-9a-fA-F]{1,4}){1,7}|:)"#,
r#"|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}"#,
r#"|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#,
r#"|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#,
r#"")[/](12[0-8]|1[0-1][0-9]|[0-9]?[0-9])$"#,
).to_string(),
),
..Default::default()
})),
extensions: [("x-rust-type".to_string(), "ipnetwork::Ipv6Network".into())]
.iter()
.cloned()
.collect(),
..Default::default()
}
.into()
}
}
impl Ipv6Network {
/// Constructs a new `Ipv6Network` from any `Ipv6Addr` and a prefix denoting the network size.
///
/// 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 })
}
}
@ -59,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);
@ -76,42 +160,6 @@ 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 {
self.addr
}
@ -133,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`.
@ -152,14 +201,46 @@ impl Ipv6Network {
/// 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;
debug_assert!(self.prefix <= IPV6_BITS);
let mask = u128::MAX << (IPV6_BITS - self.prefix);
Ipv6Addr::from(mask)
}
Ipv6Addr::from(segments)
/// 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 network = u128::from(self.addr) & mask;
Ipv6Addr::from(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 fn broadcast(&self) -> Ipv6Addr {
let mask = u128::from(self.mask());
let broadcast = u128::from(self.addr) | !mask;
Ipv6Addr::from(broadcast)
}
/// Checks if a given `Ipv6Addr` is in this `Ipv6Network`
@ -176,14 +257,10 @@ impl Ipv6Network {
/// ```
#[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)
let ip = u128::from(ip);
let net = u128::from(self.network());
let mask = u128::from(self.mask());
(ip & mask) == net
}
/// Returns number of possible host addresses in this `Ipv6Network`.
@ -201,8 +278,32 @@ 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);
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
}
}
}
@ -223,12 +324,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)
}
}
@ -345,6 +442,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();
@ -561,9 +670,7 @@ mod test {
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -606,9 +713,7 @@ mod test {
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
"testing with {src} and {dest}"
);
}
}
@ -618,7 +723,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]
@ -633,4 +738,23 @@ 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);
}
}

View File

@ -1,24 +1,27 @@
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
//! Rust.
#![crate_type = "lib"]
#![deny(
missing_debug_implementations,
unsafe_code,
unused_extern_crates,
unused_import_braces)]
unused_import_braces
)]
use std::{fmt, net::IpAddr, str::FromStr, convert::TryFrom};
use std::{convert::TryFrom, fmt, net::IpAddr, str::FromStr};
mod common;
mod error;
mod ipv4;
mod ipv6;
mod parse;
mod size;
pub use crate::common::IpNetworkError;
pub use crate::error::{NetworkSizeError, IpNetworkError};
pub use crate::ipv4::Ipv4NetworkIterator;
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
pub use crate::ipv6::Ipv6NetworkIterator;
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
pub use crate::size::NetworkSize;
/// Represents a generic network range. This type can have two variants:
/// the v4 and the v6 case.
@ -49,11 +52,72 @@ impl serde::Serialize 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),
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for IpNetwork {
fn schema_name() -> String {
"IpNetwork".to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(
schemars::schema::Metadata {
..Default::default()
}
.into(),
),
subschemas: Some(
schemars::schema::SubschemaValidation {
one_of: Some(vec![
schemars::schema::SchemaObject {
metadata: Some(
schemars::schema::Metadata {
title: Some("v4".to_string()),
..Default::default()
}
.into(),
),
subschemas: Some(
schemars::schema::SubschemaValidation {
all_of: Some(vec![gen.subschema_for::<Ipv4Network>()]),
..Default::default()
}
.into(),
),
..Default::default()
}
.into(),
schemars::schema::SchemaObject {
metadata: Some(
schemars::schema::Metadata {
title: Some("v6".to_string()),
..Default::default()
}
.into(),
),
subschemas: Some(
schemars::schema::SubschemaValidation {
all_of: Some(vec![gen.subschema_for::<Ipv6Network>()]),
..Default::default()
}
.into(),
),
..Default::default()
}
.into(),
]),
..Default::default()
}
.into(),
),
extensions: [("x-rust-type".to_string(), "ipnetwork::IpNetwork".into())]
.iter()
.cloned()
.collect(),
..Default::default()
}
.into()
}
}
impl IpNetwork {

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

@ -3,7 +3,7 @@
#[cfg(test)]
mod tests {
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
@ -11,6 +11,7 @@ mod tests {
let json_string = r#"{"ipnetwork":"127.1.0.0/24"}"#;
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
struct MyStruct {
ipnetwork: Ipv4Network,
}
@ -21,6 +22,11 @@ mod tests {
assert_eq!(mystruct.ipnetwork.prefix(), 24);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
#[cfg(feature = "schemars")]
if let Err(s) = does_it_json::validate_with_output(&mystruct) {
panic!("{}", s);
}
}
#[test]
@ -28,6 +34,7 @@ mod tests {
let json_string = r#"{"ipnetwork":"::1/0"}"#;
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
struct MyStruct {
ipnetwork: Ipv6Network,
}
@ -41,6 +48,11 @@ mod tests {
assert_eq!(mystruct.ipnetwork.prefix(), 0);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
#[cfg(feature = "schemars")]
if let Err(s) = does_it_json::validate_with_output(&mystruct) {
panic!("{}", s);
}
}
#[test]
@ -48,6 +60,7 @@ mod tests {
let json_string = r#"{"ipnetwork":["127.1.0.0/24","::1/0"]}"#;
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
struct MyStruct {
ipnetwork: Vec<IpNetwork>,
}
@ -63,5 +76,18 @@ mod tests {
assert_eq!(mystruct.ipnetwork[1].prefix(), 0);
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
#[cfg(feature = "schemars")]
if let Err(s) = does_it_json::validate_with_output(&mystruct) {
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);
}
}