diff --git a/README.md b/README.md index 8c25328..0b376f5 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,149 @@ # netip -A Rust library for working with IP networks and MAC addresses. +[![crates.io](https://img.shields.io/crates/v/netip.svg)](https://crates.io/crates/netip) +[![docs.rs](https://docs.rs/netip/badge.svg)](https://docs.rs/netip) +[![License](https://img.shields.io/crates/l/netip.svg)](LICENSE) -Key feature: support for **non-contiguous subnet masks**, e.g. `192.168.0.0/255.255.0.255`. +**Zero-dependency** Rust library for IPv4/IPv6 networks and MAC addresses — with first-class support for **non-contiguous subnet masks**. -Zero runtime dependencies. Rust edition 2024. +## Why netip? -## Types +Most networking libraries assume subnet masks are contiguous (a run of 1-bits followed by 0-bits, like `255.255.255.0`). Real-world network hardware — routers, firewalls, packet classifiers — often uses **non-contiguous masks** like `255.0.255.0` or even `170.85.170.85` to match on arbitrary bit patterns. + +`netip` treats every network as a `(address, mask)` pair where the mask can be *any* bit pattern. All operations — containment, intersection, difference, iteration — work correctly with non-contiguous masks. + +If your masks happen to be contiguous, the `Contiguous` wrapper gives you a compile-time guarantee and CIDR prefix access. + +### Key features + +- **Non-contiguous mask support** — the only open-source Rust library that handles arbitrary subnet masks +- **Full set algebra** — `contains`, `intersection`, `difference`, `merge`, `is_adjacent` on networks as sets of addresses +- **Correct iteration** — iterate over addresses in a non-contiguous network in host-index order +- **Zero dependencies** — only `core`, nothing else +- **Performance-first** — `const fn` where possible, `#[inline]` on hot paths, minimal instruction count +- **Type-safe** — `Contiguous` enforces contiguous masks at parse time; mismatched IPv4/IPv6 operations are compile errors + +## Quick start + +Add to your `Cargo.toml`: + +```toml +[dependencies] +netip = "0.2" +``` + +### Parsing and basic operations + +```rust +use netip::{Ipv4Network, IpNetwork}; + +// Standard CIDR notation. +let net: Ipv4Network = Ipv4Network::parse("10.0.0.0/8").unwrap(); +assert_eq!(net.prefix(), Some(8)); -| Type | Description | -| --------------- | ----------- | -| `Ipv4Network` | IPv4 network: (address, mask) pair, supports non-contiguous masks | -| `Ipv6Network` | IPv6 network: (address, mask) pair, supports non-contiguous masks | -| `IpNetwork` | Enum over `Ipv4Network` / `Ipv6Network` | -| `Contiguous` | Newtype wrapper that enforces contiguous masks at parse time | -| `MacAddr` | MAC address (EUI-48), stored as the lower 48 bits of a `u64` | - -## Build and test - -```sh -cargo build -cargo clippy -cargo test -cargo bench +// Dotted-mask notation — including non-contiguous masks. +let net = Ipv4Network::parse("192.168.0.0/255.0.255.0").unwrap(); +assert!(!net.is_contiguous()); + +// IpNetwork parses both IPv4 and IPv6. +let net: IpNetwork = IpNetwork::parse("2001:db8::/32").unwrap(); +``` + +### Set operations on networks + +Networks are sets of addresses. `netip` gives you the algebra: + +```rust +use netip::Ipv4Network; + +let a = Ipv4Network::parse("10.0.0.0/255.0.0.0").unwrap(); +let b = Ipv4Network::parse("10.1.0.0/255.255.0.0").unwrap(); + +// Containment: is every address in b also in a? +assert!(a.contains(&b)); + +// Intersection: the set of addresses in both networks. +let ab = a.intersection(&b).unwrap(); +assert_eq!(ab, b); + +// Difference: addresses in a but not in b — returns an iterator of networks. +for net in a.difference(&b) { + println!("{net}"); // each chunk of the remaining address space +} ``` + +### Non-contiguous masks in action + +This is where `netip` shines. Non-contiguous masks let you express bit-pattern matching that CIDR cannot: + +```rust +use netip::Ipv4Network; + +// Match all addresses where the first and third octets are 10 and 0. +// Second and fourth octets can be anything. +let pattern = Ipv4Network::parse("10.0.0.0/255.0.255.0").unwrap(); +let specific = Ipv4Network::parse("10.42.0.99/255.255.255.255").unwrap(); +assert!(pattern.contains(&specific)); // 10.42.0.99 matches 10.*.0.* + +// Intersection of two non-contiguous networks — always a single network. +let a = Ipv4Network::parse("10.0.0.0/255.0.255.0").unwrap(); +let b = Ipv4Network::parse("10.0.5.0/255.255.0.255").unwrap(); +let ab = a.intersection(&b).unwrap(); +assert_eq!(ab.to_string(), "10.0.5.0/255.255.255.255"); + +// Iterate over all addresses in a non-contiguous network. +let small = Ipv4Network::parse("192.168.0.0/255.255.255.252").unwrap(); +let addrs: Vec<_> = small.addrs().collect(); +assert_eq!(addrs.len(), 4); +``` + +### Contiguous wrapper + +When you need the CIDR guarantee, `Contiguous` enforces it at parse time: + +```rust +use netip::Contiguous; +use netip::Ipv4Network; + +let net: Contiguous = Contiguous::parse("10.0.0.0/8").unwrap(); +assert_eq!(net.prefix(), 8); // guaranteed to succeed — no Option + +// Non-contiguous mask is rejected at parse time. +assert!(Contiguous::::parse("10.0.0.0/255.0.255.0").is_err()); + +// Contiguous derefs to T — all network methods are available. +assert!(net.contains(&Ipv4Network::parse("10.1.2.3/32").unwrap())); +``` + +### MAC addresses + +```rust +use netip::MacAddr; + +let mac = MacAddr::parse("aa:bb:cc:dd:ee:ff").unwrap(); +assert_eq!(mac.to_string(), "aa:bb:cc:dd:ee:ff"); +``` + +## Types + +| Type | Description | +|------|-------------| +| `Ipv4Network` | IPv4 network — `(address, mask)` pair, arbitrary masks | +| `Ipv6Network` | IPv6 network — `(address, mask)` pair, arbitrary masks | +| `IpNetwork` | Enum over `Ipv4Network` / `Ipv6Network` | +| `Contiguous` | Newtype that enforces contiguous masks at parse time | +| `MacAddr` | MAC address (EUI-48), stored as lower 48 bits of `u64` | + +## Roadmap + +`netip` is on the path to a stable 1.0. Current version: **0.2.0**. + +- **v0.3** — API cleanup: `#[must_use]`, `#[deny(missing_docs)]` +- **v0.4** — Complete set algebra: `complement`, `Contiguous` merge/adjacent +- **v0.5** — Testing hardening: fuzz targets, exhaustive non-contiguous edge cases +- **v0.6+** — Documentation polish, API review, CHANGELOG +- **v1.0** — Stable release + +## License + +Apache-2.0