ipnetx 0.3.0

ipnetx is the crate for set operations on IP address space — union, intersection, difference, and complement over arbitrary collections of ranges and CIDR prefixes, for both IPv4 and IPv6.
Documentation
# Fuzz Testing

`ipnetx` uses [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) (libFuzzer) for fuzz testing. Four targets cover the public API surface:

| Target | What it tests |
|---|---|
| `parse_range` | `IpRange::from_str` for IPv4 and IPv6 — verifies no panics and that successful parses round-trip through `Display` |
| `parse_prefix` | `IpPrefix::from_str` for IPv4 and IPv6 — same round-trip + `masked()` idempotency |
| `ipset_ops` | Set algebra (`union`, `intersection`, `difference`, `complement`) — verifies commutativity, De Morgan's laws, inclusion–exclusion, and the normalization invariant |
| `ipset_builder` | `IpSetBuilder` add/remove operations — verifies the normalization invariant holds after arbitrary sequences of mutations |

## Prerequisites

`cargo-fuzz` requires a nightly Rust toolchain (libFuzzer uses nightly-only flags):

```sh
rustup toolchain install nightly
cargo +nightly install cargo-fuzz --locked
```

## Running fuzz targets

From the repository root:

```sh
# Run a single target indefinitely against the seed corpus (Ctrl-C to stop)
cargo +nightly fuzz run parse_range fuzz/seeds/parse_range

# Run with a local corpus dir so coverage-increasing inputs are saved
mkdir -p fuzz/corpus/parse_range
cargo +nightly fuzz run parse_range fuzz/corpus/parse_range fuzz/seeds/parse_range

# Run with a time limit (seconds)
cargo +nightly fuzz run ipset_ops fuzz/seeds/ipset_ops -- -max_total_time=300

# Run with a length limit (bytes) — useful on resource-constrained machines
cargo +nightly fuzz run ipset_builder fuzz/seeds/ipset_builder -- -max_total_time=60 -max_len=4096

# List all targets
cargo +nightly fuzz list
```

On macOS (Apple Silicon), libFuzzer's AddressSanitizer is not supported.
Add `-s none` to disable it:

```sh
cargo +nightly fuzz run parse_range -s none -- -max_total_time=300
```

## Seed corpus

`fuzz/seeds/<target>/` contains hand-crafted seed inputs that give the fuzzer
a head start. Seeds are checked in and cover boundary cases (full address
space, single IPs, host-bits-set prefixes, etc.).

libFuzzer automatically saves coverage-increasing inputs into the corpus
directory you point it at. Auto-generated corpus files are gitignored
(`fuzz/corpus/` and `fuzz/artifacts/`); pass a local corpus dir to persist
them between runs:

## Investigating a crash

When a crash is found, libFuzzer writes the reproducer to
`fuzz/artifacts/<target>/crash-<hash>`. To reproduce it:

```sh
cargo +nightly fuzz run parse_range fuzz/artifacts/parse_range/crash-<hash>
```

To get a full stack trace, build with the address sanitizer enabled (Linux only):

```sh
cargo +nightly fuzz run parse_range
```

Then minimize the input to the smallest case that still crashes:

```sh
cargo +nightly fuzz tmin parse_range fuzz/artifacts/parse_range/crash-<hash>
```

## Extending coverage

To add a new target:

```sh
cargo +nightly fuzz add <target_name>
# then edit fuzz/fuzz_targets/<target_name>.rs
```

Add representative seed inputs in `fuzz/corpus/<target_name>/`.

## CI integration

The `fuzz.yml` workflow runs a 30-second smoke session for each target on
every push to `main` and on demand via `workflow_dispatch`. This catches
obvious panics and regressions against the seed corpus quickly.

For sustained coverage, run fuzz targets locally overnight or on a dedicated
machine — 30 seconds is not enough time to explore deep paths.