mirror of
https://github.com/achanda/ipnetwork.git
synced 2025-06-16 16:58:50 +00:00
Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
c3af07d69a | |||
35977adc81 | |||
0deb2abd8b | |||
170fc4ca6b | |||
aa65f088b1 | |||
5f929258ef | |||
a21f5df04e | |||
876f882523 | |||
49f07a4838 | |||
2f219a2ada | |||
9ae719637d | |||
c2812a91a8 | |||
e1d3ac6de1 | |||
551d1a62b1 | |||
285cf9d409 | |||
ea467bb744 | |||
209d5432a6 | |||
16095cb510 | |||
4f7d898a86 | |||
d665bb6beb | |||
ad46759a55 | |||
cbf8d6b29a | |||
7e0519cd35 | |||
83a62c9605 | |||
c840f27855 | |||
4b16661585 | |||
a0831a77b9 | |||
f0fd0521e1 | |||
bb51dee73a | |||
b5f5b79d23 | |||
70d1f74e29 | |||
068959e2c4 | |||
241b1dcdf8 | |||
6e32dd2e88 | |||
a1966d4089 | |||
817b5babed | |||
9bf407e634 | |||
1bd937e90c | |||
d19ac33071 | |||
19aafee3c8 | |||
a01d32ede8 | |||
b4c0554d11 | |||
3a2d1da053 | |||
98914649d6 | |||
0bb1ea8fd4 | |||
b624487f72 | |||
dff59ea0a5 | |||
432f7342e3 | |||
b15eb54695 | |||
ec79428c75 | |||
a96f30d64e | |||
ef3f536d48 | |||
a42e9d9593 | |||
2c9bf9b29b | |||
1b6904bfc7 | |||
ab6f1012b3 | |||
5146125c35 | |||
86dbbd997a | |||
372f9bdbb7 | |||
c0e04d534a | |||
7c9ff1bc30 |
63
.github/workflows/publish.yml
vendored
63
.github/workflows/publish.yml
vendored
@ -1,13 +1,54 @@
|
|||||||
|
name: Release-plz
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches:
|
||||||
- 'v.*.*'
|
- main
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
# Release unpublished packages.
|
||||||
toolchain: stable
|
release-plz-release:
|
||||||
override: true
|
name: Release-plz release
|
||||||
- uses: katyo/publish-crates@v1
|
runs-on: ubuntu-latest
|
||||||
with:
|
steps:
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
- 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
|
||||||
|
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 }}
|
||||||
|
8
.github/workflows/rust.yml
vendored
8
.github/workflows/rust.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Rust
|
name: Rust
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -12,9 +13,10 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
rust: [stable, beta, nightly]
|
rust: [stable, beta, nightly]
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
rust-version: ${{ matrix.rust }}
|
rust-version: ${{ matrix.rust }}
|
||||||
|
components: clippy, rustfmt
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
@ -23,6 +25,8 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose --all-features
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: cargo doc --verbose
|
run: cargo doc --verbose
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy --verbose --all-features
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
.gitignore
|
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
target
|
target
|
||||||
.vscode
|
.vscode
|
||||||
.idea/
|
.idea/
|
||||||
|
61
CHANGELOG.md
Normal file
61
CHANGELOG.md
Normal 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))
|
17
Cargo.toml
17
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ipnetwork"
|
name = "ipnetwork"
|
||||||
version = "0.19.0"
|
version = "0.21.1"
|
||||||
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
|
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
|
||||||
description = "A library to work with IP CIDRs in Rust"
|
description = "A library to work with IP CIDRs in Rust"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -9,21 +9,26 @@ keywords = ["network", "ip", "address", "cidr"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = ["network-programming", "parser-implementations"]
|
categories = ["network-programming", "parser-implementations"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.80.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1.0.200", optional = true }
|
||||||
|
schemars = { version = "0.8.17", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0.116"
|
||||||
serde_derive = "1"
|
criterion = {version = "0.5.1", features= ["html_reports"]}
|
||||||
criterion = {version = "0.3.4", features= ["html_reports"]}
|
does-it-json = "0.0.4"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "achanda/ipnetwork" }
|
travis-ci = { repository = "achanda/ipnetwork" }
|
||||||
maintenance = { status = "passively-maintained" }
|
maintenance = { status = "passively-maintained" }
|
||||||
|
|
||||||
[features]
|
[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]]
|
[[bench]]
|
||||||
name = "parse_bench"
|
name = "parse_bench"
|
||||||
|
6
renovate.json
Normal file
6
renovate.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
]
|
||||||
|
}
|
@ -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
56
src/error.rs
Normal 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 {}
|
210
src/ipv4.rs
210
src/ipv4.rs
@ -1,5 +1,6 @@
|
|||||||
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
|
use crate::error::IpNetworkError;
|
||||||
use std::{fmt, net::Ipv4Addr, str::FromStr, convert::TryFrom};
|
use crate::parse::{cidr_parts, parse_prefix};
|
||||||
|
use std::{convert::TryFrom, fmt, net::Ipv4Addr, str::FromStr};
|
||||||
|
|
||||||
const IPV4_BITS: u8 = 32;
|
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 {
|
impl Ipv4Network {
|
||||||
/// Constructs a new `Ipv4Network` from any `Ipv4Addr` and a prefix denoting the network size.
|
/// 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`.
|
/// If the prefix is larger than 32 this will return an `IpNetworkError::InvalidPrefix`.
|
||||||
pub const fn new(addr: Ipv4Addr, prefix: u8) -> Result<Ipv4Network, IpNetworkError> {
|
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 {
|
if prefix > IPV4_BITS {
|
||||||
Err(IpNetworkError::InvalidPrefix)
|
None
|
||||||
} else {
|
} else {
|
||||||
Ok(Ipv4Network { addr, prefix })
|
Some(Ipv4Network { addr, prefix })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,11 +140,11 @@ impl Ipv4Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ip(self) -> Ipv4Addr {
|
pub const fn ip(self) -> Ipv4Addr {
|
||||||
self.addr
|
self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefix(self) -> u8 {
|
pub const fn prefix(self) -> u8 {
|
||||||
self.prefix
|
self.prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +161,9 @@ impl Ipv4Network {
|
|||||||
/// Checks if the given `Ipv4Network` is partly contained in other.
|
/// Checks if the given `Ipv4Network` is partly contained in other.
|
||||||
pub fn overlaps(self, other: Ipv4Network) -> bool {
|
pub fn overlaps(self, other: Ipv4Network) -> bool {
|
||||||
other.contains(self.ip())
|
other.contains(self.ip())
|
||||||
|| (other.contains(self.broadcast())
|
|| other.contains(self.broadcast())
|
||||||
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
|
|| self.contains(other.ip())
|
||||||
|
|| self.contains(other.broadcast())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the mask for this `Ipv4Network`.
|
/// Returns the mask for this `Ipv4Network`.
|
||||||
@ -109,10 +180,13 @@ impl Ipv4Network {
|
|||||||
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
|
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
|
||||||
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
|
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn mask(self) -> Ipv4Addr {
|
pub const fn mask(&self) -> Ipv4Addr {
|
||||||
let prefix = self.prefix;
|
debug_assert!(self.prefix <= 32);
|
||||||
let mask = !(0xffff_ffff as u64 >> prefix) as u32;
|
if self.prefix == 0 {
|
||||||
Ipv4Addr::from(mask)
|
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`.
|
/// Returns the address of the network denoted by this `Ipv4Network`.
|
||||||
@ -127,10 +201,10 @@ impl Ipv4Network {
|
|||||||
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
|
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
|
||||||
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
|
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn network(self) -> Ipv4Addr {
|
pub const fn network(&self) -> Ipv4Addr {
|
||||||
let mask = u32::from(self.mask());
|
let mask = self.mask().to_bits();
|
||||||
let ip = u32::from(self.addr) & mask;
|
let ip = self.addr.to_bits() & mask;
|
||||||
Ipv4Addr::from(ip)
|
Ipv4Addr::from_bits(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the broadcasting address of this `Ipv4Network`.
|
/// Returns the broadcasting address of this `Ipv4Network`.
|
||||||
@ -145,10 +219,10 @@ impl Ipv4Network {
|
|||||||
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
||||||
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn broadcast(self) -> Ipv4Addr {
|
pub const fn broadcast(&self) -> Ipv4Addr {
|
||||||
let mask = u32::from(self.mask());
|
let mask = self.mask().to_bits();
|
||||||
let broadcast = u32::from(self.addr) | !mask;
|
let broadcast = self.addr.to_bits() | !mask;
|
||||||
Ipv4Addr::from(broadcast)
|
Ipv4Addr::from_bits(broadcast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a given `Ipv4Addr` is in this `Ipv4Network`
|
/// Checks if a given `Ipv4Addr` is in this `Ipv4Network`
|
||||||
@ -164,10 +238,12 @@ impl Ipv4Network {
|
|||||||
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
|
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(self, ip: Ipv4Addr) -> bool {
|
pub const fn contains(&self, ip: Ipv4Addr) -> bool {
|
||||||
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
|
debug_assert!(self.prefix <= IPV4_BITS);
|
||||||
let net = u32::from(self.addr) & mask;
|
|
||||||
(u32::from(ip) & mask) == net
|
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`.
|
/// Returns number of possible host addresses in this `Ipv4Network`.
|
||||||
@ -185,8 +261,11 @@ impl Ipv4Network {
|
|||||||
/// assert_eq!(tinynet.size(), 1);
|
/// assert_eq!(tinynet.size(), 1);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn size(self) -> u32 {
|
pub fn size(self) -> u32 {
|
||||||
let host_bits = u32::from(IPV4_BITS - self.prefix);
|
debug_assert!(self.prefix <= 32);
|
||||||
(2 as u32).pow(host_bits)
|
if self.prefix == 0 {
|
||||||
|
return u32::MAX;
|
||||||
|
}
|
||||||
|
1 << (IPV4_BITS - self.prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `n`:th address within this network.
|
/// Returns the `n`:th address within this network.
|
||||||
@ -239,8 +318,7 @@ impl FromStr for Ipv4Network {
|
|||||||
type Err = IpNetworkError;
|
type Err = IpNetworkError;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (addr_str, prefix_str) = cidr_parts(s)?;
|
let (addr_str, prefix_str) = cidr_parts(s)?;
|
||||||
let addr = Ipv4Addr::from_str(addr_str)
|
let addr = Ipv4Addr::from_str(addr_str)?;
|
||||||
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
|
|
||||||
let prefix = match prefix_str {
|
let prefix = match prefix_str {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
if let Ok(netmask) = Ipv4Addr::from_str(v) {
|
if let Ok(netmask) = Ipv4Addr::from_str(v) {
|
||||||
@ -290,6 +368,15 @@ impl Iterator for Ipv4NetworkIterator {
|
|||||||
};
|
};
|
||||||
Some(next.into())
|
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 {
|
impl IntoIterator for &'_ Ipv4Network {
|
||||||
@ -304,13 +391,24 @@ impl IntoIterator for &'_ Ipv4Network {
|
|||||||
///
|
///
|
||||||
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
||||||
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
|
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
|
||||||
let mask = u32::from(mask);
|
match ipv4_mask_to_prefix_checked(mask) {
|
||||||
|
Some(prefix) => Ok(prefix),
|
||||||
|
None => Err(IpNetworkError::InvalidPrefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `Ipv4Addr` network mask into a prefix.
|
||||||
|
///
|
||||||
|
/// If the mask is invalid this will return `None`. This is useful in const contexts where
|
||||||
|
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
|
||||||
|
pub const fn ipv4_mask_to_prefix_checked(mask: Ipv4Addr) -> Option<u8> {
|
||||||
|
let mask = mask.to_bits();
|
||||||
|
|
||||||
let prefix = (!mask).leading_zeros() as u8;
|
let prefix = (!mask).leading_zeros() as u8;
|
||||||
if (u64::from(mask) << prefix) & 0xffff_ffff != 0 {
|
if ((mask as u64) << prefix) & 0xffff_ffff != 0 {
|
||||||
Err(IpNetworkError::InvalidPrefix)
|
None
|
||||||
} else {
|
} else {
|
||||||
Ok(prefix)
|
Some(prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +431,18 @@ mod test {
|
|||||||
assert!(net.is_err());
|
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]
|
#[test]
|
||||||
fn parse_v4_24bit() {
|
fn parse_v4_24bit() {
|
||||||
let cidr: Ipv4Network = "127.1.0.0/24".parse().unwrap();
|
let cidr: Ipv4Network = "127.1.0.0/24".parse().unwrap();
|
||||||
@ -415,6 +525,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(dropping_copy_types)]
|
||||||
fn copy_compatibility_v4() {
|
fn copy_compatibility_v4() {
|
||||||
let net = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 16).unwrap();
|
let net = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 16).unwrap();
|
||||||
mem::drop(net);
|
mem::drop(net);
|
||||||
@ -474,12 +585,31 @@ mod test {
|
|||||||
fn iterator_v4_huge() {
|
fn iterator_v4_huge() {
|
||||||
let cidr: Ipv4Network = "0/0".parse().unwrap();
|
let cidr: Ipv4Network = "0/0".parse().unwrap();
|
||||||
let mut iter = cidr.iter();
|
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!(i as u32, u32::from(iter.next().unwrap()));
|
||||||
}
|
}
|
||||||
assert_eq!(None, iter.next());
|
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]
|
#[test]
|
||||||
fn v4_mask_to_prefix() {
|
fn v4_mask_to_prefix() {
|
||||||
let mask = Ipv4Addr::new(255, 255, 255, 128);
|
let mask = Ipv4Addr::new(255, 255, 255, 128);
|
||||||
@ -586,9 +716,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
src.is_subnet_of(dest),
|
src.is_subnet_of(dest),
|
||||||
*val,
|
*val,
|
||||||
"testing with {} and {}",
|
"testing with {src} and {dest}"
|
||||||
src,
|
|
||||||
dest
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -631,9 +759,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
src.is_supernet_of(dest),
|
src.is_supernet_of(dest),
|
||||||
*val,
|
*val,
|
||||||
"testing with {} and {}",
|
"testing with {src} and {dest}"
|
||||||
src,
|
|
||||||
dest
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,9 +771,9 @@ mod test {
|
|||||||
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
|
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
|
||||||
|
|
||||||
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
|
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
|
||||||
assert_eq!(skynet.overlaps(other), true);
|
assert!(skynet.overlaps(other));
|
||||||
assert_eq!(skynet.overlaps(other2), false);
|
assert!(!skynet.overlaps(other2));
|
||||||
assert_eq!(other2.overlaps(other3), true);
|
assert!(other2.overlaps(other3));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
346
src/ipv6.rs
346
src/ipv6.rs
@ -1,5 +1,6 @@
|
|||||||
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
|
use crate::error::IpNetworkError;
|
||||||
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr, convert::TryFrom};
|
use crate::parse::{cidr_parts, parse_prefix};
|
||||||
|
use std::{convert::TryFrom, fmt, net::Ipv6Addr, str::FromStr};
|
||||||
|
|
||||||
const IPV6_BITS: u8 = 128;
|
const IPV6_BITS: u8 = 128;
|
||||||
const IPV6_SEGMENT_BITS: u8 = 16;
|
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 {
|
impl Ipv6Network {
|
||||||
/// Constructs a new `Ipv6Network` from any `Ipv6Addr` and a prefix denoting the network size.
|
/// 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`.
|
/// If the prefix is larger than 128 this will return an `IpNetworkError::InvalidPrefix`.
|
||||||
pub const fn new(addr: Ipv6Addr, prefix: u8) -> Result<Ipv6Network, IpNetworkError> {
|
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 {
|
if prefix > IPV6_BITS {
|
||||||
Err(IpNetworkError::InvalidPrefix)
|
None
|
||||||
} else {
|
} 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
|
/// 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
|
/// `Ipv6Addr` in the given network. `None` will be returned when there are no more
|
||||||
/// addresses.
|
/// addresses.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
///
|
||||||
|
/// This can return up to 2^128 addresses, which will take a _long_ time to iterate over.
|
||||||
pub fn iter(&self) -> Ipv6NetworkIterator {
|
pub fn iter(&self) -> Ipv6NetworkIterator {
|
||||||
let dec = u128::from(self.addr);
|
let dec = u128::from(self.addr);
|
||||||
let max = u128::max_value();
|
let max = u128::MAX;
|
||||||
let prefix = self.prefix;
|
let prefix = self.prefix;
|
||||||
|
|
||||||
let mask = max.checked_shl(u32::from(IPV6_BITS - prefix)).unwrap_or(0);
|
let mask = max.checked_shl(u32::from(IPV6_BITS - prefix)).unwrap_or(0);
|
||||||
@ -76,47 +160,11 @@ impl Ipv6Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the address of the network denoted by this `Ipv6Network`.
|
pub const fn ip(&self) -> Ipv6Addr {
|
||||||
/// 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
|
self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefix(&self) -> u8 {
|
pub const fn prefix(&self) -> u8 {
|
||||||
self.prefix
|
self.prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,8 +181,9 @@ impl Ipv6Network {
|
|||||||
/// Checks if the given `Ipv6Network` is partly contained in other.
|
/// Checks if the given `Ipv6Network` is partly contained in other.
|
||||||
pub fn overlaps(self, other: Ipv6Network) -> bool {
|
pub fn overlaps(self, other: Ipv6Network) -> bool {
|
||||||
other.contains(self.ip())
|
other.contains(self.ip())
|
||||||
|| (other.contains(self.broadcast())
|
|| other.contains(self.broadcast())
|
||||||
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
|
|| self.contains(other.ip())
|
||||||
|
|| self.contains(other.broadcast())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the mask for this `Ipv6Network`.
|
/// Returns the mask for this `Ipv6Network`.
|
||||||
@ -151,15 +200,50 @@ impl Ipv6Network {
|
|||||||
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
|
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
|
||||||
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
|
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn mask(&self) -> Ipv6Addr {
|
pub const fn mask(&self) -> Ipv6Addr {
|
||||||
// Ipv6Addr::from is only implemented for [u8; 16]
|
debug_assert!(self.prefix <= IPV6_BITS);
|
||||||
let mut segments = [0; 16];
|
|
||||||
for (i, segment) in segments.iter_mut().enumerate() {
|
if self.prefix == 0 {
|
||||||
let bits_remaining = self.prefix.saturating_sub(i as u8 * 8);
|
return Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
let set_bits = cmp::min(bits_remaining, 8);
|
|
||||||
*segment = !(0xff as u16 >> set_bits) as u8;
|
|
||||||
}
|
}
|
||||||
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`
|
/// Checks if a given `Ipv6Addr` is in this `Ipv6Network`
|
||||||
@ -175,15 +259,11 @@ impl Ipv6Network {
|
|||||||
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
|
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self, ip: Ipv6Addr) -> bool {
|
pub const fn contains(&self, ip: Ipv6Addr) -> bool {
|
||||||
let a = self.addr.segments();
|
let ip = ip.to_bits();
|
||||||
let b = ip.segments();
|
let net = self.network().to_bits();
|
||||||
let addrs = Iterator::zip(a.iter(), b.iter());
|
let mask = self.mask().to_bits();
|
||||||
self.mask()
|
(ip & mask) == net
|
||||||
.segments()
|
|
||||||
.iter()
|
|
||||||
.zip(addrs)
|
|
||||||
.all(|(mask, (a, b))| a & mask == b & mask)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns number of possible host addresses in this `Ipv6Network`.
|
/// Returns number of possible host addresses in this `Ipv6Network`.
|
||||||
@ -201,8 +281,36 @@ impl Ipv6Network {
|
|||||||
/// assert_eq!(tinynet.size(), 1);
|
/// assert_eq!(tinynet.size(), 1);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn size(&self) -> u128 {
|
pub fn size(&self) -> u128 {
|
||||||
let host_bits = u32::from(IPV6_BITS - self.prefix);
|
debug_assert!(self.prefix <= IPV6_BITS);
|
||||||
(2 as u128).pow(host_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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,12 +331,8 @@ impl FromStr for Ipv6Network {
|
|||||||
type Err = IpNetworkError;
|
type Err = IpNetworkError;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (addr_str, prefix_str) = cidr_parts(s)?;
|
let (addr_str, prefix_str) = cidr_parts(s)?;
|
||||||
let addr = Ipv6Addr::from_str(addr_str)
|
let addr = Ipv6Addr::from_str(addr_str)?;
|
||||||
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
|
let prefix = parse_prefix(prefix_str.unwrap_or(&IPV6_BITS.to_string()), IPV6_BITS)?;
|
||||||
let prefix = match prefix_str {
|
|
||||||
Some(v) => parse_prefix(v, IPV6_BITS)?,
|
|
||||||
None => IPV6_BITS,
|
|
||||||
};
|
|
||||||
Ipv6Network::new(addr, prefix)
|
Ipv6Network::new(addr, prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,6 +372,15 @@ impl Iterator for Ipv6NetworkIterator {
|
|||||||
};
|
};
|
||||||
Some(next.into())
|
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 {
|
impl IntoIterator for &'_ Ipv6Network {
|
||||||
@ -287,12 +400,25 @@ impl fmt::Display for Ipv6Network {
|
|||||||
/// Converts a `Ipv6Addr` network mask into a prefix.
|
/// Converts a `Ipv6Addr` network mask into a prefix.
|
||||||
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
||||||
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
||||||
|
match ipv6_mask_to_prefix_checked(mask) {
|
||||||
|
Some(prefix) => Ok(prefix),
|
||||||
|
None => Err(IpNetworkError::InvalidPrefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `Ipv6Addr` network mask into a prefix.
|
||||||
|
///
|
||||||
|
/// If the mask is invalid this will return `None`. This is useful in const contexts where
|
||||||
|
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
|
||||||
|
pub const fn ipv6_mask_to_prefix_checked(mask: Ipv6Addr) -> Option<u8> {
|
||||||
let mask = mask.segments();
|
let mask = mask.segments();
|
||||||
let mut mask_iter = mask.iter();
|
|
||||||
|
|
||||||
// Count the number of set bits from the start of the address
|
// Count the number of set bits from the start of the address
|
||||||
let mut prefix = 0;
|
let mut prefix = 0;
|
||||||
for &segment in &mut mask_iter {
|
let mut i = 0;
|
||||||
|
while i < mask.len() {
|
||||||
|
let segment = mask[i];
|
||||||
|
i += 1;
|
||||||
if segment == 0xffff {
|
if segment == 0xffff {
|
||||||
prefix += IPV6_SEGMENT_BITS;
|
prefix += IPV6_SEGMENT_BITS;
|
||||||
} else if segment == 0 {
|
} else if segment == 0 {
|
||||||
@ -302,7 +428,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
|||||||
let prefix_bits = (!segment).leading_zeros() as u8;
|
let prefix_bits = (!segment).leading_zeros() as u8;
|
||||||
// Check that the remainder of the bits are all unset
|
// Check that the remainder of the bits are all unset
|
||||||
if segment << prefix_bits != 0 {
|
if segment << prefix_bits != 0 {
|
||||||
return Err(IpNetworkError::InvalidPrefix);
|
return None;
|
||||||
}
|
}
|
||||||
prefix += prefix_bits;
|
prefix += prefix_bits;
|
||||||
break;
|
break;
|
||||||
@ -310,13 +436,15 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now check all the remaining bits are unset
|
// Now check all the remaining bits are unset
|
||||||
for &segment in mask_iter {
|
while i < mask.len() {
|
||||||
|
let segment = mask[i];
|
||||||
|
i += 1;
|
||||||
if segment != 0 {
|
if segment != 0 {
|
||||||
return Err(IpNetworkError::InvalidPrefix);
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(prefix)
|
Some(prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -345,6 +473,18 @@ mod test {
|
|||||||
assert!(cidr.is_err());
|
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]
|
#[test]
|
||||||
fn parse_v6() {
|
fn parse_v6() {
|
||||||
let cidr: Ipv6Network = "::1/0".parse().unwrap();
|
let cidr: Ipv6Network = "::1/0".parse().unwrap();
|
||||||
@ -481,6 +621,18 @@ mod test {
|
|||||||
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2), iter.next().unwrap());
|
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]
|
#[test]
|
||||||
fn network_v6() {
|
fn network_v6() {
|
||||||
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
|
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
|
||||||
@ -561,9 +713,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
src.is_subnet_of(dest),
|
src.is_subnet_of(dest),
|
||||||
*val,
|
*val,
|
||||||
"testing with {} and {}",
|
"testing with {src} and {dest}"
|
||||||
src,
|
|
||||||
dest
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -606,9 +756,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
src.is_supernet_of(dest),
|
src.is_supernet_of(dest),
|
||||||
*val,
|
*val,
|
||||||
"testing with {} and {}",
|
"testing with {src} and {dest}"
|
||||||
src,
|
|
||||||
dest
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -618,7 +766,7 @@ mod test {
|
|||||||
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
|
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
|
||||||
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/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]
|
#[test]
|
||||||
@ -633,4 +781,36 @@ mod test {
|
|||||||
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
|
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
|
||||||
assert_eq!(256, high_addrs.len());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
142
src/lib.rs
142
src/lib.rs
@ -1,24 +1,32 @@
|
|||||||
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
|
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
|
||||||
//! Rust.
|
//! Rust.
|
||||||
#![crate_type = "lib"]
|
#![crate_type = "lib"]
|
||||||
|
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
unsafe_code,
|
unsafe_code,
|
||||||
unused_extern_crates,
|
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, Ipv4Addr, Ipv6Addr},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
mod common;
|
mod error;
|
||||||
mod ipv4;
|
mod ipv4;
|
||||||
mod ipv6;
|
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::Ipv4NetworkIterator;
|
||||||
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
|
pub use crate::ipv4::{ipv4_mask_to_prefix, ipv4_mask_to_prefix_checked, Ipv4Network};
|
||||||
pub use crate::ipv6::Ipv6NetworkIterator;
|
pub use crate::ipv6::Ipv6NetworkIterator;
|
||||||
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
|
pub use crate::ipv6::{ipv6_mask_to_prefix, ipv6_mask_to_prefix_checked, Ipv6Network};
|
||||||
|
pub use crate::size::NetworkSize;
|
||||||
|
|
||||||
/// Represents a generic network range. This type can have two variants:
|
/// Represents a generic network range. This type can have two variants:
|
||||||
/// the v4 and the v6 case.
|
/// the v4 and the v6 case.
|
||||||
@ -49,11 +57,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
|
#[cfg(feature = "schemars")]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
impl schemars::JsonSchema for IpNetwork {
|
||||||
pub enum NetworkSize {
|
fn schema_name() -> String {
|
||||||
V4(u32),
|
"IpNetwork".to_string()
|
||||||
V6(u128),
|
}
|
||||||
|
|
||||||
|
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 {
|
impl IpNetwork {
|
||||||
@ -77,7 +146,7 @@ impl IpNetwork {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the IP part of a given `IpNetwork`
|
/// Returns the IP part of a given `IpNetwork`
|
||||||
pub fn ip(&self) -> IpAddr {
|
pub const fn ip(&self) -> IpAddr {
|
||||||
match *self {
|
match *self {
|
||||||
IpNetwork::V4(ref a) => IpAddr::V4(a.ip()),
|
IpNetwork::V4(ref a) => IpAddr::V4(a.ip()),
|
||||||
IpNetwork::V6(ref a) => IpAddr::V6(a.ip()),
|
IpNetwork::V6(ref a) => IpAddr::V6(a.ip()),
|
||||||
@ -96,7 +165,7 @@ impl IpNetwork {
|
|||||||
/// assert_eq!(IpNetwork::V6("ff01::0".parse().unwrap()).prefix(), 128u8);
|
/// assert_eq!(IpNetwork::V6("ff01::0".parse().unwrap()).prefix(), 128u8);
|
||||||
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).prefix(), 32u8);
|
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).prefix(), 32u8);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn prefix(&self) -> u8 {
|
pub const fn prefix(&self) -> u8 {
|
||||||
match *self {
|
match *self {
|
||||||
IpNetwork::V4(ref a) => a.prefix(),
|
IpNetwork::V4(ref a) => a.prefix(),
|
||||||
IpNetwork::V6(ref a) => a.prefix(),
|
IpNetwork::V6(ref a) => a.prefix(),
|
||||||
@ -117,7 +186,7 @@ impl IpNetwork {
|
|||||||
/// let net: IpNetwork = "2001:db8::/96".parse().unwrap();
|
/// let net: IpNetwork = "2001:db8::/96".parse().unwrap();
|
||||||
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
|
/// 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 {
|
match *self {
|
||||||
IpNetwork::V4(ref a) => IpAddr::V4(a.network()),
|
IpNetwork::V4(ref a) => IpAddr::V4(a.network()),
|
||||||
IpNetwork::V6(ref a) => IpAddr::V6(a.network()),
|
IpNetwork::V6(ref a) => IpAddr::V6(a.network()),
|
||||||
@ -136,7 +205,7 @@ impl IpNetwork {
|
|||||||
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
||||||
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn broadcast(&self) -> IpAddr {
|
pub const fn broadcast(&self) -> IpAddr {
|
||||||
match *self {
|
match *self {
|
||||||
IpNetwork::V4(ref a) => IpAddr::V4(a.broadcast()),
|
IpNetwork::V4(ref a) => IpAddr::V4(a.broadcast()),
|
||||||
IpNetwork::V6(ref a) => IpAddr::V6(a.broadcast()),
|
IpNetwork::V6(ref a) => IpAddr::V6(a.broadcast()),
|
||||||
@ -162,7 +231,7 @@ impl IpNetwork {
|
|||||||
/// let v6_net: IpNetwork = "ff01::0/32".parse().unwrap();
|
/// let v6_net: IpNetwork = "ff01::0/32".parse().unwrap();
|
||||||
/// assert_eq!(v6_net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
|
/// 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 {
|
match *self {
|
||||||
IpNetwork::V4(ref a) => IpAddr::V4(a.mask()),
|
IpNetwork::V4(ref a) => IpAddr::V4(a.mask()),
|
||||||
IpNetwork::V6(ref a) => IpAddr::V6(a.mask()),
|
IpNetwork::V6(ref a) => IpAddr::V6(a.mask()),
|
||||||
@ -181,7 +250,7 @@ impl IpNetwork {
|
|||||||
/// assert_eq!(v4.is_ipv4(), true);
|
/// assert_eq!(v4.is_ipv4(), true);
|
||||||
/// assert_eq!(v4.is_ipv6(), false);
|
/// assert_eq!(v4.is_ipv6(), false);
|
||||||
///```
|
///```
|
||||||
pub fn is_ipv4(&self) -> bool {
|
pub const fn is_ipv4(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
IpNetwork::V4(_) => true,
|
IpNetwork::V4(_) => true,
|
||||||
IpNetwork::V6(_) => false,
|
IpNetwork::V6(_) => false,
|
||||||
@ -200,7 +269,7 @@ impl IpNetwork {
|
|||||||
/// assert_eq!(v6.is_ipv6(), true);
|
/// assert_eq!(v6.is_ipv6(), true);
|
||||||
/// assert_eq!(v6.is_ipv4(), false);
|
/// assert_eq!(v6.is_ipv4(), false);
|
||||||
///```
|
///```
|
||||||
pub fn is_ipv6(&self) -> bool {
|
pub const fn is_ipv6(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
IpNetwork::V4(_) => false,
|
IpNetwork::V4(_) => false,
|
||||||
IpNetwork::V6(_) => true,
|
IpNetwork::V6(_) => true,
|
||||||
@ -228,7 +297,7 @@ impl IpNetwork {
|
|||||||
/// assert!(!net.contains(ip4));
|
/// assert!(!net.contains(ip4));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self, ip: IpAddr) -> bool {
|
pub const fn contains(&self, ip: IpAddr) -> bool {
|
||||||
match (*self, ip) {
|
match (*self, ip) {
|
||||||
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
|
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
|
||||||
(IpNetwork::V6(net), IpAddr::V6(ip)) => net.contains(ip),
|
(IpNetwork::V6(net), IpAddr::V6(ip)) => net.contains(ip),
|
||||||
@ -313,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 {
|
impl From<IpAddr> for IpNetwork {
|
||||||
fn from(addr: IpAddr) -> IpNetwork {
|
fn from(addr: IpAddr) -> IpNetwork {
|
||||||
match addr {
|
match addr {
|
||||||
IpAddr::V4(a) => IpNetwork::V4(Ipv4Network::from(a)),
|
IpAddr::V4(a) => IpNetwork::from(a),
|
||||||
IpAddr::V6(a) => IpNetwork::V6(Ipv6Network::from(a)),
|
IpAddr::V6(a) => IpNetwork::from(a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,6 +431,12 @@ impl Iterator for IpNetworkIterator {
|
|||||||
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
|
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 {
|
impl IntoIterator for &'_ IpNetwork {
|
||||||
@ -369,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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
|
33
src/parse.rs
Normal file
33
src/parse.rs
Normal 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
207
src/size.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
|
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -11,6 +11,7 @@ mod tests {
|
|||||||
let json_string = r#"{"ipnetwork":"127.1.0.0/24"}"#;
|
let json_string = r#"{"ipnetwork":"127.1.0.0/24"}"#;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
ipnetwork: Ipv4Network,
|
ipnetwork: Ipv4Network,
|
||||||
}
|
}
|
||||||
@ -21,6 +22,11 @@ mod tests {
|
|||||||
assert_eq!(mystruct.ipnetwork.prefix(), 24);
|
assert_eq!(mystruct.ipnetwork.prefix(), 24);
|
||||||
|
|
||||||
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
|
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]
|
#[test]
|
||||||
@ -28,6 +34,7 @@ mod tests {
|
|||||||
let json_string = r#"{"ipnetwork":"::1/0"}"#;
|
let json_string = r#"{"ipnetwork":"::1/0"}"#;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
ipnetwork: Ipv6Network,
|
ipnetwork: Ipv6Network,
|
||||||
}
|
}
|
||||||
@ -41,6 +48,11 @@ mod tests {
|
|||||||
assert_eq!(mystruct.ipnetwork.prefix(), 0);
|
assert_eq!(mystruct.ipnetwork.prefix(), 0);
|
||||||
|
|
||||||
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
|
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]
|
#[test]
|
||||||
@ -48,6 +60,7 @@ mod tests {
|
|||||||
let json_string = r#"{"ipnetwork":["127.1.0.0/24","::1/0"]}"#;
|
let json_string = r#"{"ipnetwork":["127.1.0.0/24","::1/0"]}"#;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
ipnetwork: Vec<IpNetwork>,
|
ipnetwork: Vec<IpNetwork>,
|
||||||
}
|
}
|
||||||
@ -63,5 +76,18 @@ mod tests {
|
|||||||
assert_eq!(mystruct.ipnetwork[1].prefix(), 0);
|
assert_eq!(mystruct.ipnetwork[1].prefix(), 0);
|
||||||
|
|
||||||
assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user