157 Commits

Author SHA1 Message Date
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
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
7af6e16f98 Merge pull request #98 from achanda/release-0.15
Uprev for a new release
2019-09-12 20:34:06 +01:00
9e42aaeb3e Uprev for a new release 2019-09-12 20:25:48 +01:00
2b34ba991a Merge pull request #96 from achanda/dependabot/cargo/criterion-0.3.0
Update criterion requirement from 0.2.9 to 0.3.0
2019-08-26 08:25:21 +01:00
2dff6a0863 Update criterion requirement from 0.2.9 to 0.3.0
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.2.9...0.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-26 05:35:47 +00:00
a45e76c92f Merge pull request #95 from faern/upgrade-to-rust2018
Upgrade to Rust 2018 edition
2019-03-25 16:57:22 +00:00
a6dfadc21d Manual import cleanup 2019-03-25 15:24:17 +01:00
61eeb5843f Run cargo fix --edition-idioms 2019-03-25 15:15:41 +01:00
d8f3cc1992 Upgrade to edition 2018 2019-03-25 15:15:21 +01:00
c2d4b9aeae Run cargo fix --edition 2019-03-25 15:13:50 +01:00
3479b6f272 Merge pull request #94 from mullvad/fix-deserialization
Deserialize from String rather &str for Ipv4Network and Ipv6Network
2019-02-20 16:49:28 +01:00
10d12cd945 Deserialize from String rather &str for ipnetwork:Ipv{4,6}Network 2019-02-20 11:07:29 +00:00
47a539f2f1 Merge pull request #93 from achanda/new-methods
Implement is_subnet_of, is_supernet_of and overlaps
2019-02-07 18:43:47 +01:00
e7c85b5b81 Implement overlaps for both types 2019-02-06 23:29:46 +00:00
439a0deeb7 Implement is_subnet_of and is_supernet_of 2019-02-06 23:14:07 +00:00
b2c458e6f1 Merge pull request #91 from achanda/contains-bench
Track benchmark for contains
2019-02-03 01:10:09 +01:00
1b06452e88 Optimise contains for IPv4 2019-02-02 23:57:25 +00:00
2b9936fa8e Track benchmark for contains 2019-02-02 23:36:08 +00:00
553fa0ba1c Merge pull request #90 from achanda/parse-bench
Add a benchmark for CIDR parsing
2019-02-03 00:22:15 +01:00
a19f24a9c6 Add a benchmark for CIDR parsing
Also optimise the cidr_parts implementation
2019-02-02 21:54:16 +00:00
20 changed files with 1664 additions and 286 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 }}

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

@ -0,0 +1,32 @@
name: Rust
on:
workflow_dispatch:
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: dtolnay/rust-toolchain@stable
with:
rust-version: ${{ matrix.rust }}
components: clippy, rustfmt
- 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 --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/

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

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,26 +1,35 @@
[package]
name = "ipnetwork"
version = "0.14.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, 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 = "2021"
rust-version = "1.80.0"
[dependencies]
clippy = {version = "0.0.302", optional = true}
serde = ">=0.8.0, <2.0"
serde = { version = "1.0.200", optional = true }
schemars = { version = "0.8.17", optional = true }
[dev-dependencies]
serde_json = "1.0"
serde_derive = ">=0.8.0, <2.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]
# 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 = []
dev = ["clippy"]
[[bench]]
name = "parse_bench"
harness = false

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 = "*"
```

49
benches/parse_bench.rs Normal file
View File

@ -0,0 +1,49 @@
use criterion::{criterion_group, criterion_main, Criterion};
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
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())
});
}
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) {
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) {
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_prefix_benchmark,
parse_ipv6_benchmark,
parse_ipv4_netmask_benchmark,
contains_ipv4_benchmark,
contains_ipv6_benchmark
);
criterion_main!(benches);

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,57 +0,0 @@
use std::error::Error;
use std::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 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 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> {
let parts = cidr.split('/').collect::<Vec<&str>>();
if parts.len() == 1 {
Ok((parts[0], None))
} else if parts.len() == 2 {
Ok((parts[0], Some(parts[1])))
} else {
Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {}",
cidr
)))
}
}
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,10 +1,6 @@
use std::fmt;
use std::net::Ipv4Addr;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use common::{cidr_parts, parse_prefix, IpNetworkError};
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;
@ -15,53 +11,161 @@ 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 = <&str>::deserialize(deserializer)?;
Ipv4Network::from_str(s).map_err(de::Error::custom)
let s = <String>::deserialize(deserializer)?;
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())
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()
}
}
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> {
if prefix > IPV4_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(Ipv4Network { addr, prefix })
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 {
None
} else {
Some(Ipv4Network { addr, prefix })
}
}
/// 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 const fn ip(self) -> Ipv4Addr {
self.addr
}
pub fn prefix(&self) -> u8 {
pub const fn prefix(self) -> u8 {
self.prefix
}
/// Checks if the given `Ipv4Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv4Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv4Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv4Network) -> bool {
other.is_subnet_of(self)
}
/// 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())
}
/// Returns the mask for this `Ipv4Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -76,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`.
@ -94,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`.
@ -112,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`
@ -130,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 net = u32::from(self.network());
let mask = u32::from(self.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`.
@ -150,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.
@ -172,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))
@ -183,7 +296,7 @@ impl Ipv4Network {
}
impl fmt::Display for Ipv4Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
@ -203,18 +316,31 @@ 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) => 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 {
@ -224,8 +350,9 @@ impl From<Ipv4Addr> for Ipv4Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv4NetworkIterator {
next: u32,
next: Option<u32>,
end: u32,
}
@ -233,26 +360,55 @@ 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())
}
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 {
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);
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)
}
}
@ -275,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();
@ -357,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);
@ -416,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);
@ -429,6 +617,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);
@ -436,6 +640,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));
@ -454,4 +676,121 @@ mod test {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv4Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[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,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {src} and {dest}"
);
}
}
#[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,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {src} and {dest}"
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv4Network = "1.2.3.0/30".parse().unwrap();
let other2: Ipv4Network = "1.2.2.0/24".parse().unwrap();
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
assert!(skynet.overlaps(other));
assert!(!skynet.overlaps(other2));
assert!(other2.overlaps(other3));
}
#[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,11 +1,6 @@
use std::cmp;
use std::fmt;
use std::net::Ipv6Addr;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use common::{cidr_parts, parse_prefix, IpNetworkError};
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;
@ -17,42 +12,140 @@ 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 = <&str>::deserialize(deserializer)?;
Ipv6Network::from_str(s).map_err(de::Error::custom)
let s = <String>::deserialize(deserializer)?;
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())
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()
}
}
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> {
if prefix > IPV6_BITS {
Err(IpNetworkError::InvalidPrefix)
} else {
Ok(Ipv6Network { addr, prefix })
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 {
None
} else {
Some(Ipv6Network { addr, prefix })
}
}
/// 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.
///
/// # 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);
@ -62,55 +155,37 @@ impl Ipv6Network {
let end: u128 = dec | mask;
Ipv6NetworkIterator {
next: start,
end: end,
next: Some(start),
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
}
/// Checks if the given `Ipv6Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv6Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv6Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv6Network) -> bool {
other.is_subnet_of(self)
}
/// 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())
}
/// Returns the mask for this `Ipv6Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -125,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`
@ -148,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`.
@ -174,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 {
@ -202,8 +354,9 @@ impl From<Ipv6Addr> for Ipv6Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv6NetworkIterator {
next: u128,
next: Option<u128>,
end: u128,
}
@ -211,18 +364,35 @@ 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())
}
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 {
type IntoIter = Ipv6NetworkIterator;
type Item = Ipv6Addr;
fn into_iter(self) -> Ipv6NetworkIterator {
self.iter()
}
}
impl fmt::Display for Ipv6Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
@ -230,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.into_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 {
@ -245,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;
@ -253,18 +436,21 @@ 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)]
mod test {
use super::*;
use std::collections::HashMap;
use std::net::Ipv6Addr;
#[test]
@ -273,12 +459,32 @@ 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);
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();
@ -353,6 +559,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();
@ -396,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();
@ -436,4 +673,144 @@ mod test {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv6Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[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,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {src} and {dest}"
);
}
}
#[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,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {src} and {dest}"
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap();
assert!(other2.overlaps(other));
}
#[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());
}
#[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,27 +1,32 @@
//! 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.14.0")]
#![deny(
missing_debug_implementations,
unsafe_code,
unused_extern_crates,
unused_import_braces
)]
extern crate serde;
use std::{
convert::TryFrom,
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use std::fmt;
use std::net::IpAddr;
mod common;
mod error;
mod ipv4;
mod ipv6;
mod parse;
mod size;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
pub use common::IpNetworkError;
pub use ipv4::{ipv4_mask_to_prefix, Ipv4Network};
pub use 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.
@ -31,30 +36,93 @@ 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())
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 {
@ -69,8 +137,16 @@ 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 {
pub const fn ip(&self) -> IpAddr {
match *self {
IpNetwork::V4(ref a) => IpAddr::V4(a.ip()),
IpNetwork::V6(ref a) => IpAddr::V6(a.ip()),
@ -89,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(),
@ -110,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()),
@ -129,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()),
@ -155,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()),
@ -174,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,
@ -193,13 +269,17 @@ 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,
}
}
// TODO(abhishek) when TryFrom is stable, implement it for IpNetwork to
// variant conversions. Then use that to implement a generic is_subnet_of
// is_supernet_of, overlaps
/// Checks if a given `IpAddr` is in this `IpNetwork`
///
/// # Examples
@ -216,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),
@ -241,6 +322,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
@ -259,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) {
@ -270,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)
@ -282,17 +382,29 @@ 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),
}
}
}
impl fmt::Display for IpNetwork {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
IpNetwork::V4(net) => net.fmt(f),
IpNetwork::V6(net) => net.fmt(f),
@ -300,6 +412,41 @@ 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),
}
}
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 {
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> {
@ -309,14 +456,27 @@ 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]
#[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");
}
}

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

@ -1,16 +1,9 @@
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate ipnetwork;
#![cfg(feature = "serde")]
#[cfg(test)]
mod tests {
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use serde::{Deserialize, Serialize};
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
@ -18,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,
}
@ -28,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]
@ -35,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,
}
@ -48,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]
@ -55,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>,
}
@ -70,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);
}
}