64 Commits

Author SHA1 Message Date
c9b2bc5519 Cut a new release for v0.18.0 2021-04-13 18:29:34 -05:00
fe9f1ba8d5 Merge pull request #140 from achanda/contains-inline
Always inline the contains function
2021-04-05 14:25:30 -05:00
9921661ebe Always inline the contains function
This function is commonly called in a loop (or iterator). Inlining it
makes it run faster, which is beneficial in this case.
2021-04-04 23:15:58 -05:00
14108098d6 Merge pull request #139 from achanda/iter-public
Reexport underlying iterators and do not implement Copy
2021-02-16 22:12:27 -06:00
1f3e10f73d Reexport underlying iterators and do not implement Copy
Closes #138
2021-02-15 11:12:15 -06:00
c26c836b39 Merge pull request #137 from achanda/const-fn
Convert Ipv{4,6}Network::new to const functions
2020-08-29 11:19:23 +01:00
8fcff82c7b Convert Ipv{4,6}Network::new to const functions
IpNetwork::new cannot be const since it is not supported with the ?
operator

Closes #111
2020-08-27 18:01:07 +01:00
46cc64d424 Merge pull request #136 from achanda/tryfrom
Implement TryFrom<&str> for all basic types
2020-08-09 22:32:03 +01:00
e15e8cb812 Implement TryFrom<&str> for all basic types
Also improve some docs and FromStr type signature
Closes #135
2020-08-09 22:27:49 +01:00
0b98eecf6a Merge pull request #134 from achanda/v0.17.0
Cut a new release
2020-07-13 11:47:44 +01:00
6c6bdb333d Cut a new release 2020-07-13 11:45:28 +01:00
eded5f13a9 Merge pull request #133 from jethrogb/patch-1
Update serde version
2020-07-13 11:39:35 +01:00
1d4665dfe6 Update serde version
This crate doesn't actually work with serde 0.8 or 0.9.
2020-07-13 12:01:48 +02:00
e80eab0c80 Merge pull request #131 from tshepang/patch-1
Update README.md
2020-06-06 14:09:43 +01:00
8f1c6b59bb Update README.md
nits
2020-06-06 14:48:15 +02:00
a9f4547c82 Merge pull request #128 from achanda/dual-license
Dual license this project
2020-02-26 16:35:36 +00:00
fa128680b5 Dual license this project
closes #127
2020-02-26 13:47:18 +00:00
620ec4d42c Merge pull request #123 from achanda/bench-speedup
Isolate the contains call in bench
2020-01-18 18:48:51 +00:00
b6397ca2bc Isolate the contains call in bench 2020-01-18 18:42:49 +00:00
c0257fc59d Merge pull request #122 from achanda/clippy-lints
Address all changes clippy pointed out
2020-01-18 17:59:43 +00:00
45fbf458b3 Address all changes clippy pointed out 2020-01-18 17:53:15 +00:00
ca1d332e71 Merge pull request #121 from achanda/clippy
Remove the clippy plugin
2020-01-18 17:47:04 +00:00
121820add8 Drop clippy from dependencies 2020-01-18 17:41:43 +00:00
65bd6724c2 Remove the clippy plugin
And update instructions to run clippy from cargo
2020-01-18 17:39:07 +00:00
41e0f8a8a3 Merge pull request #120 from achanda/forbid
Add a few deny lints
2020-01-18 11:16:59 +00:00
f23ea4d7d6 Add a few deny lints
And conform to the new ones
2020-01-18 11:03:37 +00:00
fc958f16dc Merge pull request #119 from thepacketgeek/master
Update README for IPv6 support
2020-01-17 20:12:23 +00:00
23b95c3b85 Update README for IPv6 support
IPv6 looks implemented, cleaning up the README comment
2020-01-17 11:22:52 -08:00
38460c0598 Merge pull request #117 from achanda/release-0.16
Uprev for a new release
2020-01-08 10:42:56 +00:00
ca2da8ae30 Uprev for a new release 2020-01-08 10:37:12 +00:00
156ae55da0 Merge pull request #116 from achanda/cleanup
Cleanup comment on IPv6 implementation
2020-01-08 10:35:57 +00:00
4dfdf142d0 Cleanup comment on IPv6 implementation 2020-01-08 10:29:58 +00:00
6851a0b272 Merge pull request #115 from achanda/maintenance
Set a maintenance status
2020-01-06 20:29:55 +00:00
00256cf885 Set a maintenance status
Putting this as passively-maintained since this crate is mostly feature
complete.
2020-01-05 13:27:46 +00:00
80c6794829 Merge pull request #114 from achanda/ipv6-netmask
Check against specific errors in parsing test
2020-01-05 13:00:30 +00:00
18677e3b69 Run cargo fmt 2020-01-05 11:38:28 +00:00
986ee0ba1c Check against specific errors in parsing test 2020-01-05 11:36:49 +00:00
f3ac9ba3fe Merge pull request #113 from achanda/bench-netmask
Add a benchmark for parsing IPv4 netmask
2020-01-05 11:20:31 +00:00
bc1064e5f0 Add a benchmark for parsing IPv4 netmask 2020-01-05 11:14:06 +00:00
ddc9ef2f7b Merge pull request #112 from vorner/netmask-parse
Be able to parse the netmask format
2020-01-04 10:19:35 +00:00
87ea7b24fa Be able to parse the netmask format
Allow accepting the netmask format, eg 192.168.1.0/255.255.255.0, in
addition to the more modern /24 prefix version.

Adding support for IPv4 only, as IPv6 never actually used that format in
practice.

Closes #107.
2019-12-21 16:19:20 +01:00
08126f5103 Merge pull request #109 from mullvad/optional-serde
Make the serde dependency optional
2019-12-13 16:23:08 +00:00
376081d253 Merge pull request #108 from vorner/iterator
Iterator
2019-12-13 16:22:50 +00:00
fa85cdb500 Test without features on CI 2019-12-11 12:51:39 +01:00
fa5deed40b Make the serde dependency optional 2019-12-11 12:51:39 +01:00
5b09d82d43 Fix bug with overflow in iterator 2019-12-11 09:40:55 +01:00
dbf366cfdf Iterators: reproducer for overflow on high end of iteration 2019-12-11 09:40:55 +01:00
6cbb011597 Iterators for the IpNetwork
Version-agnostic iterator in addition to the version specific ones.
2019-12-11 09:40:55 +01:00
f7d9524f70 Make the iterators slightly better
* Implement some useful traits on them.
* Make reference an into-iterator.
2019-12-11 09:40:55 +01:00
e1f8566301 Merge pull request #110 from mullvad/metadata-cleanup
Metadata cleanup
2019-12-10 12:16:02 +00:00
a3197a8411 Remove the "heavily WIP" part of description 2019-12-10 09:40:08 +01:00
0479ca45a9 Add and remove appropriate keywords and categories 2019-12-10 09:40:08 +01:00
11025d3b1a Remove documentation URL that is already default 2019-12-10 09:28:26 +01:00
43a38d4117 Merge pull request #103 from lucab/ups/parse-net-pair
ipnetwork: add netmask constructors
2019-11-28 13:52:55 +00:00
152f1a8393 ipnetwork: add netmask constructors
This adds constructors to build all network types from a network
address and network mask pair.
2019-11-27 17:20:59 +00:00
31fc25f9ce cargo: rustfmt whole project 2019-11-27 15:50:47 +00:00
bdaa8a7843 Merge pull request #101 from achanda/release-v0.15.1
Uprev for a new release
2019-11-23 13:54:22 +00:00
d54f463f86 Uprev for a new release 2019-11-23 13:48:22 +00:00
fb782086ce Merge pull request #100 from LukasKalbertodt/master
Use `slice::iter` instead of `into_iter` to avoid future breakage
2019-10-30 22:17:22 +00:00
26706b3790 Use slice::iter instead of into_iter to avoid future breakage
`an_array.into_iter()` currently just works because of the autoref
feature, which then calls `<[T] as IntoIterator>::into_iter`. But
in the future, arrays will implement `IntoIterator`, too. In order
to avoid problems in the future, the call is replaced by `iter()`
which is shorter and more explicit.
2019-10-30 15:32:11 +01:00
2eed2de227 Merge pull request #99 from achanda/action-config
Add a github action config
2019-10-12 16:28:31 +01:00
7395af2658 Show current git branch 2019-10-12 16:17:29 +01:00
fefd674721 Add a checkout step 2019-10-12 16:11:31 +01:00
20ed033826 Add a github action config 2019-10-09 22:56:14 +01:00
12 changed files with 567 additions and 137 deletions

28
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Rust
on:
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
with:
ref: ${{ github.ref }}
- name: Show current git branch
run: git branch
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build docs
run: cargo doc --verbose

View File

@ -9,6 +9,8 @@ env:
- FEATURES: default
script:
- cargo build --verbose --no-default-features
- cargo test --verbose --no-default-features
- cargo build --verbose
- cargo test --verbose
- cargo build --release --verbose

View File

@ -1,31 +1,29 @@
[package]
name = "ipnetwork"
version = "0.15.0" # When updating version, also modify html_root_url in the lib.rs
version = "0.18.0" # When updating version, also modify html_root_url in the lib.rs
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
description = "A library to work with IP CIDRs in Rust, heavily WIP"
license = "Apache-2.0"
description = "A library to work with IP CIDRs in Rust"
license = "MIT OR Apache-2.0"
repository = "https://github.com/achanda/ipnetwork"
keywords = ["network", "ip", "address"]
keywords = ["network", "ip", "address", "cidr"]
readme = "README.md"
documentation = "https://docs.rs/ipnetwork/"
categories = ["network-programming", "os"]
categories = ["network-programming", "parser-implementations"]
edition = "2018"
[dependencies]
clippy = {version = "0.0.302", optional = true}
serde = ">=0.8.0, <2.0"
serde = { version = "1", optional = true }
[dev-dependencies]
serde_json = "1.0"
serde_derive = ">=0.8.0, <2.0"
criterion = "0.3.0"
serde_derive = "1"
criterion = {version = "0.3.4", features= ["html_reports"]}
[badges]
travis-ci = { repository = "achanda/ipnetwork" }
maintenance = { status = "passively-maintained" }
[features]
default = []
dev = ["clippy"]
default = ["serde"]
[[bench]]
name = "parse_bench"

25
LICENSE-MIT.md Normal file
View 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.

View File

@ -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
[![Build Status](https://travis-ci.org/achanda/ipnetwork.svg?branch=master)](https://travis-ci.org/achanda/ipnetwork)
[![Merit Badge](http://meritbadge.herokuapp.com/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 = "*"
```

View File

@ -2,31 +2,48 @@ use criterion::{criterion_group, criterion_main, Criterion};
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
fn parse_ipv4_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4", |b| b.iter(|| {
"127.1.0.0/24".parse::<Ipv4Network>().unwrap()
}));
fn parse_ipv4_prefix_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 prefix", |b| {
b.iter(|| "127.1.0.0/24".parse::<Ipv4Network>().unwrap())
});
}
fn parse_ipv6_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv6", |b| b.iter(|| {
"FF01:0:0:17:0:0:0:2/64".parse::<Ipv6Network>().unwrap()
}));
c.bench_function("parse ipv6", |b| {
b.iter(|| "FF01:0:0:17:0:0:0:2/64".parse::<Ipv6Network>().unwrap())
});
}
fn parse_ipv4_netmask_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 netmask", |b| {
b.iter(|| "127.1.0.0/255.255.255.0".parse::<Ipv4Network>().unwrap())
});
}
fn contains_ipv4_benchmark(c: &mut Criterion) {
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))
}));
let cidr = "74.125.227.0/25".parse::<Ipv4Network>().unwrap();
c.bench_function("contains ipv4", |b| {
b.iter(|| {
cidr.contains(Ipv4Addr::new(74, 125, 227, 4))
})
});
}
fn contains_ipv6_benchmark(c: &mut Criterion) {
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))
}));
let cidr = "FF01:0:0:17:0:0:0:2/65".parse::<Ipv6Network>().unwrap();
c.bench_function("contains ipv6", |b| {
b.iter(|| {
cidr.contains(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0x7fff, 0, 0, 0x2))
})
});
}
criterion_group!(benches, parse_ipv4_benchmark, parse_ipv6_benchmark, contains_ipv4_benchmark, contains_ipv6_benchmark);
criterion_group!(
benches,
parse_ipv4_prefix_benchmark,
parse_ipv6_benchmark,
parse_ipv4_netmask_benchmark,
contains_ipv4_benchmark,
contains_ipv6_benchmark
);
criterion_main!(benches);

View File

@ -36,18 +36,17 @@ pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
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 {
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..])));
Ok((ip, Some(&prefix[1..])))
}
} else {
// Handle the case when cidr does not have a slash
return Ok((cidr, None));
Ok((cidr, None))
}
}

View File

@ -1,6 +1,5 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, net::Ipv4Addr, str::FromStr};
use std::{fmt, net::Ipv4Addr, str::FromStr, convert::TryFrom};
const IPV4_BITS: u8 = 32;
@ -11,20 +10,22 @@ pub struct Ipv4Network {
prefix: u8,
}
impl<'de> Deserialize<'de> for Ipv4Network {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv4Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
Ipv4Network::from_str(&s).map_err(de::Error::custom)
Ipv4Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for Ipv4Network {
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv4Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -32,8 +33,9 @@ impl Serialize for Ipv4Network {
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> {
if prefix > IPV4_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
@ -41,20 +43,38 @@ impl Ipv4Network {
}
}
/// Constructs a new `Ipv4Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(
netaddr: Ipv4Addr,
netmask: Ipv4Addr,
) -> Result<Ipv4Network, IpNetworkError> {
let prefix = ipv4_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// 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();
Ipv4NetworkIterator { next: start, end }
let end = start + (self.size() - 1);
Ipv4NetworkIterator {
next: Some(start),
end,
}
}
pub fn ip(&self) -> Ipv4Addr {
pub fn ip(self) -> Ipv4Addr {
self.addr
}
pub fn prefix(&self) -> u8 {
pub fn prefix(self) -> u8 {
self.prefix
}
@ -70,13 +90,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.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv4Network`.
@ -93,7 +109,7 @@ impl Ipv4Network {
/// let net: Ipv4Network = "127.0.0.0/16".parse().unwrap();
/// assert_eq!(net.mask(), Ipv4Addr::new(255, 255, 0, 0));
/// ```
pub fn mask(&self) -> Ipv4Addr {
pub fn mask(self) -> Ipv4Addr {
let prefix = self.prefix;
let mask = !(0xffff_ffff as u64 >> prefix) as u32;
Ipv4Addr::from(mask)
@ -111,7 +127,7 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.1.9.32/16".parse().unwrap();
/// assert_eq!(net.network(), Ipv4Addr::new(10, 1, 0, 0));
/// ```
pub fn network(&self) -> Ipv4Addr {
pub fn network(self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let ip = u32::from(self.addr) & mask;
Ipv4Addr::from(ip)
@ -129,7 +145,7 @@ impl Ipv4Network {
/// let net: Ipv4Network = "10.9.0.32/16".parse().unwrap();
/// assert_eq!(net.broadcast(), Ipv4Addr::new(10, 9, 255, 255));
/// ```
pub fn broadcast(&self) -> Ipv4Addr {
pub fn broadcast(self) -> Ipv4Addr {
let mask = u32::from(self.mask());
let broadcast = u32::from(self.addr) | !mask;
Ipv4Addr::from(broadcast)
@ -147,7 +163,8 @@ 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 {
#[inline]
pub fn contains(self, ip: Ipv4Addr) -> bool {
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
let net = u32::from(self.addr) & mask;
(u32::from(ip) & mask) == net
@ -167,7 +184,7 @@ impl Ipv4Network {
/// let tinynet: Ipv4Network = "0.0.0.0/32".parse().unwrap();
/// assert_eq!(tinynet.size(), 1);
/// ```
pub fn size(&self) -> u32 {
pub fn size(self) -> u32 {
let host_bits = u32::from(IPV4_BITS - self.prefix);
(2 as u32).pow(host_bits)
}
@ -189,7 +206,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))
@ -220,18 +237,32 @@ 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 prefix = match prefix_str {
Some(v) => parse_prefix(v, IPV4_BITS)?,
Some(v) => {
if let Ok(netmask) = Ipv4Addr::from_str(v) {
ipv4_mask_to_prefix(netmask)?
} else {
parse_prefix(v, IPV4_BITS)?
}
}
None => IPV4_BITS,
};
Ipv4Network::new(addr, prefix)
}
}
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 {
@ -241,8 +272,9 @@ impl From<Ipv4Addr> for Ipv4Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv4NetworkIterator {
next: u32,
next: Option<u32>,
end: u32,
}
@ -250,17 +282,26 @@ impl Iterator for Ipv4NetworkIterator {
type Item = Ipv4Addr;
fn next(&mut self) -> Option<Ipv4Addr> {
if self.next < self.end {
let next = Ipv4Addr::from(self.next as u32);
self.next += 1;
Some(next)
} else {
let next = self.next?;
self.next = if next == self.end {
None
}
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv4Network {
type IntoIter = Ipv4NetworkIterator;
type Item = Ipv4Addr;
fn into_iter(self) -> Ipv4NetworkIterator {
self.iter()
}
}
/// Converts a `Ipv4Addr` network mask into a prefix.
///
/// 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);
@ -446,6 +487,22 @@ mod test {
assert_eq!(prefix, 25);
}
/// Parse netmask as well as prefix
#[test]
fn parse_netmask() {
let from_netmask: Ipv4Network = "192.168.1.0/255.255.255.0".parse().unwrap();
let from_prefix: Ipv4Network = "192.168.1.0/24".parse().unwrap();
assert_eq!(from_netmask, from_prefix);
}
#[test]
fn parse_netmask_broken_v4() {
assert_eq!(
"192.168.1.0/255.0.255.0".parse::<Ipv4Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn invalid_v4_mask_to_prefix() {
let mask = Ipv4Addr::new(255, 0, 255, 0);
@ -453,6 +510,24 @@ mod test {
assert!(prefix.is_err());
}
#[test]
fn ipv4network_with_netmask() {
{
// Positive test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 0, 0);
let net = Ipv4Network::with_netmask(addr, mask).unwrap();
let expected = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 8).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 255, 0);
Ipv4Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn ipv4network_from_ipv4addr() {
let net = Ipv4Network::from(Ipv4Addr::new(127, 0, 0, 1));
@ -477,14 +552,44 @@ mod test {
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false);
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.0.0/24".parse().unwrap()), true);
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false);
test_cases.insert(("10.0.1.0/24".parse().unwrap(), "10.0.0.0/30".parse().unwrap()), false);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.1.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(src.is_subnet_of(dest), *val, "testing with {} and {}", src, dest);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
@ -492,14 +597,44 @@ mod test {
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false);
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.0.0/24".parse().unwrap()), false);
test_cases.insert(("10.0.0.0/30".parse().unwrap(), "10.0.1.0/24".parse().unwrap()), false);
test_cases.insert(("10.0.0.0/24".parse().unwrap(), "10.0.0.0/30".parse().unwrap()), true);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
@ -514,4 +649,22 @@ mod test {
assert_eq!(skynet.overlaps(other2), false);
assert_eq!(other2.overlaps(other3), true);
}
#[test]
fn edges() {
let low: Ipv4Network = "0.0.0.0/24".parse().unwrap();
let low_addrs: Vec<Ipv4Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
assert_eq!("0.0.0.0".parse::<Ipv4Addr>().unwrap(), low_addrs[0]);
assert_eq!("0.0.0.255".parse::<Ipv4Addr>().unwrap(), low_addrs[255]);
let high: Ipv4Network = "255.255.255.0/24".parse().unwrap();
let high_addrs: Vec<Ipv4Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
assert_eq!("255.255.255.0".parse::<Ipv4Addr>().unwrap(), high_addrs[0]);
assert_eq!(
"255.255.255.255".parse::<Ipv4Addr>().unwrap(),
high_addrs[255]
);
}
}

View File

@ -1,6 +1,5 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr};
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr, convert::TryFrom};
const IPV6_BITS: u8 = 128;
const IPV6_SEGMENT_BITS: u8 = 16;
@ -12,20 +11,22 @@ pub struct Ipv6Network {
prefix: u8,
}
impl<'de> Deserialize<'de> for Ipv6Network {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv6Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
Ipv6Network::from_str(&s).map_err(de::Error::custom)
Ipv6Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for Ipv6Network {
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv6Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -33,8 +34,9 @@ impl Serialize for Ipv6Network {
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> {
if prefix > IPV6_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
@ -42,6 +44,18 @@ impl Ipv6Network {
}
}
/// Constructs a new `Ipv6Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: Ipv6Addr, netmask: Ipv6Addr) -> Result<Self, IpNetworkError> {
let prefix = ipv6_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// 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.
@ -57,8 +71,8 @@ impl Ipv6Network {
let end: u128 = dec | mask;
Ipv6NetworkIterator {
next: start,
end: end,
next: Some(start),
end,
}
}
@ -118,13 +132,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.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv6Network`.
@ -164,6 +174,7 @@ 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)));
/// ```
#[inline]
pub fn contains(&self, ip: Ipv6Addr) -> bool {
let a = self.addr.segments();
let b = ip.segments();
@ -195,9 +206,22 @@ impl Ipv6Network {
}
}
/// 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()))?;
@ -209,6 +233,14 @@ impl FromStr for Ipv6Network {
}
}
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 {
@ -218,8 +250,9 @@ impl From<Ipv6Addr> for Ipv6Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv6NetworkIterator {
next: u128,
next: Option<u128>,
end: u128,
}
@ -227,13 +260,21 @@ impl Iterator for Ipv6NetworkIterator {
type Item = Ipv6Addr;
fn next(&mut self) -> Option<Ipv6Addr> {
if self.next <= self.end {
let next = Ipv6Addr::from(self.next);
self.next += 1;
Some(next)
} else {
let next = self.next?;
self.next = if next == self.end {
None
}
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv6Network {
type IntoIter = Ipv6NetworkIterator;
type Item = Ipv6Addr;
fn into_iter(self) -> Ipv6NetworkIterator {
self.iter()
}
}
@ -247,7 +288,7 @@ impl fmt::Display for Ipv6Network {
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
let mask = mask.segments();
let mut mask_iter = mask.into_iter();
let mut mask_iter = mask.iter();
// Count the number of set bits from the start of the address
let mut prefix = 0;
@ -290,6 +331,14 @@ mod test {
assert_eq!(cidr.prefix(), 24);
}
#[test]
fn parse_netmask_broken_v6() {
assert_eq!(
"FF01:0:0:17:0:0:0:2/255.255.255.0".parse::<Ipv6Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn create_v6_invalid_prefix() {
let cidr = Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 129);
@ -370,6 +419,25 @@ mod test {
assert!(prefix.is_err());
}
#[test]
fn ipv6network_with_netmask() {
{
// Positive test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0);
let net = Ipv6Network::with_netmask(addr, mask).unwrap();
let expected =
Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2), 48).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0, 0, 0xffff, 0xffff, 0, 0, 0, 0);
Ipv6Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn iterator_v6() {
let cidr: Ipv6Network = "2001:db8::/126".parse().unwrap();
@ -459,14 +527,44 @@ mod test {
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(("2000:999::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false);
test_cases.insert(("2000:aaa::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), true);
test_cases.insert(("2000:bbb::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false);
test_cases.insert(("2000:aaa::/48".parse().unwrap(), "2000:aaa::/56".parse().unwrap()), false);
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(src.is_subnet_of(dest), *val, "testing with {} and {}", src, dest);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
@ -474,14 +572,44 @@ mod test {
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(("2000:999::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false);
test_cases.insert(("2000:aaa::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false);
test_cases.insert(("2000:bbb::/56".parse().unwrap(), "2000:aaa::/48".parse().unwrap()), false);
test_cases.insert(("2000:aaa::/48".parse().unwrap(), "2000:aaa::/56".parse().unwrap()), true);
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(src.is_supernet_of(dest), *val, "testing with {} and {}", src, dest);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
@ -492,4 +620,17 @@ mod test {
assert_eq!(other2.overlaps(other), true);
}
#[test]
fn edges() {
let low: Ipv6Network = "::0/120".parse().unwrap();
let low_addrs: Vec<Ipv6Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
let high: Ipv6Network = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120"
.parse()
.unwrap();
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
}
}

View File

@ -1,20 +1,24 @@
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
//! Rust. Implementation for IPv4 is more or less stable, IPv6 implementation
//! is still WIP.
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]
//! Rust.
#![crate_type = "lib"]
#![doc(html_root_url = "https://docs.rs/ipnetwork/0.15.0")]
#![doc(html_root_url = "https://docs.rs/ipnetwork/0.18.0")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, net::IpAddr, str::FromStr};
#![deny(
missing_debug_implementations,
unsafe_code,
unused_extern_crates,
unused_import_braces)]
use std::{fmt, net::IpAddr, str::FromStr, convert::TryFrom};
mod common;
mod ipv4;
mod ipv6;
pub use crate::common::IpNetworkError;
pub use crate::ipv4::Ipv4NetworkIterator;
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
pub use crate::ipv6::Ipv6NetworkIterator;
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
/// Represents a generic network range. This type can have two variants:
@ -25,20 +29,22 @@ pub enum IpNetwork {
V6(Ipv6Network),
}
impl<'de> Deserialize<'de> for IpNetwork {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for IpNetwork {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
IpNetwork::from_str(&s).map_err(de::Error::custom)
IpNetwork::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for IpNetwork {
#[cfg(feature = "serde")]
impl serde::Serialize for IpNetwork {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -63,6 +69,14 @@ impl IpNetwork {
}
}
/// Constructs a new `IpNetwork` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: IpAddr, netmask: IpAddr) -> Result<Self, IpNetworkError> {
let prefix = ip_mask_to_prefix(netmask)?;
Self::new(netaddr, prefix)
}
/// Returns the IP part of a given `IpNetwork`
pub fn ip(&self) -> IpAddr {
match *self {
@ -214,6 +228,7 @@ impl IpNetwork {
/// assert!(!net.contains(ip2));
/// assert!(!net.contains(ip4));
/// ```
#[inline]
pub fn contains(&self, ip: IpAddr) -> bool {
match (*self, ip) {
(IpNetwork::V4(net), IpAddr::V4(ip)) => net.contains(ip),
@ -239,6 +254,17 @@ impl IpNetwork {
IpNetwork::V6(ref ip) => NetworkSize::V6(ip.size()),
}
}
/// Returns an iterator over the addresses contained in the network.
///
/// This lists all the addresses in the network range, in ascending order.
pub fn iter(&self) -> IpNetworkIterator {
let inner = match self {
IpNetwork::V4(ip) => IpNetworkIteratorInner::V4(ip.iter()),
IpNetwork::V6(ip) => IpNetworkIteratorInner::V6(ip.iter()),
};
IpNetworkIterator { inner }
}
}
/// Tries to parse the given string into a `IpNetwork`. Will first try to parse
@ -257,7 +283,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) {
@ -268,6 +294,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)
@ -298,6 +332,35 @@ impl fmt::Display for IpNetwork {
}
}
#[derive(Clone, Debug)]
enum IpNetworkIteratorInner {
V4(Ipv4NetworkIterator),
V6(Ipv6NetworkIterator),
}
#[derive(Clone, Debug)]
pub struct IpNetworkIterator {
inner: IpNetworkIteratorInner,
}
impl Iterator for IpNetworkIterator {
type Item = IpAddr;
fn next(&mut self) -> Option<IpAddr> {
match &mut self.inner {
IpNetworkIteratorInner::V4(iter) => iter.next().map(IpAddr::V4),
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
}
}
}
impl IntoIterator for &'_ IpNetwork {
type IntoIter = IpNetworkIterator;
type Item = IpAddr;
fn into_iter(self) -> IpNetworkIterator {
self.iter()
}
}
/// Converts a `IpAddr` network mask into a prefix.
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
@ -310,11 +373,13 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "serde")]
fn deserialize_from_serde_json_value() {
use super::*;
let network = IpNetwork::from_str("0.0.0.0/0").unwrap();
let val: serde_json::value::Value = serde_json::from_str(&serde_json::to_string(&network).unwrap()).unwrap();
let _deser: IpNetwork =
serde_json::from_value(val).expect("Fails to deserialize from json_value::value::Value");
let val: serde_json::value::Value =
serde_json::from_str(&serde_json::to_string(&network).unwrap()).unwrap();
let _deser: IpNetwork = serde_json::from_value(val)
.expect("Fails to deserialize from json_value::value::Value");
}
}

View File

@ -1,3 +1,5 @@
#![cfg(feature = "serde")]
#[cfg(test)]
mod tests {
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};