phantom-protocol 0.2.0

Post-quantum-secure L4/L6 universal transport framework — hybrid X25519+ML-KEM-768 / Ed25519+ML-DSA-65, multi-path, UniFFI bindings
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
[package]
name = "phantom-protocol"
version = "0.2.0"
edition = "2021"
rust-version = "1.93"
description = "Post-quantum-secure L4/L6 universal transport framework — hybrid X25519+ML-KEM-768 / Ed25519+ML-DSA-65, multi-path, UniFFI bindings"
license = "Apache-2.0"
repository = "https://github.com/snaart/phantom_protocol"
documentation = "https://docs.rs/phantom-protocol"
readme = "README.md"
keywords = ["post-quantum", "transport", "cryptography", "networking", "ml-kem"]
categories = ["network-programming", "cryptography", "no-std"]

[features]
# `classical-crypto` (SUPPLY-03) is folded into `default` so the normal
# (non-FIPS) build pulls the classical AEAD + KEM substrate (`ring` /
# `x25519-dalek`). It is intentionally NOT implied by `std`, because the
# `fips` build also implies `std` and must NOT link them — under
# `--features fips` the AEAD swaps to `aws-lc-rs` and the classical KEM half
# swaps to ECDH-P-256, so `ring` and `x25519-dalek` are genuinely unused.
# A FIPS build therefore drops `default` (`--no-default-features --features
# fips,...`); every other std build keeps `default` (or, for the
# `--no-default-features` cross-target rows in cross.yml, names
# `classical-crypto` explicitly).
default = ["compression-zstd", "std", "bindings", "classical-crypto"]

# UniFFI bindings surface. Pulled in by `default` so the historical
# native-FFI consumers (Swift / Kotlin / Python / C bindings under
# `tests/bindings/`) compile unchanged. Split out from `std` because
# UniFFI's exported-symbol metadata is incompatible with
# `wasm-component-ld` (the wasm32-wasip2 linker); the WASI guest in
# `core/tests/fixtures/wasi-guest/` therefore opts out of `default`
# and explicitly enables `["std", "wasi-leg"]` only.
bindings = ["std", "dep:uniffi"]

# `uniffi-cli` (SUPPLY-01): the codegen-only surface. Adds uniffi's `cli`
# feature (which drags in `clap` / `cargo_metadata` / `tempfile`) and is the
# `required-features` for the `uniffi-bindgen` binary. Kept OUT of `bindings`
# so a normal lib / server / mobile build links the FFI scaffolding WITHOUT the
# build-time CLI dependency tree. The `tests/bindings/generate_*.sh` scripts
# enable it when they regenerate the language bindings.
uniffi-cli = ["bindings", "uniffi/cli"]

# `std` (Phase 3.6): the standard "everything on" build. Pulls every std-bound
# optional dep into the graph and unlocks all top-level modules (`api`, `runtime`,
# `transport/legs/{tcp,kcp,faketls,websocket}`, `networks`, `config`, `validation`,
# `crypto/*`, `security/*`, std-bound parts of `transport/*`). Default-on so the
# historical surface keeps compiling unchanged.
#
# Bare-metal targets pass `--no-default-features --features embedded,no-std` to
# drop this entirely.
std = [
    "dep:tokio",
    "dep:tokio-util",
    "dep:futures",
    "dep:async-trait",
    "dep:parking_lot",
    "dep:dashmap",
    "dep:arc-swap",
    "dep:env_logger",
    "dep:tracing",
    "dep:time",
    "dep:argon2",
    "dep:crossbeam-queue",
    "dep:crossbeam-utils",
    "dep:lz4_flex",
    "dep:base64",
    "dep:once_cell",
    "dep:anyhow",
    "dep:thiserror",
    "dep:rand",
    # NB: `ring` + `x25519-dalek` are NOT listed here — they live behind the
    # `classical-crypto` feature so the `fips` build (which also implies `std`)
    # can drop them. See the `classical-crypto` feature below (SUPPLY-03).
    "dep:ed25519-dalek",
    "dep:ml-kem",
    "dep:ml-dsa",
    "dep:log",
    "dep:hmac",
    "dep:borsh",
    "dep:hex",
    "dep:bitflags",
    "dep:zeroize",
    "dep:subtle",
    "dep:blake3",
    "dep:hkdf",
    "dep:sha2",
    "dep:getrandom",
    # Native (non-wasm32) optional deps; activating these on a wasm32 target
    # is a no-op because the deps themselves are gated by the
    # `[target.'cfg(not(target_arch = "wasm32"))']` table.
    "dep:socket2",
    "bytes/std",
    "serde/std",
    "header-protection",
]

# Classical (non-FIPS) crypto substrate (SUPPLY-03). Pulls `ring`
# (HW-accelerated AES-256-GCM + ChaCha20-Poly1305 AEAD) and `x25519-dalek`
# (the classical half of the hybrid KEM). Folded into `default` rather than
# `std` on purpose: the `fips` build implies `std` but swaps the AEAD to
# `aws-lc-rs` and the classical KEM to ECDH-P-256, so it must NOT link these.
# Keeping them out of `std` lets a FIPS build (`--no-default-features
# --features fips,...`) drop them entirely (verifiable with
# `cargo tree --no-default-features --features fips -i ring`). Every non-FIPS
# std build pulls them via `default`; the `--no-default-features` cross-target
# rows in `.github/workflows/cross.yml` name `classical-crypto` explicitly.
classical-crypto = ["dep:ring", "dep:x25519-dalek"]

# Header protection (QUIC RFC 9001 §5.4, T4.6). Pulls the RustCrypto `aes` +
# `chacha20` mask primitives. Folded into `std` because the data-plane codec
# (`crypto`/`transport::session`) that applies HP is itself std-gated; the
# bare-metal `no-std` subset compiles none of it, so it needs neither crate.
# Under `--features fips` the AES mask uses `aws_lc_rs::cipher` instead, but the
# RustCrypto crates still link (dead) — the same posture as `ring` under fips.
header-protection = ["dep:aes", "dep:chacha20"]

# zstd compression. Disabled when building for WASM / embedded targets where
# the C bindings in `zstd-sys` are unavailable. lz4_flex (pure-Rust) remains
# the always-on fallback. Implies `std`.
compression-zstd = ["dep:zstd", "std"]

# `EmbeddedLeg` transport over `embedded-io-async` byte streams (Phase 3.4).
# Pure-Rust + no_std-friendly; safe to enable on the host for `cargo test`.
embedded = ["dep:embedded-io-async", "dep:async-lock"]

# `no-std` foundation (Phase 3.6). The audit-gate marker. When ON, the
# in-subset modules (`errors`, `transport::session_transport`, `transport::legs::
# embedded`) compile with `#![no_std] + extern crate alloc`. Whole-crate
# bare-metal compilation additionally requires `--no-default-features` to drop
# the `std` feature and all the std-bound optional deps it gates.
no-std = []

# FIPS 140-3 posture. Pulls in `aws-lc-rs` (FIPS-validated AWS-LC
# bindings) as the substrate for the FIPS-approved primitive set:
#
#   - classical KEM half: X25519 → ECDH-P-256
#   - AEAD: `ring` AES-GCM → `aws-lc-rs`
#   - ChaCha20-Poly1305 dropped (not FIPS-approved)
#   - KDF: blake3 → HKDF-SHA256 everywhere blake3 is used as a KDF
#   - RNG: `aws-lc-rs::rand` (SP 800-90A CTR_DRBG) instead of getrandom
#   - `crypto::self_tests::run_post()` wired into the bind/connect bootstrap
#
# Implies `std`; mutually exclusive with `no-std` (enforced by a
# `compile_error!` in `core/src/lib.rs`). Native-only — `aws-lc-rs`
# does not support wasm32 targets. See `docs/compliance/fips-readiness.md`.
fips = ["std", "dep:aws-lc-rs"]

# WasiLeg transport + WasiRuntime for `wasm32-wasi*` targets (Section B
# of the pre-1.0 deferred-followups plan). Pulls in the `wasi` crate
# (>= 0.14, Preview 2 stable) only on `cfg(target_os = "wasi")`, so
# enabling this feature on non-WASI hosts is a no-op at the dep level.
# Mutually exclusive with `wasm32-unknown-unknown` (browser) — the
# `wasi` crate's WIT bindings do not target the browser; a
# `compile_error!` in `core/src/lib.rs` rejects the combination
# explicitly.
wasi-leg = ["std", "dep:wasi"]

# `MimicTlsLeg` (queue item #13) — a TLS-over-TCP active-mimicry `SessionTransport`
# that makes a Phantom flow look like ordinary HTTPS to an on-path observer (a
# synthetic TLS 1.3 handshake "theater" prelude, then Phantom inside TLS
# ApplicationData records). Off by default; native-only (the module is gated
# `cfg(all(feature = "mimicry", not(target_arch = "wasm32")))` and rides
# `tokio::net`). Framing-only, NO outer AEAD — the inner Phantom ciphertext is
# already random-looking; the leg only adds TLS record framing. No `WIRE_VERSION`
# change (outer, leg-local framing). The outer TLS is anti-DPI obfuscation ONLY,
# and is DETECTABLE by active probing — see the module head + threat model.
mimicry = ["std"]

# OpenTelemetry pipeline (Phase 8). Off by default; the `core` library exposes
# OTel `Counter` / `Histogram` / `UpDownCounter` instruments to the embedder
# when this is on. The library never installs a `MeterProvider` /
# `TracerProvider` — that's the embedder's job (see `server/src/telemetry.rs`).
# Implies `std`; mutually compatible with `compression-zstd`. Pulls
# `opentelemetry` + `opentelemetry_sdk` + `tracing-opentelemetry` so
# `tracing` spans bridge into OTel traces.
telemetry-otel = [
    "std",
    "dep:opentelemetry",
    "dep:opentelemetry_sdk",
    "dep:tracing-opentelemetry",
]

[dependencies]
# --- Async & Runtime (std-only) ---
# `tokio` is split per target (Phase 3.5 conditional compilation matrix):
# - Cross-target features: sync primitives, time, io-util, macros, single-thread
#   runtime. These work on both native and wasm32.
# - Native-only features (`net`, `rt-multi-thread`, `signal`, `process`, `fs`,
#   `io-std`) are added in the `[target.'cfg(not(target_arch = "wasm32"))']`
#   block at the bottom of this file.
tokio = { version = "1.36", default-features = false, optional = true, features = [
    "sync", "macros", "rt", "time", "io-util",
] }
tokio-util = { version = "0.7", optional = true, features = ["codec"] }
futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true }

# --- Serialization & Utils ---
# `bytes` and `serde` have first-class no_std modes (`default-features = false`
# + the `alloc` feature). Kept as required deps so the no-std subset can use
# them; the `std` feature wires the `std` cargo feature back in for richer
# `std::io::Read`-style integration.
bytes = { version = "1", default-features = false, features = ["serde"] }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
# Wire-stability pin. `borsh` encodes the handshake messages (the packet header
# + `PhantomPacket` use a hand-rolled big-endian codec, `PacketHeader::to_wire`,
# so they have no serialization dependency). The exact serialized bytes are
# frozen by `core/tests/wire_vectors.rs` (and the transcript hash by
# `transport::handshake::tests::transcript_hash_wire_vector`). A patch/minor bump
# to `borsh` can silently shift the handshake bytes and break interop with no
# `WIRE_VERSION` change, so it is pinned to an exact version — bump it only as a
# deliberate wire change, paired with a vector regeneration.
borsh = { version = "=1.6.1", default-features = false, optional = true, features = ["derive"] }
hex = { version = "0.4", default-features = false, optional = true, features = ["alloc"] }
base64 = { version = "0.22", default-features = false, optional = true, features = ["alloc"] }
anyhow = { version = "1", optional = true }
thiserror = { version = "2", optional = true }
log = { version = "0.4", optional = true }
env_logger = { version = "0.11", optional = true }
rand = { version = "0.8", optional = true }
once_cell = { version = "1.18", optional = true }
subtle = { version = "2.5", default-features = false, optional = true }  # Constant-time equality for secrets (cookies, tokens)
tracing = { version = "0.1", optional = true }  # Structured spans / events for observability (Phase 4.5)

# --- Crypto (std-only) ---
x25519-dalek = { version = "2", optional = true, features = ["static_secrets", "zeroize"] }
ed25519-dalek = { version = "2", optional = true, features = ["zeroize", "rand_core"] }
ring = { version = "0.17", optional = true }  # HW-accelerated AES-GCM (ARM64 AES intrinsics / x86 AES-NI) + ChaCha20-Poly1305
# --- Header protection (QUIC RFC 9001 §5.4) primitives, T4.6 ---
# Raw AES-256-ECB single-block (`aes::Aes256`) + ChaCha20 keystream
# (`chacha20::ChaCha20`) for the per-packet header-protection mask — `ring` /
# `aws-lc-rs` AEAD expose neither. Both are pure-Rust, `#![no_std]`, with a
# constant-time soft fallback on every target (no `cpufeatures` off x86/aarch64),
# so they cross-compile to wasm32 + thumbv7em unchanged. `aes 0.8` / `chacha20 0.9`
# share one `cipher 0.4` / `crypto-common 0.1` generation (already in-tree via
# ml-kem → digest 0.10). Pulled by the `header-protection` feature (folded into
# `std`); under `--features fips` the AES mask routes through `aws_lc_rs::cipher`
# instead and the ChaCha mask is unreachable (suite pinned to AES) — these crates
# then link dead, exactly as `ring` does under fips.
aes = { version = "0.8", default-features = false, optional = true }
chacha20 = { version = "0.9", default-features = false, optional = true }
# Phase 5.1: switched from `pqcrypto-kyber` / `pqcrypto-dilithium` (which
# wrap NIST PQC round-3 C reference impls and require libc) to the
# RustCrypto pure-Rust FIPS-203 / FIPS-204 implementations. These compile
# on every target (server, mobile, embedded, wasm32) without C bindings.
# Wire-incompatible with the prior build — bytes laid out per FIPS, not
# per NIST round-3.
# `deterministic` exposes `generate_deterministic` / `encapsulate_deterministic`
# (additive API; no change to the production random path). Used by the NIST KAT
# test (`core/tests/nist_kat.rs`) to byte-match published FIPS-203 vectors.
ml-kem = { version = "0.2", optional = true, features = ["deterministic"] }
ml-dsa = { version = "0.1.1", optional = true }   # stable release; a release candidate must not ship in a 1.0
# `signature` traits are re-exported via `ml_dsa::{Signer, Verifier}` from
# the version ml-dsa pins (3.x); we do not need a direct dependency here.
hkdf = { version = "0.13", optional = true }
sha2 = { version = "0.11", optional = true }
blake3 = { version = "1.5", optional = true }
getrandom = { version = "0.2", optional = true }
bitflags = { version = "2.13", optional = true }
parking_lot = { version = "0.12", optional = true }  # Fast mutex for buffer pool
arc-swap = { version = "1", optional = true }        # Lock-free atomic swap for the CryptoState rekey path (Phase 1.5)
lz4_flex = { version = "0.13.1", optional = true }   # Fastest pure-Rust LZ4 (safe, no C bindings); >=0.13.1 floor avoids yanked 0.11.5 (RUSTSEC-2026-0041)
# zstd pinned to a release version. Optional (gated by `compression-zstd`
# feature) so WASM / embedded builds can opt out of the C bindings in
# zstd-sys, falling back to lz4_flex.
zstd = { version = "0.13", optional = true }

# --- Networking ---
# TCP/UDP-using crates are non-wasm-only (Phase 3.5). Moved to the
# `[target.'cfg(not(target_arch = "wasm32"))']` block at the bottom.
time = { version = "0.3", optional = true }

# --- FFI ---
uniffi = { version = "0.31", optional = true, features = ["tokio"] }
zeroize = { version = "1.8.2", optional = true, features = ["derive"] }
argon2 = { version = "0.5.3", optional = true }
crossbeam-queue = { version = "0.3.12", optional = true }
crossbeam-utils = { version = "0.8", optional = true }  # CachePadded for observability hot-path atomics

# --- OpenTelemetry (optional, behind the `telemetry-otel` feature, Phase 8) ---
# Library-side: only the API + SDK crates. The OTLP exporter and any tonic
# transport live in `server/` (or any embedder), keeping the core library
# transport-runtime-agnostic and matching the existing "core defines, server
# wires" boundary used for HTTP metrics today.
opentelemetry = { version = "0.32", optional = true, default-features = false, features = ["metrics", "trace"] }
opentelemetry_sdk = { version = "0.32", optional = true, default-features = false, features = ["metrics", "trace", "rt-tokio"] }
tracing-opentelemetry = { version = "0.33", optional = true, default-features = false, features = ["tracing-log"] }
hmac = { version = "0.13", optional = true }
dashmap = { version = "6.1.0", optional = true }

# --- Embedded transport (optional, behind the `embedded` feature) ---
# Pure-Rust, no_std + alloc. `embedded-io-async` is the byte-stream trait the
# `EmbeddedLeg` is generic over; `async-lock` is the async mutex guarding the split
# read/write handles (chosen over `embassy-sync`, which drags in `heapless` + a
# link-time `critical-section` impl). The crate MSRV is now 1.93, so the prior
# `async-lock < 3.4` cap (held only for the old 1.75 MSRV — 3.4 needs 1.85) is
# dropped; `embedded-io-async` stays at 0.6 to avoid a 0.7 API migration for now.
embedded-io-async = { version = "0.6", optional = true }
async-lock = { version = ">=3, <4", optional = true, default-features = false }

[build-dependencies]
uniffi = { version = "0.31", features = ["build"] }

# ─── Native (non-wasm32) dependencies (Phase 3.5) ────────────────────────
# TCP/UDP transport legs and raw sockets live here. wasm32 builds
# skip these entirely and use the `WebSocketLeg` instead (see below).
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.36", optional = true, features = [
    "net", "rt-multi-thread", "signal", "process", "fs", "io-std",
] }
# socket2 gives portable access to SO_REUSEPORT (Phase 2.9 — Linux
# multi-accept). Already in the transitive tree via tokio so this is
# free from a build-size standpoint.
socket2 = { version = "0.6", optional = true }
# `aws-lc-rs` is the FIPS-validated substrate behind the `fips` Cargo
# feature (AES-256-GCM, ECDH-P-256, HKDF-SHA256, CTR_DRBG). Pinned to
# 1.x, declared only in this native-only target block because the
# AWS-LC C library + bindgen pipeline does not target wasm32. macOS
# hosts may need `brew install pkg-config openssl@3` for the first
# build (see CONTRIBUTING.md).
#
# Feature selection: `default-features = false` + `features = ["fips"]`.
# In aws-lc-rs 1.17, the `fips` feature is mutually exclusive with the
# default `aws-lc-sys` backend — it activates `dep:aws-lc-fips-sys`
# (AWS-LC-FIPS 3.0.x) as the sole backend. Listing both `aws-lc-sys`
# and `fips` would attempt to enable both backends and bloat the link.
aws-lc-rs = { version = "1.17", optional = true, default-features = false, features = ["fips"] }

# ─── Linux-only dependencies ──────────────────────────────────────────────
# `libc` backs the Linux UDP fast paths in `transport/udp_transport` — the
# `SO_MAX_PACING_RATE` pacing offload (`setsockopt`) and the `sendmmsg` batch
# send — all gated on `cfg(target_os = "linux")`. Declared in a Linux-only
# target block so the direct dependency edge matches the only place the code
# names `libc::`; macOS / Windows / wasm / WASI builds never pull it here.
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"

# ─── Browser-WASM-only dependencies (Phase 3.3) ───────────────────────────
# Pulled in only for `wasm32-unknown-unknown` builds (the browser target).
# Non-WASM hosts and WASI targets (wasm32-wasi*) never see these crates in
# their dependency graph.
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
web-sys = { version = "0.3", features = [
    "BinaryType",
    "Blob",
    "CloseEvent",
    "console",
    "ErrorEvent",
    "Event",
    "MessageEvent",
    "WebSocket",
] }
# Phase 3.8 partial: enable getrandom's "js" feature for wasm32-unknown-
# unknown so it falls back to `crypto.getRandomValues` rather than
# emitting a compile_error!. Non-wasm targets and WASI targets ignore
# this — it's a no-op there.
getrandom = { version = "0.2", features = ["js"] }
# Force-enable the `wasm_js` feature on getrandom 0.4 for wasm32-unknown-
# unknown. getrandom 0.4 is pulled in transitively by `ml-dsa →
# crypto-common 0.2` and `uniffi → tempfile`. The wasm path requires the
# `wasm_js` Cargo feature (not a --cfg rustflag) to compile in the
# js-sys/wasm-bindgen backend. WASI builds get the default getrandom path
# (which uses WASI's `random_get`).
getrandom04 = { package = "getrandom", version = "0.4", features = ["wasm_js"] }

# ─── WASI-only dependencies (Section B — wasi-leg) ────────────────────────
# WASI Preview 2 bindings for the WasiLeg transport + WasiRuntime. Lives
# under `cfg(target_os = "wasi")` so it is pulled in only when the
# `wasi-leg` Cargo feature is active AND the build target is a WASI
# triple (wasm32-wasi, wasm32-wasip1, wasm32-wasip2). Non-WASI builds
# (including wasm32-unknown-unknown) never see this crate in their graph.
[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.14", optional = true }

[dev-dependencies]
blake3 = "1.5"
ed25519-dalek = { version = "2", features = ["rand_core"] }
rand = "0.8"
uniffi = { version = "0.31", features = ["bindgen-tests"] }
criterion = { version = "0.5", features = ["html_reports", "async_tokio"] }
tokio-test = "0.4"
proptest = "1"  # property-based testing harness
serde_json = "1"  # parse the committed NIST ACVP KAT vectors (core/tests/nist_kat.rs)

# UniFFI codegen entry point. Gated on `uniffi-cli` (SUPPLY-01) so a default
# `cargo build` skips it entirely — the binary needs uniffi's `cli` feature for
# `uniffi_bindgen_main`, and that feature (clap / cargo_metadata / tempfile)
# must not leak into the runtime library / server / mobile artifacts.
[[bin]]
name = "uniffi-bindgen"
path = "src/bin/uniffi-bindgen.rs"
required-features = ["uniffi-cli"]

[[bench]]
name = "transport_bench"
harness = false

[[bench]]
name = "buffer_pool_bench"
harness = false

[[bench]]
name = "syn_flood_bench"
harness = false

# `protocol_comparison` benches the classical `x25519-dalek` keygen directly,
# so it requires the `classical-crypto` feature (SUPPLY-03) — under `--features
# fips` that crate is not linked, and the bench is meaningless there anyway.
[[bench]]
name = "protocol_comparison"
harness = false
required-features = ["classical-crypto"]

[[bench]]
name = "observability_bench"
harness = false

# `embedded_demo` depends on `embedded-io-async`, which is only pulled in by the
# `embedded` feature — gate it so the default `cargo test` / `cargo build
# --examples` skip it instead of failing to resolve the crate.
[[example]]
name = "embedded_demo"
required-features = ["embedded"]

# `crypto_bench` calls `ring::aead` directly to micro-benchmark the classical
# AEAD, so it requires the `classical-crypto` feature (SUPPLY-03) — `ring` is
# not linked under `--features fips`. Declared explicitly (it would otherwise
# be an auto-discovered example) so the `required-features` gate applies and a
# `cargo build --examples --features fips` skips it instead of failing to
# resolve `ring`.
[[example]]
name = "crypto_bench"
required-features = ["classical-crypto"]

# NOTE: `[profile.release]` lives in the workspace root `Cargo.toml`. Declaring
# release profiles in non-root members is silently ignored by cargo (see warning
# from `cargo check`). Keep this comment as a reminder for future maintainers.

[lib]
name = "phantom_protocol"
crate-type = ["lib", "cdylib"]