cachekit-core 0.7.2

LZ4 compression, xxHash3 integrity, AES-256-GCM encryption for byte payloads
Documentation
[package]
name = "cachekit-core"
version = "0.7.2"
edition = "2021"
authors = ["cachekit Contributors"]
description = "LZ4 compression, xxHash3 integrity, AES-256-GCM encryption for byte payloads"
rust-version = "1.91"
license = "MIT"
repository = "https://github.com/cachekit-io/cachekit-core"
homepage = "https://github.com/cachekit-io/cachekit-core"
documentation = "https://docs.rs/cachekit-core"
readme = "README.md"
keywords = ["lz4", "xxhash3", "aes-gcm", "encryption", "compression"]
categories = ["compression", "cryptography"]

[lib]
name = "cachekit_core"
# cdylib for C FFI, rlib for Rust crates, staticlib for static linking
crate-type = ["cdylib", "rlib", "staticlib"]

[dependencies]
# Error handling
thiserror = "2.0"

# Core serialization
serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11"

# MessagePack serialization (optional)
rmp-serde = { version = "1.3", optional = true }

# High-performance LZ4 compression (optional). Block API only, so no `frame`.
# Decode input is untrusted: `safe-decode` and `checked-decode` stay on
# explicitly (the decoder is the bounds-checked one under `forbid(unsafe_code)`
# whatever the encode feature is). On 64-bit targets the encoder is lz4_flex's
# unsafe one: same algorithm and byte-identical output, with unchecked pointer
# reads and writes instead of bounds-checked slice access. Other targets keep
# the safe encoder (target table below). See SECURITY.md, "Decompression limits".
lz4_flex = { version = "0.12", default-features = false, features = ["std", "safe-decode", "checked-decode"], optional = true }

# Fast non-cryptographic hashing for data integrity (optional)
# xxHash3-64: fast enough to run on every store and retrieve; it detects corruption, while
# the AES-GCM auth tag provides security
xxhash-rust = { version = "0.8", features = ["xxh3"], optional = true }

# Encryption dependencies (all optional, gated by encryption feature)
# Uses HKDF-SHA256 for key derivation (NOT Blake2b - that's only for Python cache keys)
# ring is native-only (see [target.'cfg(not(target_arch = "wasm32"))'.dependencies])
zeroize = { version = "1.8", features = ["derive"], optional = true }
hkdf = { version = "0.12", optional = true }
sha2 = { version = "0.10", optional = true }
hmac = { version = "0.12", optional = true }
generic-array = { version = "0.14", optional = true }

# wasm32 RNG: getrandom with JS feature for wasm32-unknown-unknown targets
getrandom = { version = "0.2", features = ["js"], optional = true }

# RustCrypto: pure-Rust AES-256-GCM for wasm32 targets (ring requires clang + C asm)
aes-gcm = { version = "0.10", features = ["zeroize"], optional = true }
aes = { version = "0.8", features = ["zeroize"], optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# ring: hardware-accelerated AES-256-GCM for native targets only.
# Does NOT compile on wasm32-unknown-unknown (requires clang for C asm).
# Kept optional so it is only compiled when the `encryption` feature is active.
ring = { version = "0.17", optional = true }

[target.'cfg(not(target_pointer_width = "64"))'.dependencies]
# Targets below 64 bits (i686, armv7, win32, wasm32) keep lz4_flex's safe
# encoder. lz4_flex 0.12's output bound, `input_len * 110 / 100`, wraps in a
# 32-bit usize above about 37 MiB, inside this crate's 512 MiB input cap: the
# unsafe encoder would then write past its allocation, where the safe one
# panics. On wasm32 (the Workers build) the cached value, the encoder and the
# tenant keys also share one linear memory, and Miri and the fuzzers run 64-bit
# native only. Cargo unions this `safe-encode` with the features above for
# these targets only.
lz4_flex = { version = "0.12", default-features = false, features = ["safe-encode"], optional = true }

[build-dependencies]
# C header generation (required for ffi feature)
cbindgen = { version = "0.29", optional = true }

[dev-dependencies]
serde_json = "1.0"
blake2 = "0.10"
hex = "0.4"
# Unconditional for tests: the optional sha2 above only exists under `encryption`,
# but the wire-format vector fixture pin must verify under every feature set.
sha2 = "0.10"
aes-gcm = { version = "0.10", features = ["zeroize"] }

# Native-only: neither builds for wasm32-unknown-unknown, and either would
# break the wasm32 test build below. proptest pulls getrandom 0.3, which
# refuses the target; criterion's default rayon feature refuses it too.
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
proptest = "1.4"
criterion = { version = "0.5", features = ["html_reports"] }

# wasm32 test harness for tests/wire_format_constructed.rs (libtest's #[test]
# does not run on wasm32-unknown-unknown). Exact-pinned: it releases in
# lockstep with wasm-bindgen, and the wasm-bindgen-test-runner binary in CI
# must match the locked wasm-bindgen version (bump this, Cargo.lock and the
# runner pin in ci.yml together).
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "=0.3.71"

[features]
default = ["compression", "checksum", "messagepack"]

# Core features
compression = ["dep:lz4_flex"]
checksum = ["dep:xxhash-rust"]
messagepack = ["dep:rmp-serde"]

# Encryption (AES-256-GCM with HKDF-SHA256 key derivation)
# Native: ring provides hardware-accelerated AES-256-GCM (target-conditional dep above)
# wasm32: aes-gcm (pure Rust) is used instead — automatically selected via cfg(target_arch)
# aes-gcm/aes/getrandom compile on native but are dead code in non-test native builds;
# the compiler optimizes them out.
encryption = [
    "dep:ring",
    "dep:zeroize",
    "dep:hkdf",
    "dep:sha2",
    "dep:hmac",
    "dep:generic-array",
    "dep:aes-gcm",
    "dep:aes",
    "dep:getrandom",
]

# C FFI layer (generates include/cachekit.h)
ffi = ["dep:cbindgen"]

# wasm32 support: alias for encryption (deps now folded into encryption feature)
wasm = ["encryption"]

# Kani formal verification configuration
# Provides mathematical proofs of memory safety for unsafe code and FFI boundaries
[package.metadata.kani]
default-unwind = 10
output-format = "terse"
concrete-playback = "print"
enable-unstable = false
solver = "cadical"

[package.metadata.kani.unstable]
stubbing = false

# Lint configuration
[lints.rust]
# Suppress benign warnings about cfg(kani) which is set by Kani verifier
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] }

# Release profile for cachekit-core's own release builds.
#
# Cargo only honors the ROOT package's [profile.release]. Workspace consumers
# (cachekit-rs, cachekit-py via PyO3, cachekit-ts via NAPI) define their own
# [profile.release] and use it instead — this profile does NOT propagate to
# them. The value of setting it here is:
#
#   1. `cargo bench` from inside cachekit-core runs under a stable, fully
#      optimized profile (Phase 3 perf measurement depends on this).
#   2. `cargo build --release` and `cargo publish` produce a consistently
#      optimized artifact.
#   3. Standalone binary consumers WITHOUT their own [profile.release]
#      inherit this one instead of Cargo defaults (codegen-units=16, lto=false).
#
# Mirrors cachekit-py/rust/Cargo.toml for consistency.
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"
debug = "line-tables-only"

# Bench profile: inherits release, but keeps symbols so callgrind/perf can
# attribute cost to functions. Release's strip = "symbols" also drops the
# line tables, which would make debug = "line-tables-only" a no-op here.
[profile.bench]
strip = false
debug = "line-tables-only"

# hot_path also benches ZeroKnowledgeEncryptor, so it needs `encryption`;
# without it a plain `cargo bench` skips this target instead of failing to compile.
[[bench]]
name = "hot_path"
harness = false
required-features = ["encryption"]

# Instruction-count gate (`make perf-ir`). It needs valgrind, so `bench = false`
# keeps it out of a plain `cargo bench`; `--bench perf_ir` still selects it, and
# `clippy --all-targets` still compiles it.
[[bench]]
name = "perf_ir"
harness = false
bench = false
required-features = ["encryption"]

# The README's Performance table. It times ByteStorage's store and retrieve, which need the
# three default features, and encrypt/decrypt.
[[example]]
name = "bench_throughput"
required-features = ["compression", "checksum", "messagepack", "encryption"]