104 Commits

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-08 04:39:00 +00:00
c3af07d69a Impl From<Ipv4Addr, Ipv6Addr> for IpNetwork (#218) 2025-03-22 15:30:10 -05:00
35977adc81 Iterator size hints (#217)
* feat: iterator size hints

* fix: add size_hint to IpNetworkIterator
2025-02-28 10:00:56 -06:00
0deb2abd8b Add ip_mask_to_prefix_checked (#214)
* Add `ipv4_mask_to_prefix_checked`

* Add `ipv6_mask_to_prefix_checked`

* Add `ip_mask_to_prefix_checked`
2025-01-12 22:04:28 -06:00
170fc4ca6b Add rust-version to Cargo.toml and set MSRV to 1.80.0 (#215) 2025-01-12 22:03:05 -06:00
aa65f088b1 Make IpNetwork::is_ipv4 and IpNetwork::is_ipv6 const (#213) 2025-01-09 13:06:22 -06:00
5f929258ef Make IpNetwork::ip const (#212) 2025-01-08 17:38:34 -06:00
a21f5df04e Make IpNetwork::broadcast const (#211) 2025-01-08 17:38:16 -06:00
876f882523 Make IpNetwork::network const (#210) 2025-01-08 17:37:32 -06:00
49f07a4838 Make IpNetwork::prefix const (#209) 2025-01-08 17:36:53 -06:00
2f219a2ada Make IpNetwork::contains const (#208)
* Make `Ipv6Network::contains` const

* Make `Ipv4Network::contains` const

* Make `IpNetwork::contains` const
2025-01-08 10:22:33 -06:00
9ae719637d Make IpNetwork::mask const (#207)
* Make `Ipv4Network::mask` const

* Make `Ipv6Network::mask` const

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

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

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

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

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

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

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

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

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

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

* add IpNetwork v6 nth test

---------

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

* Add tests for NetworkSize conversion/equality

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

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

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

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

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

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 08:58:10 -05:00
1b6904bfc7 Cut a new release for v0.20.0 (#158) 2022-06-30 22:00:50 -05:00
ab6f1012b3 Correct and validate JsonSchema for Ipv4Network, Ipv6Network, and IpNetwork (#157)
* show failed test cases

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

Solution: Derive implementation via `schemars`. Add corresponding
feature to the crate.
2022-05-31 23:37:44 +03:00
372f9bdbb7 Add workflow_dispatch 2022-04-13 23:11:42 -05:00
c0e04d534a Merge pull request #151 from achanda/update-publish-job
Update publish.yml
2022-04-13 23:04:52 -05:00
7c9ff1bc30 Update publish.yml 2022-04-13 23:04:38 -05:00
6d4e4ba47a Merge pull request #150 from achanda/autopublish
Auto publish to crates.io on new tag push
2022-04-13 22:59:50 -05:00
e842e0edc0 Auto publish to crates.io on new tag push 2022-04-13 22:56:43 -05:00
61550eda45 Merge pull request #149 from achanda/v0.19.0
Cut a new release for v0.19.0
2022-04-12 22:47:18 -05:00
3503e4c094 Cut a new release for v0.19.0
This one is still a manual release, I am hoping to get this automated
soon.
2022-04-12 22:44:10 -05:00
91528476a9 Merge pull request #148 from achanda/remove-html_root_url
Remove the redundant html_root_url attribute
2022-04-12 22:38:29 -05:00
501e5533d7 Remove the redundant html_root_url attribute 2022-04-12 22:35:37 -05:00
7120b93837 Merge pull request #144 from paolobarbolini/collect-str
Remove useless allocation when serializing
2021-12-29 10:24:38 -06:00
a57afd9e1a Remove useless allocation when serializing 2021-12-29 16:07:07 +01:00
8ad43541cd Merge pull request #143 from achanda/upgrade-edition
feat: Upgrade rust edition
2021-11-09 22:36:04 -06:00
9651b3b721 feat: Upgrade rust edition
cargo fix --edition did not result in any changes to src
2021-11-08 09:04:50 -06:00
5764406000 Merge pull request #142 from achanda/dependabot/add-v2-config-file 2021-04-29 15:43:16 -05:00
4cbd1f2424 Upgrade to GitHub-native Dependabot 2021-04-29 19:21:55 +00:00
772db6c569 Merge pull request #141 from achanda/v0.18.0
Cut a new release for v0.18.0
2021-04-13 18:32:45 -05:00
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
19 changed files with 1112 additions and 245 deletions

7
.github/dependabot.yml vendored Normal file
View 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
View 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 }}

View File

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

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
.gitignore
Cargo.lock
target
.vscode
.idea/
.idea/

61
CHANGELOG.md Normal file
View File

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

View File

@ -1,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
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

@ -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
View File

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

View File

@ -1,62 +0,0 @@
use std::{error::Error, fmt};
/// Represents a bunch of errors that can occur while working with a `IpNetwork`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpNetworkError {
InvalidAddr(String),
InvalidPrefix,
InvalidCidrFormat(String),
}
impl fmt::Display for IpNetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(ref s) => write!(f, "invalid address: {}", s),
InvalidPrefix => write!(f, "invalid prefix"),
InvalidCidrFormat(ref s) => write!(f, "invalid cidr format: {}", s),
}
}
}
impl Error for IpNetworkError {
fn description(&self) -> &str {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(_) => "address is invalid",
InvalidPrefix => "prefix is invalid",
InvalidCidrFormat(_) => "cidr is invalid",
}
}
}
pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
// Try to find a single slash
if let Some(sep) = cidr.find('/') {
let (ip, prefix) = cidr.split_at(sep);
// Error if cidr has multiple slashes
if prefix[1..].find('/').is_some() {
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
View File

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

View File

@ -1,5 +1,6 @@
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{fmt, net::Ipv4Addr, str::FromStr};
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]

View File

@ -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);
}
}

View File

@ -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
View File

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

207
src/size.rs Normal file
View File

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

View File

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