i2pd-sys 0.0.4

Raw FFI bindings to a minimal C shim over libi2pd (PurpleI2P/i2pd).
Documentation
# i2pd-sys

Raw FFI bindings to a minimal C shim over [`libi2pd`](https://github.com/PurpleI2P/i2pd)
(PurpleI2P/i2pd), the reference C++ implementation of the I2P anonymous-networking protocol.

This crate builds a real I2P router in-process — not a client for a separately-running `i2pd`
daemon — and exposes just enough of it (destinations and streams) through a small, stable `extern
"C"` surface for Rust to call into safely at the ABI level.

> **Status:** pre-0.1 (`0.0.4`), developed alongside and consumed by the
> [`tachyon-web`]https://codeberg.org/hacer-bark/tachyon-web workspace. The API may still
> change without notice.

## Is this the crate you want?

This crate is **100% `unsafe`** — raw pointers, manual lifetime management, no ownership types.
Unless you specifically need the bindgen-generated FFI surface itself (e.g. you're building your
own safe wrapper), you almost certainly want
[`tachyon-i2p`](https://crates.io/crates/tachyon-i2p) instead:
an async, safe `tokio`-based wrapper built directly on top of this crate.

## How it's built

`build.rs` does the following, entirely offline (no network access, no shelling out to
`configure`/`b2`/`bootstrap.sh`):

1. Compiles a trimmed **zlib** snapshot (`vendor/zlib-src`) directly via `cc::Build`.
2. Builds a minimal **Boost** subset (`vendor/boost-src`, extracted from the upstream git
   superproject with `bcp`) via Boost's own CMake support.
3. Builds **[AWS-LC]https://github.com/aws/aws-lc** (via the `aws-lc-sys` dependency) as the
   crypto/TLS backend, standing in for OpenSSL — nothing OpenSSL-shaped is vendored or linked.
4. Builds **libi2pd**/**libi2pdclient** (`vendor/i2pd-src`, a trimmed snapshot of upstream i2pd)
   via its own CMake build, as static libraries only (`WITH_BINARY=OFF` — no daemon binary).
5. Compiles `shim/shim.cpp`, a hand-written C++ file implementing the flat C ABI declared in
   `shim/shim.h`, and links everything together.
6. Runs `bindgen` against `shim/shim.h` **only** — never against `libi2pd`'s own C++ headers,
   which use `std::shared_ptr`/STL types with no stable C ABI.

Every dependency is a **committed source snapshot**, not a submodule or a build-time fetch, so
the crate builds reproducibly and works from a `crates.io` tarball with no network egress at
build time. See [`scripts/update-vendor.sh`](scripts/update-vendor.sh) for how each snapshot was
produced and how to refresh one against a newer upstream release.

**Hardening:** every C/C++ translation unit this crate compiles (zlib, i2pd, and the shim — all
three either handle attacker-controlled network/decompression input directly or sit right next to
code that does) is built with `-D_FORTIFY_SOURCE=2` and `-fstack-protector-strong`, applied as
plain compiler flags in `build.rs` rather than relying on i2pd's own `WITH_HARDENING` CMake option
(which is wired up for GCC only upstream — a silent no-op under Clang).

## Requirements

- A C++17 compiler (`cc`/`c++` on the host, found automatically).
- [CMake]https://cmake.org/ (used to build Boost and libi2pd).
- Rust `1.89`+ (edition 2024).

No system `boost`, `openssl`, or `i2pd` packages are required or used — everything is vendored
and statically linked.

### Cross-compilation

Native builds (including building natively *inside* a musl system, e.g. an Alpine container)
need nothing beyond the ambient `cc`/`c++`. True cross-compilation (e.g. a glibc host targeting
`x86_64-unknown-linux-musl`) requires a real musl C++ cross toolchain, since no C++ compiler
shipped by default on a glibc distro can target musl. `build.rs` looks for one, in order:

1. `CC_<target>` / `CXX_<target>` env vars (the standard [`cc`]https://docs.rs/cc crate
   convention), e.g. `CXX_x86_64_unknown_linux_musl`.
2. `MUSL_CROSS_TOOLCHAIN` pointing at a
   [`musl-cross-make`]https://github.com/richfelker/musl-cross-make-style install (expects
   `<dir>/bin/<target-triple>-g++`).
3. A same-named compiler already on `$PATH` (`<target-triple>-g++`).

If none is found, the build fails with an explanation rather than attempting to build a
toolchain from scratch (a 20–40 minute surprise on a library crate's first build).

## Usage

```toml
[dependencies]
i2pd-sys = "0.0.4"
```

```rust,no_run
use i2pd_sys::*;
use std::ffi::{c_void, CString};

unsafe {
    // 1. Initialize i2pd's config/crypto/router-context globals — exactly once, before
    //    anything else.
    let app_name = CString::new("my-app").unwrap();
    i2pd_init(app_name.as_ptr());
    i2pd_start();

    // 2. Create a destination (an I2P address you can send/receive on).
    let dest = i2pd_create_transient_destination();
    assert!(!dest.is_null());

    let addr = i2pd_destination_b32_address(dest);
    // ... read the `<52 chars>.b32.i2p` C string, then:
    i2pd_free_string(addr);

    // 3. Register an acceptor, then create streams to other destinations with
    //    i2pd_create_stream / i2pd_stream_send / i2pd_stream_receive.

    // 4. Tear down in reverse order.
    i2pd_destroy_destination(dest);
    i2pd_stop();
    i2pd_terminate();
}
```

See [`shim/shim.h`](shim/shim.h) for the full, documented API surface (lifecycle, destinations,
streams), and [`tests/roundtrip.rs`](tests/roundtrip.rs) for a complete two-destination,
real-network round trip.

### API overview

| Group | Functions |
|---|---|
| Lifecycle | `i2pd_init`, `i2pd_start`, `i2pd_stop`, `i2pd_terminate` |
| Destinations | `i2pd_create_transient_destination`, `i2pd_generate_keys`, `i2pd_create_persistent_destination`, `i2pd_destroy_destination`, `i2pd_destination_b32_address`, `i2pd_destination_ident_hash`, `i2pd_accept_stream` |
| Streams | `i2pd_create_stream`, `i2pd_stream_send`, `i2pd_stream_receive`, `i2pd_stream_is_open`, `i2pd_stream_close`, `i2pd_destroy_stream` |
| Memory | `i2pd_free_buffer`, `i2pd_free_string` |

### Thread safety

`i2pd_stream_send`/`i2pd_stream_receive` are safe to call concurrently from different threads on
the same stream (one thread sending, another receiving) — each posts work onto i2pd's internal
`io_service` and blocks the *calling* thread on a condition variable, the same pattern i2pd's own
SAM/BOB bridges use. The inbound-stream callback registered via `i2pd_accept_stream` runs on
i2pd's own internal thread and must return quickly without blocking, or it stalls the event loop
for every destination sharing that router.

Every shim function catches all C++ exceptions internally (`try { ... } catch (...) {}`), since
an exception unwinding across the `extern "C"` boundary would be undefined behavior — failures
surface as `NULL`/`0`/`-1` return values instead.

## Testing

```sh
cargo test -p i2pd-sys                              # build + unit tests, no network
cargo test -p i2pd-sys --test roundtrip -- --ignored --nocapture   # live I2P network round trip
```

The round-trip test is `#[ignore]`d by default: it builds real tunnels over the live I2P network
between two in-process destinations, which can take several minutes.

## Vendored third-party code

| Component | License | Snapshot |
|---|---|---|
| [i2pd]https://github.com/PurpleI2P/i2pd | BSD-3-Clause | tag `2.61.0`, commit [`635b013`]https://github.com/PurpleI2P/i2pd/commit/635b013a612ff47278ef02acf8580a28e10e26c5 |
| [Boost]https://www.boost.org/ (minimal subset via `bcp`) | Boost Software License 1.0 | tag `boost-1.86.0` |
| [zlib]https://zlib.net/ | zlib License | `1.3.1` |
| [AWS-LC]https://github.com/aws/aws-lc (via `aws-lc-sys`, not vendored directly) | Apache-2.0 OR ISC ||

Six small compatibility patches ([`scripts/patches/`](scripts/patches/)) are applied
automatically (by `i2pd_snapshot` in [`scripts/update-vendor.sh`](scripts/update-vendor.sh)) on
top of pristine upstream i2pd source. Five are `#ifdef OPENSSL_IS_AWSLC`-guarded, working around
AWS-LC's OpenSSL-1.1.1 API-compatibility gaps (siphash, GOST curve naming, raw ChaCha20,
compressed EC point decoding, and — the newest, largest one — a from-scratch ML-KEM
implementation against AWS-LC's own KEM API; see "Post-quantum (ML-KEM) support" below); those
five apply equally to the `fips` backend, since AWS-LC-FIPS defines the same `OPENSSL_IS_AWSLC`
marker. The sixth is unconditional (a missing `#include <openssl/hmac.h>` that only matters —
see the "FIPS" section below — under the `fips` backend's slightly different header graph). Full
attribution and license text: [`LICENSE-THIRD-PARTY`](LICENSE-THIRD-PARTY).

## Post-quantum (ML-KEM) support

i2pd's own ML-KEM (hybrid post-quantum) code — the thing that lets a destination publish a
`MLKEM768_X25519` LeaseSet2 encryption key alongside classical `ECIES_X25519`/`ELGAMAL_2048`
ones, for "harvest now, decrypt later" resistance — is written against OpenSSL 3.5's "provider"
API (`EVP_PKEY_CTX_new_from_name`/`EVP_PKEY_fromdata`/`OSSL_PARAM`/`EVP_PKEY_{en,de}capsulate_init`).
AWS-LC implements none of that (confirmed against its own headers — zero matches for any of
those symbols), so upstream's `OPENSSL_PQ` gate never fires for AWS-LC builds and this code is
compiled out entirely by default.

**`scripts/patches/0005-awslc-mlkem-compat.patch` reimplements the same `MLKEMKeys` methods
against AWS-LC's own (differently-shaped, non-"provider") ML-KEM API** —
`EVP_PKEY_CTX_new_id(EVP_PKEY_KEM)` + `EVP_PKEY_CTX_kem_set_params` for keygen, a plain
`EVP_PKEY_encapsulate`/`decapsulate` with no separate `_init` call, and — since AWS-LC's
`EVP_PKEY_new_raw_public_key` doesn't accept `EVP_PKEY_KEM` at all — a small hand-built DER
`SubjectPublicKeyInfo` wrapper (mirroring exactly what AWS-LC's own `kem_pub_encode` produces)
fed through `d2i_PUBKEY` to reconstruct a peer's public key from the raw bytes I2P's wire format
carries. Once this patch is applied, `OPENSSL_PQ` is also flipped on for AWS-LC in `Crypto.h`,
which means every destination created through this crate publishes hybrid PQ+classical
LeaseSet2 encryption keys **by default** — `libi2pd`'s own existing (unmodified) fallback logic
in `Destination.cpp` already prefers `ECIES_MLKEM768_X25519_AEAD` over `ECIES_X25519_AEAD`/
`ELGAMAL_2048` whenever `OPENSSL_PQ` is set; this patch is what makes that condition true.

**What's been verified:** the ML-KEM-768 primitive itself round-trips correctly under this patch
(generate → export public key → reconstruct from raw bytes as a peer would → encapsulate →
decapsulate → compare shared secrets byte-for-byte, on both the AWS-LC and AWS-LC-FIPS backends
this crate supports), and a destination created this way publishes the expected three-key
LeaseSet2 and is reachable over the live I2P network through a real router's HTTP proxy.

**What has *not* been independently verified:** wire-level interop of the ML-KEM hybrid Noise
ratchet handshake itself against other live i2pd/Java I2P peers — this patch only touches the
underlying KEM primitive (`MLKEMKeys`), not the ratchet protocol code in
`ECIESX25519AEADRatchetSession.cpp` that consumes it, which is unmodified upstream i2pd. Treat
this as "the crypto primitive works," not as an independent audit of the protocol-level
interoperability claim. Review this patch specifically — it's larger and newer than the other
five, touches post-quantum key material, and hand-builds DER — before relying on it for anything
security-sensitive.

## FIPS

By default this crate links regular [AWS-LC](https://github.com/aws/aws-lc) (via `aws-lc-sys`).
Enabling the `fips` feature instead (`default-features = false, features = ["fips"]`) swaps in
the FIPS 140-3-validated [AWS-LC-FIPS](https://github.com/aws/aws-lc/tree/fips-2024-09-27) module
via [`aws-lc-fips-sys`](https://crates.io/crates/aws-lc-fips-sys) instead, with no *manual* source
changes needed on i2pd's side beyond the five patches above (four shared with the default
backend, one FIPS-specific) applied automatically by `scripts/update-vendor.sh`.

`aws-lc`/`fips` aren't a hard-exclusive pair: if a dependent crate's own default pulls in `aws-lc`
while something further up the dependency tree also asks for `fips` (e.g. a workspace-wide FIPS
switch), **`fips` silently takes priority** — this crate only ever emits link directives for one
backend (see `build.rs`), and `aws-lc-sys`/`aws-lc-fips-sys` link with per-version-prefixed symbol
names, so an inert, unused copy of the other backend being compiled in alongside it is harmless.
You only need `default-features = false` if you want to guarantee `aws-lc-sys` isn't compiled at
all (e.g. to keep build times down).

**What this does and does not get you:** linking a FIPS-validated crypto *module* is not the same
thing as a FIPS-*certified application*. i2pd's own protocol code hasn't been evaluated by NIST,
and some of i2pd's supported algorithm choices (legacy ElGamal destinations, GOST signatures —
`libi2pd/Gost.cpp`) aren't FIPS-approved at all; those code paths still compile and still work
identically either way (they only use generic, algorithm-agnostic EC/bignum primitives, not a
FIPS "service"), they just don't contribute to any compliance claim. Treat `fips` as "the
underlying crypto library has been through FIPS 140-3 validation," not as "this application is
FIPS compliant" — check with whoever owns your compliance requirement before relying on it as one.

**Costs of enabling it:**
- A much larger build: `aws-lc-fips-sys` vendors the full AWS-LC-FIPS source tree (~53 MiB) and
  compiles considerably more (self-test and integrity-check infrastructure the non-FIPS module
  doesn't have).
- A mandatory runtime self-check: AWS-LC-FIPS links a startup hook into every dependent binary
  that calls `FIPS_mode()` and **aborts the process** if the runtime library isn't actually
  running in FIPS mode. This is by design (it's what "FIPS-validated" requires operationally),
  but it's a real behavior change worth knowing about before flipping the feature on in
  production — a binary that built fine can still abort at startup on a host where FIPS mode
  isn't available.
- Narrower pregenerated-bindings platform support than the default backend: currently
  `{x86_64,aarch64}-unknown-linux-{gnu,musl}` and `{x86_64,aarch64}-apple-darwin`. Other targets
  may still work (`bindgen` can generate bindings on the fly) but aren't validated here.
- Go and Perl toolchains are used for some AWS-LC-FIPS code generation *if present*, falling back
  to committed pre-generated code if not — so, same as the rest of this crate, no new hard
  build-time tool requirement, just potentially slower/less-optimized codegen without them.

## License

Licensed under either of

- [Apache License, Version 2.0]LICENSE-APACHE
- [MIT license]LICENSE-MIT

at your option, in addition to the vendored third-party licenses listed above (which govern the
statically-linked C/C++ code, not this crate's own Rust source) — see
[`LICENSE-THIRD-PARTY`](LICENSE-THIRD-PARTY) for the full text and attribution of each.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
this crate, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.