obj-core 1.1.2

Storage engine internals for the obj embedded document database (pager, WAL, B-tree, codec, catalog).
Documentation
[package]
name = "obj-core"
description = "Storage engine internals for the obj embedded document database (pager, WAL, B-tree, codec, catalog)."
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
repository.workspace = true
authors.workspace = true
homepage = "https://github.com/uname-n/obj"
documentation = "https://docs.rs/obj-core"
readme = "README.md"
keywords = ["database", "embedded", "storage", "wal", "btree"]
categories = ["database-implementations", "data-structures"]

[dependencies]
# `thiserror` keeps the public `Error` enum readable while still giving us
# the `Display` + `source()` chain the project plan requires. v1 is the
# stable mainline (Apache-2.0 OR MIT).
thiserror = "1"

# `crc32c` provides hardware-accelerated CRC32C (Castagnoli) on x86-64
# (SSE 4.2) and ARMv8 (CRC32C instructions), with a software fallback
# elsewhere. The on-disk format pins CRC32C as its integrity primitive;
# see `docs/format.md` § Checksum algorithm. Apache-2.0 OR MIT.
crc32c = "0.6"

# `rand` + `rand_chacha`: deterministic PRNG for the WAL generation
# salt and the fault-injection harness (M3 issues #14, #17). MIT OR
# Apache-2.0. We use the OS RNG at WAL open time for the initial salt
# (via `rand`) and ChaCha8 for reproducible fault sequences.
rand = "0.9"
rand_chacha = "0.9"

# `heapless`: fixed-capacity collections with no heap allocation. The
# B+tree (M4) uses `heapless::Vec<PageId, 32>` for its traversal stack,
# upholding power-of-ten Rule 3 (no heap allocation on the read /
# write hot path). MIT OR Apache-2.0.
heapless = "0.8"

# `serde` + `postcard`: the M5 document codec (L4) is built around
# postcard's stable v1.x wire format wrapped in obj's per-document
# header (`docs/format.md` § Document records). serde is required for
# user `Document` types; both crates are MIT OR Apache-2.0. The
# postcard `experimental-derive` and `use-std` features are NOT
# enabled — we use the bare allocvec/from_bytes path.
#
# `default-features = false` disables postcard's ONLY default feature,
# `heapless-cas`. That feature pulls in `heapless 0.7`, whose `cas`
# path drags in `atomic-polyfill 1.x` (RUSTSEC-2023-0089, unmaintained
# — see issues #39/#68). obj-core only uses `postcard::to_allocvec` /
# `from_bytes` / the `experimental` derive (the alloc path); it never
# touches postcard's heapless-output APIs (`to_vec` / `to_slice`), so
# dropping `heapless-cas` removes heapless 0.7 + atomic-polyfill from
# the tree with ZERO impact on the on-disk wire format. heapless 0.8
# (already on portable-atomic) remains a direct obj-core dependency
# for the B+tree traversal stack; this only removes the stale 0.7.
serde = { version = "1", features = ["derive"] }
postcard = { version = "1", default-features = false, features = ["alloc", "experimental-derive"] }

# Phase 2B (issue #7): `tracing` is an opt-in feature on obj-core.
# When the `tracing` feature is enabled on the parent obj-db crate
# (which propagates via `obj-core/tracing`), the pager's checkpoint
# path emits a `pager.checkpoint` span. The dependency is optional
# so the default build adds zero new transitive deps. Apache-2.0 OR
# MIT.
tracing = { version = "0.1", optional = true }

# Phase 3 (issue #8): `lz4_flex` provides the LZ4 raw-block codec
# the page compression feature uses (`compress_into` /
# `decompress_into` — fixed-output-buffer APIs that avoid heap
# allocation on the hot read/write path, upholding power-of-ten
# Rule 3). The dependency is optional and only pulled in when the
# `compression` feature is enabled. MIT.
lz4_flex = { version = "0.11", optional = true, default-features = false, features = ["std"] }

# Phase 4 (issue #9): `chacha20poly1305` is the RustCrypto AEAD
# used for at-rest page encryption. Pure-Rust, no `unsafe` on the
# library side (we forbid `unsafe` in `crypto.rs` ourselves). The
# `getrandom` feature on this crate is OFF; we surface
# `getrandom` directly below so the random-nonce generator
# returns a proper `Result` rather than panicking. Apache-2.0 OR
# MIT.
chacha20poly1305 = { version = "0.10", optional = true, default-features = false, features = ["alloc"] }
# Phase 4 (issue #9): HKDF-SHA256 KDF. Derives the per-file page
# key from a 32-byte user key + 32-byte `kdf_salt` carried in
# the page-0 header. The `info` string `obj-page-encryption-v1`
# is the versioning hook for future KDF migrations. MIT OR
# Apache-2.0.
hkdf = { version = "0.12", optional = true }
sha2 = { version = "0.10", optional = true, default-features = false }
# Phase 4 (issue #9): direct CSPRNG bridge. The pager generates
# a fresh 12-byte nonce for every page write via
# `getrandom::getrandom`; returning a `Result` keeps the
# encryption path Rule-7-compliant (no `unwrap` / `expect` on a
# CSPRNG failure). MIT OR Apache-2.0.
getrandom = { version = "0.2", optional = true }
# Issue #31: `zeroize` wipes in-memory key material on drop so the
# 32-byte master key (and the HKDF-derived per-file page key) does
# not linger in freed heap/stack after the owning `Config` / pager /
# WAL is dropped. RustCrypto crate, Apache-2.0 OR MIT, and ALREADY
# pulled into the `encryption` build transitively via
# `chacha20poly1305` (`cipher` -> `zeroize`), so naming it explicitly
# adds zero new dependencies to any build. Optional and gated on the
# `encryption` feature: the no-feature baseline build never sees it.
# The `serde` feature lets `Zeroizing<[u8; 32]>` round-trip through
# the existing derive-based pager `Config` serde impls unchanged.
zeroize = { version = "1.8", optional = true, default-features = false, features = ["alloc", "serde"] }

# `libc` exposes the OFD lock constants and `flock`/`fcntl` ABI the
# M6 file-locking layer needs (`F_OFD_SETLK` / `F_OFD_SETLKW`). On
# POSIX targets the lock layer calls `fcntl(F_OFD_SETLK*, struct
# flock*)` directly; `rustix::fs::fcntl_lock` does not expose the
# OFD variants. MIT OR Apache-2.0.
#
# `rustix` provides safe wrappers around `fcntl(F_FULLFSYNC)` and
# `fdatasync` for the `Full` durability path so this crate does not
# have to maintain its own `unsafe` syscall layer. It is Unix-only
# here because `rustix::fs` is gated off on Windows; Windows uses
# `std::fs::File::sync_all` (`FlushFileBuffers`) instead.
# Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT.
[target.'cfg(unix)'.dependencies]
libc = "0.2"
rustix = { version = "1", features = ["fs"] }

# `windows-sys` exposes `LockFileEx` / `UnlockFileEx` for the
# Windows lock path. MIT OR Apache-2.0. Feature flags pulled in are
# the smallest set that gives us the LockFileEx ABI plus the
# `OVERLAPPED` struct.
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [
    "Win32_Foundation",
    "Win32_Storage_FileSystem",
    "Win32_System_IO",
] }

[features]
# Phase 2B (issue #7): default to no features so the baseline build
# stays dependency-clean. Listed explicitly for parity with
# `crates/obj/Cargo.toml`.
default = []
# Opt-in build of the test-only fault-injection harness for callers
# outside `obj-core` (e.g. integration tests in this crate's
# `tests/` directory, sibling crates, and the crash-cycle harness).
# Inside `obj-core` `cfg(test)` ALSO enables it so unit tests can
# use the harness without the feature flag.
fault-injection = []
# Phase 2B (issue #7): emit tracing spans around hot pager paths
# (checkpoint, today). Off by default — the feature only activates
# the optional `tracing` dependency.
tracing = ["dep:tracing"]
# Phase 3 (issue #8): LZ4 per-page compression at the pager layer.
# Off by default — the feature only activates the optional
# `lz4_flex` dependency. Files written with `format_minor = 1`
# require this feature; older `format_minor = 0` files open
# without it.
compression = ["dep:lz4_flex"]
# Phase 4 (issue #9): ChaCha20-Poly1305 per-page at-rest
# encryption. Off by default — the feature pulls in the
# `chacha20poly1305`, `hkdf`, `sha2`, and `getrandom`
# dependencies and turns on the encrypted physical-page stride
# in the pager. Files written with `format_minor = 2` and
# `feature_flags` bit 1 set require this feature; older
# `format_minor < 2` files open without it.
encryption = [
    "dep:chacha20poly1305",
    "dep:hkdf",
    "dep:sha2",
    "dep:getrandom",
    # Issue #31: zeroize-on-drop for the master key, the derived
    # page key (`PageEncryptionKey`), and the WAL key (`WalKey`).
    "dep:zeroize",
]

[dev-dependencies]
# `proptest` powers the 10k randomised alloc/free property tests
# required by issue #5. MIT OR Apache-2.0.
proptest = "1"
# `tempfile` provides RAII temp directories for the file-backend tests.
# MIT OR Apache-2.0.
tempfile = "3"
# `criterion` powers the M4 collection-scan benchmark (#30). MIT OR
# Apache-2.0. We disable default features (`rayon`) — single-threaded
# measurement is sufficient for the storage-engine scan we care about.
criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] }

[[test]]
# The crash-cycle integration test imports `platform::fault`, which
# is gated behind the `fault-injection` feature (the harness must
# never compile into a release library build). Marking the test with
# `required-features` flips the feature on for `cargo test` runs of
# this target only.
name = "crash_cycles"
path = "tests/crash_cycles.rs"
required-features = ["fault-injection"]

[[bench]]
# M4 exit criterion: range-scan benchmark within 2× of design.md's
# collection-scan target. `harness = false` lets criterion own the
# test main.
name = "btree_range"
harness = false

[lints]
workspace = true