mirror of
https://github.com/achanda/ipnetwork.git
synced 2025-06-15 16:43:00 +00:00
Compare commits
104 Commits
v0.16.0
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
a21cb4d081 | |||
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 | |||
6d4e4ba47a | |||
e842e0edc0 | |||
61550eda45 | |||
3503e4c094 | |||
91528476a9 | |||
501e5533d7 | |||
7120b93837 | |||
a57afd9e1a | |||
8ad43541cd | |||
9651b3b721 | |||
5764406000 | |||
4cbd1f2424 | |||
772db6c569 | |||
c9b2bc5519 | |||
fe9f1ba8d5 | |||
9921661ebe | |||
14108098d6 | |||
1f3e10f73d | |||
c26c836b39 | |||
8fcff82c7b | |||
46cc64d424 | |||
e15e8cb812 | |||
0b98eecf6a | |||
6c6bdb333d | |||
eded5f13a9 | |||
1d4665dfe6 | |||
e80eab0c80 | |||
8f1c6b59bb | |||
a9f4547c82 | |||
fa128680b5 | |||
620ec4d42c | |||
b6397ca2bc | |||
c0257fc59d | |||
45fbf458b3 | |||
ca1d332e71 | |||
121820add8 | |||
65bd6724c2 | |||
41e0f8a8a3 | |||
f23ea4d7d6 | |||
fc958f16dc | |||
23b95c3b85 | |||
38460c0598 |
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
54
.github/workflows/publish.yml
vendored
Normal file
54
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Release-plz
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
# Release unpublished packages.
|
||||
release-plz-release:
|
||||
name: Release-plz release
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
.gitignore
|
||||
Cargo.lock
|
||||
target
|
||||
.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))
|
23
Cargo.toml
23
Cargo.toml
@ -1,31 +1,34 @@
|
||||
[package]
|
||||
name = "ipnetwork"
|
||||
version = "0.16.0" # When updating version, also modify html_root_url in the lib.rs
|
||||
version = "0.21.1"
|
||||
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
|
||||
description = "A library to work with IP CIDRs in Rust"
|
||||
license = "Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/achanda/ipnetwork"
|
||||
keywords = ["network", "ip", "address", "cidr"]
|
||||
readme = "README.md"
|
||||
categories = ["network-programming", "parser-implementations"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.80.0"
|
||||
|
||||
[dependencies]
|
||||
clippy = {version = "0.0.302", optional = true}
|
||||
serde = { version = ">=0.8.0, <2.0", optional = true }
|
||||
serde = { version = "1.0.200", optional = true }
|
||||
schemars = { version = "0.9.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0"
|
||||
serde_derive = ">=0.8.0, <2.0"
|
||||
criterion = "0.3.0"
|
||||
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"]
|
||||
dev = ["clippy"]
|
||||
# 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"
|
||||
|
25
LICENSE-MIT.md
Normal file
25
LICENSE-MIT.md
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright 2020 Developers of the ipnetwork project
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
@ -1,14 +1,14 @@
|
||||
ipnetwork
|
||||
===
|
||||
This is a library to work with IPv4 and v6 CIDRs in rust
|
||||
The IPv4 implementation is stable, IPv6 implementation is not done yet.
|
||||
This is a library to work with IPv4 and IPv6 CIDRs in Rust
|
||||
|
||||
[](https://travis-ci.org/achanda/ipnetwork)
|
||||
[](https://crates.io/crates/ipnetwork)
|
||||
|
||||
Run Clippy by doing
|
||||
```
|
||||
cargo test --features "dev"
|
||||
rustup component add clippy
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
### Installation
|
||||
@ -20,7 +20,7 @@ cargo test
|
||||
```
|
||||
|
||||
You can also add `ipnetwork` as a dependency to your project's `Cargo.toml`:
|
||||
```
|
||||
```toml
|
||||
[dependencies]
|
||||
ipnetwork = "*"
|
||||
```
|
||||
|
@ -21,18 +21,18 @@ fn parse_ipv4_netmask_benchmark(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn contains_ipv4_benchmark(c: &mut Criterion) {
|
||||
let cidr = "74.125.227.0/25".parse::<Ipv4Network>().unwrap();
|
||||
c.bench_function("contains ipv4", |b| {
|
||||
b.iter(|| {
|
||||
let cidr = "74.125.227.0/25".parse::<Ipv4Network>().unwrap();
|
||||
cidr.contains(Ipv4Addr::new(74, 125, 227, 4))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contains_ipv6_benchmark(c: &mut Criterion) {
|
||||
let cidr = "FF01:0:0:17:0:0:0:2/65".parse::<Ipv6Network>().unwrap();
|
||||
c.bench_function("contains ipv6", |b| {
|
||||
b.iter(|| {
|
||||
let cidr = "FF01:0:0:17:0:0:0:2/65".parse::<Ipv6Network>().unwrap();
|
||||
cidr.contains(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0x7fff, 0, 0, 0x2))
|
||||
})
|
||||
});
|
||||
|
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() {
|
||||
return Err(IpNetworkError::InvalidCidrFormat(format!(
|
||||
"CIDR must contain a single '/': {}",
|
||||
cidr
|
||||
)));
|
||||
} else {
|
||||
// Handle the case when cidr has exactly one slash
|
||||
return Ok((ip, Some(&prefix[1..])));
|
||||
}
|
||||
} else {
|
||||
// Handle the case when cidr does not have a slash
|
||||
return 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 {}
|
231
src/ipv4.rs
231
src/ipv4.rs
@ -1,5 +1,6 @@
|
||||
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
|
||||
use std::{fmt, net::Ipv4Addr, str::FromStr};
|
||||
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;
|
||||
|
||||
@ -27,7 +28,37 @@ impl serde::Serialize for Ipv4Network {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,11 +66,50 @@ 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 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 {
|
||||
Err(IpNetworkError::InvalidPrefix)
|
||||
None
|
||||
} else {
|
||||
Ok(Ipv4Network { addr, prefix })
|
||||
Some(Ipv4Network { addr, prefix })
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +131,7 @@ impl Ipv4Network {
|
||||
/// Returns an iterator over `Ipv4Network`. Each call to `next` will return the next
|
||||
/// `Ipv4Addr` in the given network. `None` will be returned when there are no more
|
||||
/// addresses.
|
||||
pub fn iter(&self) -> Ipv4NetworkIterator {
|
||||
pub fn iter(self) -> Ipv4NetworkIterator {
|
||||
let start = u32::from(self.network());
|
||||
let end = start + (self.size() - 1);
|
||||
Ipv4NetworkIterator {
|
||||
@ -70,11 +140,11 @@ impl Ipv4Network {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ip(&self) -> Ipv4Addr {
|
||||
pub const fn ip(self) -> Ipv4Addr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> u8 {
|
||||
pub const fn prefix(self) -> u8 {
|
||||
self.prefix
|
||||
}
|
||||
|
||||
@ -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,10 +180,13 @@ impl Ipv4Network {
|
||||
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
|
||||
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
|
||||
/// ```
|
||||
pub fn mask(&self) -> Ipv4Addr {
|
||||
let prefix = self.prefix;
|
||||
let mask = !(0xffff_ffff as u64 >> prefix) as u32;
|
||||
Ipv4Addr::from(mask)
|
||||
pub const fn mask(&self) -> Ipv4Addr {
|
||||
debug_assert!(self.prefix <= 32);
|
||||
if self.prefix == 0 {
|
||||
return Ipv4Addr::new(0, 0, 0, 0);
|
||||
}
|
||||
let mask = u32::MAX << (IPV4_BITS - self.prefix);
|
||||
Ipv4Addr::from_bits(mask)
|
||||
}
|
||||
|
||||
/// Returns the address of the network denoted by this `Ipv4Network`.
|
||||
@ -127,10 +201,10 @@ impl Ipv4Network {
|
||||
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
|
||||
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
|
||||
/// ```
|
||||
pub fn network(&self) -> Ipv4Addr {
|
||||
let mask = u32::from(self.mask());
|
||||
let ip = u32::from(self.addr) & mask;
|
||||
Ipv4Addr::from(ip)
|
||||
pub const fn network(&self) -> Ipv4Addr {
|
||||
let mask = self.mask().to_bits();
|
||||
let ip = self.addr.to_bits() & mask;
|
||||
Ipv4Addr::from_bits(ip)
|
||||
}
|
||||
|
||||
/// Returns the broadcasting address of this `Ipv4Network`.
|
||||
@ -145,10 +219,10 @@ impl Ipv4Network {
|
||||
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
||||
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
||||
/// ```
|
||||
pub fn broadcast(&self) -> Ipv4Addr {
|
||||
let mask = u32::from(self.mask());
|
||||
let broadcast = u32::from(self.addr) | !mask;
|
||||
Ipv4Addr::from(broadcast)
|
||||
pub const fn broadcast(&self) -> Ipv4Addr {
|
||||
let mask = self.mask().to_bits();
|
||||
let broadcast = self.addr.to_bits() | !mask;
|
||||
Ipv4Addr::from_bits(broadcast)
|
||||
}
|
||||
|
||||
/// Checks if a given `Ipv4Addr` is in this `Ipv4Network`
|
||||
@ -163,10 +237,13 @@ impl Ipv4Network {
|
||||
/// assert!(net.contains(Ipv4Addr::new(127, 0, 0, 70)));
|
||||
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
|
||||
/// ```
|
||||
pub fn contains(&self, ip: Ipv4Addr) -> bool {
|
||||
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
|
||||
let net = u32::from(self.addr) & mask;
|
||||
(u32::from(ip) & mask) == net
|
||||
#[inline]
|
||||
pub const fn contains(&self, ip: Ipv4Addr) -> bool {
|
||||
debug_assert!(self.prefix <= IPV4_BITS);
|
||||
|
||||
let mask = !(0xffff_ffff_u64 >> self.prefix) as u32;
|
||||
let net = self.addr.to_bits() & mask;
|
||||
(ip.to_bits() & mask) == net
|
||||
}
|
||||
|
||||
/// Returns number of possible host addresses in this `Ipv4Network`.
|
||||
@ -183,9 +260,12 @@ impl Ipv4Network {
|
||||
/// let tinynet: Ipv4Network = "0.0.0.0/32".parse().unwrap();
|
||||
/// 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)
|
||||
pub fn size(self) -> u32 {
|
||||
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.
|
||||
@ -205,7 +285,7 @@ impl Ipv4Network {
|
||||
/// let net2: Ipv4Network = "10.0.0.0/16".parse().unwrap();
|
||||
/// assert_eq!(net2.nth(256).unwrap(), Ipv4Addr::new(10, 0, 1, 0));
|
||||
/// ```
|
||||
pub fn nth(&self, n: u32) -> Option<Ipv4Addr> {
|
||||
pub fn nth(self, n: u32) -> Option<Ipv4Addr> {
|
||||
if n < self.size() {
|
||||
let net = u32::from(self.network());
|
||||
Some(Ipv4Addr::from(net + n))
|
||||
@ -236,10 +316,9 @@ impl fmt::Display for Ipv4Network {
|
||||
/// ```
|
||||
impl FromStr for Ipv4Network {
|
||||
type Err = IpNetworkError;
|
||||
fn from_str(s: &str) -> Result<Ipv4Network, 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) {
|
||||
@ -254,6 +333,14 @@ impl FromStr for Ipv4Network {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Ipv4Network {
|
||||
type Error = IpNetworkError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
Ipv4Network::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for Ipv4Network {
|
||||
fn from(a: Ipv4Addr) -> Ipv4Network {
|
||||
Ipv4Network {
|
||||
@ -281,6 +368,15 @@ impl Iterator for Ipv4NetworkIterator {
|
||||
};
|
||||
Some(next.into())
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(n) = self.next {
|
||||
let elms = (self.end - n + 1) as usize;
|
||||
(elms, Some(elms))
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &'_ Ipv4Network {
|
||||
@ -295,13 +391,24 @@ impl IntoIterator for &'_ Ipv4Network {
|
||||
///
|
||||
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
||||
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
|
||||
let mask = u32::from(mask);
|
||||
match ipv4_mask_to_prefix_checked(mask) {
|
||||
Some(prefix) => Ok(prefix),
|
||||
None => Err(IpNetworkError::InvalidPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `Ipv4Addr` network mask into a prefix.
|
||||
///
|
||||
/// If the mask is invalid this will return `None`. This is useful in const contexts where
|
||||
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
|
||||
pub const fn ipv4_mask_to_prefix_checked(mask: Ipv4Addr) -> Option<u8> {
|
||||
let mask = mask.to_bits();
|
||||
|
||||
let prefix = (!mask).leading_zeros() as u8;
|
||||
if (u64::from(mask) << prefix) & 0xffff_ffff != 0 {
|
||||
Err(IpNetworkError::InvalidPrefix)
|
||||
if ((mask as u64) << prefix) & 0xffff_ffff != 0 {
|
||||
None
|
||||
} else {
|
||||
Ok(prefix)
|
||||
Some(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,6 +431,18 @@ mod test {
|
||||
assert!(net.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_checked_v4() {
|
||||
let cidr = Ipv4Network::new_checked(Ipv4Addr::new(77, 88, 21, 11), 24).unwrap();
|
||||
assert_eq!(cidr.prefix(), 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn try_create_invalid_checked_v4() {
|
||||
Ipv4Network::new_checked(Ipv4Addr::new(0, 0, 0, 0), 33).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_v4_24bit() {
|
||||
let cidr: Ipv4Network = "127.1.0.0/24".parse().unwrap();
|
||||
@ -406,6 +525,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(dropping_copy_types)]
|
||||
fn copy_compatibility_v4() {
|
||||
let net = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 16).unwrap();
|
||||
mem::drop(net);
|
||||
@ -465,12 +585,31 @@ mod test {
|
||||
fn iterator_v4_huge() {
|
||||
let cidr: Ipv4Network = "0/0".parse().unwrap();
|
||||
let mut iter = cidr.iter();
|
||||
for i in 0..(u32::max_value() as u64 + 1) {
|
||||
for i in 0..(u32::MAX as u64 + 1) {
|
||||
assert_eq!(i as u32, u32::from(iter.next().unwrap()));
|
||||
}
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_v4_size_hint() {
|
||||
let cidr: Ipv4Network = "192.168.0.0/24".parse().unwrap();
|
||||
let mut iter = cidr.iter();
|
||||
assert_eq!((256, Some(256)), iter.size_hint());
|
||||
iter.next();
|
||||
assert_eq!((255, Some(255)), iter.size_hint());
|
||||
|
||||
let cidr: Ipv4Network = "192.168.0.0/32".parse().unwrap();
|
||||
let mut iter = cidr.iter();
|
||||
assert_eq!((1, Some(1)), iter.size_hint());
|
||||
iter.next();
|
||||
assert_eq!((0, None), iter.size_hint());
|
||||
|
||||
let cidr: Ipv4Network = "192.168.0.0/0".parse().unwrap();
|
||||
let iter = cidr.iter();
|
||||
assert_eq!((4294967295, Some(4294967295)), iter.size_hint());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v4_mask_to_prefix() {
|
||||
let mask = Ipv4Addr::new(255, 255, 255, 128);
|
||||
@ -577,9 +716,7 @@ mod test {
|
||||
assert_eq!(
|
||||
src.is_subnet_of(dest),
|
||||
*val,
|
||||
"testing with {} and {}",
|
||||
src,
|
||||
dest
|
||||
"testing with {src} and {dest}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -622,9 +759,7 @@ mod test {
|
||||
assert_eq!(
|
||||
src.is_supernet_of(dest),
|
||||
*val,
|
||||
"testing with {} and {}",
|
||||
src,
|
||||
dest
|
||||
"testing with {src} and {dest}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -636,9 +771,9 @@ mod test {
|
||||
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
|
||||
|
||||
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
|
||||
assert_eq!(skynet.overlaps(other), true);
|
||||
assert_eq!(skynet.overlaps(other2), false);
|
||||
assert_eq!(other2.overlaps(other3), true);
|
||||
assert!(skynet.overlaps(other));
|
||||
assert!(!skynet.overlaps(other2));
|
||||
assert!(other2.overlaps(other3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
376
src/ipv6.rs
376
src/ipv6.rs
@ -1,5 +1,6 @@
|
||||
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
|
||||
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr};
|
||||
use crate::error::IpNetworkError;
|
||||
use crate::parse::{cidr_parts, parse_prefix};
|
||||
use std::{convert::TryFrom, fmt, net::Ipv6Addr, str::FromStr};
|
||||
|
||||
const IPV6_BITS: u8 = 128;
|
||||
const IPV6_SEGMENT_BITS: u8 = 16;
|
||||
@ -28,7 +29,47 @@ impl serde::Serialize for Ipv6Network {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,11 +77,50 @@ 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 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 {
|
||||
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);
|
||||
@ -72,51 +156,15 @@ impl Ipv6Network {
|
||||
|
||||
Ipv6NetworkIterator {
|
||||
next: Some(start),
|
||||
end: end,
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the address of the network denoted by this `Ipv6Network`.
|
||||
/// This means the lowest possible IPv6 address inside of the network.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
|
||||
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
|
||||
/// ```
|
||||
pub fn network(&self) -> Ipv6Addr {
|
||||
let mask = u128::from(self.mask());
|
||||
let ip = u128::from(self.addr) & mask;
|
||||
Ipv6Addr::from(ip)
|
||||
}
|
||||
|
||||
/// Returns the broadcast address of this `Ipv6Network`.
|
||||
/// This means the highest possible IPv4 address inside of the network.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
|
||||
/// assert_eq!(net.broadcast(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xffff, 0xffff));
|
||||
/// ```
|
||||
pub fn broadcast(&self) -> Ipv6Addr {
|
||||
let mask = u128::from(self.mask());
|
||||
let broadcast = u128::from(self.addr) | !mask;
|
||||
Ipv6Addr::from(broadcast)
|
||||
}
|
||||
|
||||
pub fn ip(&self) -> Ipv6Addr {
|
||||
pub const fn ip(&self) -> Ipv6Addr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> u8 {
|
||||
pub const fn prefix(&self) -> u8 {
|
||||
self.prefix
|
||||
}
|
||||
|
||||
@ -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`.
|
||||
@ -151,15 +200,50 @@ impl Ipv6Network {
|
||||
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
|
||||
/// assert_eq!(net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
|
||||
/// ```
|
||||
pub fn mask(&self) -> Ipv6Addr {
|
||||
// Ipv6Addr::from is only implemented for [u8; 16]
|
||||
let mut segments = [0; 16];
|
||||
for (i, segment) in segments.iter_mut().enumerate() {
|
||||
let bits_remaining = self.prefix.saturating_sub(i as u8 * 8);
|
||||
let set_bits = cmp::min(bits_remaining, 8);
|
||||
*segment = !(0xff as u16 >> set_bits) as u8;
|
||||
pub const fn mask(&self) -> Ipv6Addr {
|
||||
debug_assert!(self.prefix <= IPV6_BITS);
|
||||
|
||||
if self.prefix == 0 {
|
||||
return Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
Ipv6Addr::from(segments)
|
||||
let mask = u128::MAX << (IPV6_BITS - self.prefix);
|
||||
Ipv6Addr::from_bits(mask)
|
||||
}
|
||||
|
||||
/// Returns the address of the network denoted by this `Ipv6Network`.
|
||||
/// This means the lowest possible IPv6 address inside of the network.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
|
||||
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
|
||||
/// ```
|
||||
pub const fn network(&self) -> Ipv6Addr {
|
||||
let mask = self.mask().to_bits();
|
||||
let network = self.addr.to_bits() & mask;
|
||||
Ipv6Addr::from_bits(network)
|
||||
}
|
||||
|
||||
/// Returns the broadcast address of this `Ipv6Network`.
|
||||
/// This means the highest possible IPv4 address inside of the network.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let net: Ipv6Network = "2001:db8::/96".parse().unwrap();
|
||||
/// assert_eq!(net.broadcast(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xffff, 0xffff));
|
||||
/// ```
|
||||
pub const fn broadcast(&self) -> Ipv6Addr {
|
||||
let mask = self.mask().to_bits();
|
||||
let broadcast = self.addr.to_bits() | !mask;
|
||||
Ipv6Addr::from_bits(broadcast)
|
||||
}
|
||||
|
||||
/// Checks if a given `Ipv6Addr` is in this `Ipv6Network`
|
||||
@ -174,15 +258,12 @@ impl Ipv6Network {
|
||||
/// assert!(net.contains(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0x1)));
|
||||
/// assert!(!net.contains(Ipv6Addr::new(0xffff, 0, 0, 0, 0, 0, 0, 0x1)));
|
||||
/// ```
|
||||
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)
|
||||
#[inline]
|
||||
pub const fn contains(&self, ip: Ipv6Addr) -> bool {
|
||||
let ip = ip.to_bits();
|
||||
let net = self.network().to_bits();
|
||||
let mask = self.mask().to_bits();
|
||||
(ip & mask) == net
|
||||
}
|
||||
|
||||
/// Returns number of possible host addresses in this `Ipv6Network`.
|
||||
@ -200,25 +281,70 @@ impl Ipv6Network {
|
||||
/// assert_eq!(tinynet.size(), 1);
|
||||
/// ```
|
||||
pub fn size(&self) -> u128 {
|
||||
let host_bits = u32::from(IPV6_BITS - self.prefix);
|
||||
(2 as u128).pow(host_bits)
|
||||
debug_assert!(self.prefix <= IPV6_BITS);
|
||||
|
||||
if self.prefix == 0 {
|
||||
return u128::MAX;
|
||||
}
|
||||
1 << (IPV6_BITS - self.prefix)
|
||||
}
|
||||
|
||||
/// Returns the `n`:th address within this network.
|
||||
/// The addresses are indexed from 0 and `n` must be smaller than the size of the network.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let net: Ipv6Network = "ff01::0/32".parse().unwrap();
|
||||
/// assert_eq!(net.nth(0).unwrap(), "ff01::0".parse::<Ipv6Addr>().unwrap());
|
||||
/// assert_eq!(net.nth(255).unwrap(), "ff01::ff".parse::<Ipv6Addr>().unwrap());
|
||||
/// assert_eq!(net.nth(65538).unwrap(), "ff01::1:2".parse::<Ipv6Addr>().unwrap());
|
||||
/// assert!(net.nth(net.size()).is_none());
|
||||
/// ```
|
||||
pub fn nth(self, n: u128) -> Option<Ipv6Addr> {
|
||||
if n < self.size() {
|
||||
let net = u128::from(self.network());
|
||||
Some(Ipv6Addr::from(net + n))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an `Ipv6Network` from parsing a string in CIDR notation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::net::Ipv6Addr;
|
||||
/// use ipnetwork::Ipv6Network;
|
||||
///
|
||||
/// let new = Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2), 65).unwrap();
|
||||
/// let from_cidr: Ipv6Network = "FF01:0:0:17:0:0:0:2/65".parse().unwrap();
|
||||
/// assert_eq!(new.ip(), from_cidr.ip());
|
||||
/// assert_eq!(new.prefix(), from_cidr.prefix());
|
||||
/// ```
|
||||
impl FromStr for Ipv6Network {
|
||||
type Err = IpNetworkError;
|
||||
fn from_str(s: &str) -> Result<Ipv6Network, 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)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Ipv6Network {
|
||||
type Error = IpNetworkError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
Ipv6Network::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for Ipv6Network {
|
||||
fn from(a: Ipv6Addr) -> Ipv6Network {
|
||||
Ipv6Network {
|
||||
@ -246,6 +372,15 @@ impl Iterator for Ipv6NetworkIterator {
|
||||
};
|
||||
Some(next.into())
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(n) = self.next {
|
||||
let elms = (self.end - n + 1) as usize;
|
||||
(elms, Some(elms))
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &'_ Ipv6Network {
|
||||
@ -265,12 +400,25 @@ impl fmt::Display for Ipv6Network {
|
||||
/// Converts a `Ipv6Addr` network mask into a prefix.
|
||||
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
|
||||
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
||||
match ipv6_mask_to_prefix_checked(mask) {
|
||||
Some(prefix) => Ok(prefix),
|
||||
None => Err(IpNetworkError::InvalidPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `Ipv6Addr` network mask into a prefix.
|
||||
///
|
||||
/// If the mask is invalid this will return `None`. This is useful in const contexts where
|
||||
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
|
||||
pub const fn ipv6_mask_to_prefix_checked(mask: Ipv6Addr) -> Option<u8> {
|
||||
let mask = mask.segments();
|
||||
let mut mask_iter = mask.iter();
|
||||
|
||||
// Count the number of set bits from the start of the address
|
||||
let mut prefix = 0;
|
||||
for &segment in &mut mask_iter {
|
||||
let mut i = 0;
|
||||
while i < mask.len() {
|
||||
let segment = mask[i];
|
||||
i += 1;
|
||||
if segment == 0xffff {
|
||||
prefix += IPV6_SEGMENT_BITS;
|
||||
} else if segment == 0 {
|
||||
@ -280,7 +428,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
||||
let prefix_bits = (!segment).leading_zeros() as u8;
|
||||
// Check that the remainder of the bits are all unset
|
||||
if segment << prefix_bits != 0 {
|
||||
return Err(IpNetworkError::InvalidPrefix);
|
||||
return None;
|
||||
}
|
||||
prefix += prefix_bits;
|
||||
break;
|
||||
@ -288,13 +436,15 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
|
||||
}
|
||||
|
||||
// Now check all the remaining bits are unset
|
||||
for &segment in mask_iter {
|
||||
while i < mask.len() {
|
||||
let segment = mask[i];
|
||||
i += 1;
|
||||
if segment != 0 {
|
||||
return Err(IpNetworkError::InvalidPrefix);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(prefix)
|
||||
Some(prefix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -323,6 +473,18 @@ mod test {
|
||||
assert!(cidr.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_checked_v6() {
|
||||
let cidr = Ipv6Network::new_checked(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 24).unwrap();
|
||||
assert_eq!(cidr.prefix(), 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn try_create_invalid_checked_v6() {
|
||||
Ipv6Network::new_checked(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 129).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_v6() {
|
||||
let cidr: Ipv6Network = "::1/0".parse().unwrap();
|
||||
@ -459,6 +621,18 @@ mod test {
|
||||
assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2), iter.next().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_v6_size_hint() {
|
||||
let cidr: Ipv6Network = "2001:db8::/128".parse().unwrap();
|
||||
let mut iter = cidr.iter();
|
||||
assert_eq!((1, Some(1)), iter.size_hint());
|
||||
assert_eq!(
|
||||
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
|
||||
iter.next().unwrap()
|
||||
);
|
||||
assert_eq!((0, None), iter.size_hint());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_v6() {
|
||||
let cidr: Ipv6Network = "2001:db8::0/96".parse().unwrap();
|
||||
@ -539,9 +713,7 @@ mod test {
|
||||
assert_eq!(
|
||||
src.is_subnet_of(dest),
|
||||
*val,
|
||||
"testing with {} and {}",
|
||||
src,
|
||||
dest
|
||||
"testing with {src} and {dest}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -584,9 +756,7 @@ mod test {
|
||||
assert_eq!(
|
||||
src.is_supernet_of(dest),
|
||||
*val,
|
||||
"testing with {} and {}",
|
||||
src,
|
||||
dest
|
||||
"testing with {src} and {dest}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -596,7 +766,7 @@ mod test {
|
||||
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
|
||||
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap();
|
||||
|
||||
assert_eq!(other2.overlaps(other), true);
|
||||
assert!(other2.overlaps(other));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -611,4 +781,36 @@ mod test {
|
||||
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
|
||||
assert_eq!(256, high_addrs.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nth_ipv6() {
|
||||
let net = Ipv6Network::from_str("ff01::/32").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
net.nth(0).unwrap(),
|
||||
Ipv6Addr::from_str("ff01:0:0:0:0:0:0:0").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
net.nth(255).unwrap(),
|
||||
Ipv6Addr::from_str("ff01::ff").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
net.nth(65538).unwrap(),
|
||||
Ipv6Addr::from_str("ff01::1:2").unwrap()
|
||||
);
|
||||
assert_eq!(net.nth(net.size()), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mask_with_prefix_0() {
|
||||
let network: Ipv6Network = "0::/0".parse().unwrap();
|
||||
let mask = network.mask();
|
||||
assert_eq!(mask, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size_with_prefix_0() {
|
||||
let network: Ipv6Network = "0::/0".parse().unwrap();
|
||||
assert_eq!(network.size(), u128::MAX);
|
||||
}
|
||||
}
|
||||
|
165
src/lib.rs
165
src/lib.rs
@ -1,21 +1,32 @@
|
||||
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
|
||||
//! Rust.
|
||||
#![cfg_attr(feature = "dev", feature(plugin))]
|
||||
#![cfg_attr(feature = "dev", plugin(clippy))]
|
||||
#![crate_type = "lib"]
|
||||
#![doc(html_root_url = "https://docs.rs/ipnetwork/0.16.0")]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
unsafe_code,
|
||||
unused_extern_crates,
|
||||
unused_import_braces
|
||||
)]
|
||||
|
||||
use std::{fmt, net::IpAddr, str::FromStr};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
mod common;
|
||||
mod error;
|
||||
mod ipv4;
|
||||
mod ipv6;
|
||||
mod parse;
|
||||
mod size;
|
||||
|
||||
pub use crate::common::IpNetworkError;
|
||||
use crate::ipv4::Ipv4NetworkIterator;
|
||||
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
|
||||
use crate::ipv6::Ipv6NetworkIterator;
|
||||
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
|
||||
pub use crate::error::{IpNetworkError, NetworkSizeError};
|
||||
pub use crate::ipv4::Ipv4NetworkIterator;
|
||||
pub use crate::ipv4::{ipv4_mask_to_prefix, ipv4_mask_to_prefix_checked, Ipv4Network};
|
||||
pub use crate::ipv6::Ipv6NetworkIterator;
|
||||
pub use crate::ipv6::{ipv6_mask_to_prefix, ipv6_mask_to_prefix_checked, Ipv6Network};
|
||||
pub use crate::size::NetworkSize;
|
||||
|
||||
/// Represents a generic network range. This type can have two variants:
|
||||
/// the v4 and the v6 case.
|
||||
@ -42,15 +53,76 @@ impl serde::Serialize for IpNetwork {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@ -74,7 +146,7 @@ impl IpNetwork {
|
||||
}
|
||||
|
||||
/// Returns the IP part of a given `IpNetwork`
|
||||
pub fn ip(&self) -> IpAddr {
|
||||
pub const fn ip(&self) -> IpAddr {
|
||||
match *self {
|
||||
IpNetwork::V4(ref a) => IpAddr::V4(a.ip()),
|
||||
IpNetwork::V6(ref a) => IpAddr::V6(a.ip()),
|
||||
@ -93,7 +165,7 @@ impl IpNetwork {
|
||||
/// assert_eq!(IpNetwork::V6("ff01::0".parse().unwrap()).prefix(), 128u8);
|
||||
/// assert_eq!(IpNetwork::V6("ff01::0/32".parse().unwrap()).prefix(), 32u8);
|
||||
/// ```
|
||||
pub fn prefix(&self) -> u8 {
|
||||
pub const fn prefix(&self) -> u8 {
|
||||
match *self {
|
||||
IpNetwork::V4(ref a) => a.prefix(),
|
||||
IpNetwork::V6(ref a) => a.prefix(),
|
||||
@ -114,7 +186,7 @@ impl IpNetwork {
|
||||
/// let net: IpNetwork = "2001:db8::/96".parse().unwrap();
|
||||
/// assert_eq!(net.network(), Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
|
||||
/// ```
|
||||
pub fn network(&self) -> IpAddr {
|
||||
pub const fn network(&self) -> IpAddr {
|
||||
match *self {
|
||||
IpNetwork::V4(ref a) => IpAddr::V4(a.network()),
|
||||
IpNetwork::V6(ref a) => IpAddr::V6(a.network()),
|
||||
@ -133,7 +205,7 @@ impl IpNetwork {
|
||||
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
|
||||
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
|
||||
/// ```
|
||||
pub fn broadcast(&self) -> IpAddr {
|
||||
pub const fn broadcast(&self) -> IpAddr {
|
||||
match *self {
|
||||
IpNetwork::V4(ref a) => IpAddr::V4(a.broadcast()),
|
||||
IpNetwork::V6(ref a) => IpAddr::V6(a.broadcast()),
|
||||
@ -159,7 +231,7 @@ impl IpNetwork {
|
||||
/// let v6_net: IpNetwork = "ff01::0/32".parse().unwrap();
|
||||
/// assert_eq!(v6_net.mask(), Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0));
|
||||
/// ```
|
||||
pub fn mask(&self) -> IpAddr {
|
||||
pub const fn mask(&self) -> IpAddr {
|
||||
match *self {
|
||||
IpNetwork::V4(ref a) => IpAddr::V4(a.mask()),
|
||||
IpNetwork::V6(ref a) => IpAddr::V6(a.mask()),
|
||||
@ -178,7 +250,7 @@ impl IpNetwork {
|
||||
/// assert_eq!(v4.is_ipv4(), true);
|
||||
/// assert_eq!(v4.is_ipv6(), false);
|
||||
///```
|
||||
pub fn is_ipv4(&self) -> bool {
|
||||
pub const fn is_ipv4(&self) -> bool {
|
||||
match *self {
|
||||
IpNetwork::V4(_) => true,
|
||||
IpNetwork::V6(_) => false,
|
||||
@ -197,7 +269,7 @@ impl IpNetwork {
|
||||
/// assert_eq!(v6.is_ipv6(), true);
|
||||
/// assert_eq!(v6.is_ipv4(), false);
|
||||
///```
|
||||
pub fn is_ipv6(&self) -> bool {
|
||||
pub const fn is_ipv6(&self) -> bool {
|
||||
match *self {
|
||||
IpNetwork::V4(_) => false,
|
||||
IpNetwork::V6(_) => true,
|
||||
@ -224,7 +296,8 @@ impl IpNetwork {
|
||||
/// assert!(!net.contains(ip2));
|
||||
/// assert!(!net.contains(ip4));
|
||||
/// ```
|
||||
pub fn contains(&self, ip: IpAddr) -> bool {
|
||||
#[inline]
|
||||
pub const fn contains(&self, ip: IpAddr) -> bool {
|
||||
match (*self, ip) {
|
||||
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
|
||||
(IpNetwork::V6(net), IpAddr::V6(ip)) => net.contains(ip),
|
||||
@ -278,7 +351,7 @@ impl IpNetwork {
|
||||
/// ```
|
||||
impl FromStr for IpNetwork {
|
||||
type Err = IpNetworkError;
|
||||
fn from_str(s: &str) -> Result<IpNetwork, IpNetworkError> {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(net) = Ipv4Network::from_str(s) {
|
||||
Ok(IpNetwork::V4(net))
|
||||
} else if let Ok(net) = Ipv6Network::from_str(s) {
|
||||
@ -289,6 +362,14 @@ impl FromStr for IpNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for IpNetwork {
|
||||
type Error = IpNetworkError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
IpNetwork::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Network> for IpNetwork {
|
||||
fn from(v4: Ipv4Network) -> IpNetwork {
|
||||
IpNetwork::V4(v4)
|
||||
@ -301,11 +382,23 @@ impl From<Ipv6Network> for IpNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for IpNetwork {
|
||||
fn from(addr: Ipv4Addr) -> IpNetwork {
|
||||
IpNetwork::V4(Ipv4Network::from(addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for IpNetwork {
|
||||
fn from(addr: Ipv6Addr) -> IpNetwork {
|
||||
IpNetwork::V6(Ipv6Network::from(addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpNetwork {
|
||||
fn from(addr: IpAddr) -> IpNetwork {
|
||||
match addr {
|
||||
IpAddr::V4(a) => IpNetwork::V4(Ipv4Network::from(a)),
|
||||
IpAddr::V6(a) => IpNetwork::V6(Ipv6Network::from(a)),
|
||||
IpAddr::V4(a) => IpNetwork::from(a),
|
||||
IpAddr::V6(a) => IpNetwork::from(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,6 +418,7 @@ enum IpNetworkIteratorInner {
|
||||
V6(Ipv6NetworkIterator),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IpNetworkIterator {
|
||||
inner: IpNetworkIteratorInner,
|
||||
}
|
||||
@ -337,6 +431,12 @@ impl Iterator for IpNetworkIterator {
|
||||
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
|
||||
}
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match &self.inner {
|
||||
IpNetworkIteratorInner::V4(iter) => iter.size_hint(),
|
||||
IpNetworkIteratorInner::V6(iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &'_ IpNetwork {
|
||||
@ -356,6 +456,17 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `IpAddr` network mask into a prefix.
|
||||
///
|
||||
/// If the mask is invalid this will return `None`. This is useful in const contexts where
|
||||
/// [`Option::unwrap`] may be called to trigger a compile-time error if the prefix is invalid.
|
||||
pub const fn ip_mask_to_prefix_checked(mask: IpAddr) -> Option<u8> {
|
||||
match mask {
|
||||
IpAddr::V4(mask) => ipv4_mask_to_prefix_checked(mask),
|
||||
IpAddr::V6(mask) => ipv6_mask_to_prefix_checked(mask),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
|
33
src/parse.rs
Normal file
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)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user