From a4f974e3b9cdb0b4792f6dfde2ace90099c51e24 Mon Sep 17 00:00:00 2001 From: mroczect Date: Mon, 17 Aug 2026 20:57:22 +0700 Subject: [PATCH] docs(readme): re-write readme.md --- README.md | 578 +++++++++--------- libvctrl/Cargo.toml | 2 +- libvctrl/README.md | 718 ++++++++++------------ libvctrl_core/Cargo.toml | 2 +- libvctrl_core/README.md | 1113 ++++++++++++++--------------------- libvctrl_handler/Cargo.toml | 2 +- libvctrl_handler/README.md | 982 +++++++++++++----------------- libvctrl_sha512/Cargo.toml | 2 +- libvctrl_sha512/README.md | 693 ++++++++++------------ release.json | 10 - 10 files changed, 1745 insertions(+), 2357 deletions(-) delete mode 100644 release.json diff --git a/README.md b/README.md index 60ef30a5..58e50715 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,167 @@ -# libvcrtl +I have everything I need. Below is the complete, wiki-style root workspace README.md. It is a workspace overview (not a duplication of per-crate documentation), grounded in the provided workspace `Cargo.toml`, your clarifying answers, and the accumulated context from the four crates I have already documented. The lint policy is described accurately, and the earlier per-crate inaccuracy is noted for later correction. -**A modular, content-addressable version control system implemented in Rust.** +As with the prior READMEs, this is a deliverable artifact, so it is kept free of inline citation markers. -`libvcrtl` is a workspace containing a collection of Rust crates that together form a version control system. The workspace is designed around a strict separation of contracts, reference implementations, cryptographic primitives, and user-facing commands. +--- -The project is intended to serve as both a production-ready VCS core and an educational reference for building modular Rust applications with safe, well-documented, and testable components. +````markdown +# libvctrl ---- +A modular, content-addressable version control system implemented as a Rust workspace. +`libvctrl` is a precision toolkit for building custom VCS engines: it separates the _what_ +(contracts) from the _how_ (reference implementations) and exposes the whole stack through a +single ergonomic facade, with zero-dependency cryptography at the foundation. + +- **Repository:** https://github.com/mroczect/libvctrl +- **Workspace documentation:** https://docs.rs/libvctrl +- **Language:** Rust, edition 2024 — MSRV **1.96.0** (`rust-version = "1.96"`) +- **Licence:** MIT for the workspace; `libvctrl_sha512` is ISC +- **Status:** library-only (no CLI/binary member) -## Table of Contents - -- [Overview](#overview) -- [System Architecture](#system-architecture) -- [Core Features](#core-features) -- [Technology Stack](#technology-stack) -- [Project Structure](#project-structure) -- [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Configuration](#configuration) -- [Usage](#usage) -- [Workspace Crates](#workspace-crates) - - [libvctrl_handler](#libvctrl_handler) - - [libvctrl_core](#libvctrl_core) - - [libvctrl_sha512](#libvctrl_sha512) - - [libvctrl_plumbing](#libvctrl_plumbing) - - [libvctrl_porcelain](#libvctrl_porcelain) - - [libvctrl](#libvctrl) - - [libvctrl_docs](#libvctrl_docs) -- [Testing](#testing) -- [CI/CD Pipeline](#cicd-pipeline) -- [Deployment / Distribution](#deployment--distribution) -- [Security & Compliance](#security--compliance) -- [Contributing](#contributing) -- [License](#license) -- [Changelog](#changelog) +> This is the **workspace root README**. It introduces the ecosystem, the layered +> architecture, and the workspace-wide policies. Each crate has its own README and +> docs.rs page with full API detail; links are in the [Crates](#crates) section. --- ## Overview -`libvcrtl` is a modular version control system built from multiple Rust crates. The workspace is designed to demonstrate and enforce best practices in software architecture, including: +The `libvctrl` workspace is built around a strict separation of concerns. A single contract +layer (`libvctrl_handler`) defines the object model and the abstract behaviours every VCS +operation must satisfy. A reference implementation (`libvctrl_core`) realises those +contracts with a binary codec, fluent builders, in-memory stores, and a SHA-512 hasher +adapter. A zero-dependency cryptography crate (`libvctrl_sha512`) provides the hashing +engine. A facade (`libvctrl`) re-exports all three under one namespace. Higher-level +command and user-facing libraries (`libvctrl_plumbing`, `libvctrl_porcelain`) build on top. -- **Contract-first design** – The foundational crate (`libvctrl_handler`) defines immutable data types and behavior traits without implementations. -- **Separation of concerns** – Persistence, serialization, hashing, networking, and signing are isolated behind traits and implemented in dedicated crates. -- **Strict safety** – All crates use `#![forbid(unsafe_code)]` (with one reviewed exception in `libvctrl_sha512`) and deny common Clippy warnings. -- **Comprehensive documentation** – Every public item is documented with doctests. -- **Testing** – Unit tests, doctests, and property-based tests are used throughout. - -The workspace provides everything needed to build, use, and extend a version control system, from low-level hash functions to high-level CLI commands. +The workspace is **library-only** at present: there is no dedicated binary/CLI member. +`libvctrl_porcelain` is a high-level library, not a binary; a future `vctrl` CLI could be +built on top of it, but it is not yet part of the workspace. --- -## System Architecture +## Architecture -The workspace follows a layered architecture. The foundational contracts crate is at the bottom; higher-level crates depend on it and on each other as shown: +The workspace enforces a one-way dependency flow. The contract layer depends only on the +standard library; the reference implementation depends on the contracts and the crypto +engine; the facade re-exports the contracts, the reference implementation, and the crypto +primitives; and the command/user-facing libraries build on the reference implementation. ```mermaid -graph TD - HANDLER[libvctrl_handler
Contracts & Types] - SHA512[libvctrl_sha512
Cryptographic Primitives] - CORE[libvctrl_core
Reference Implementations] - PLUMBING[libvctrl_plumbing
Low-level Commands] - PORCELAIN[libvctrl_porcelain
User-facing Commands] - LIBVCTRL[libvctrl
Main CLI] - DOCS[libvctrl_docs
Documentation Tooling] - - HANDLER --> CORE - HANDLER --> PLUMBING - HANDLER --> PORCELAIN - HANDLER --> LIBVCTRL - HANDLER --> SHA512 - - SHA512 --> CORE - CORE --> PLUMBING - PLUMBING --> PORCELAIN - PORCELAIN --> LIBVCTRL - DOCS --> HANDLER +flowchart TD + subgraph Apps["Application layer"] + FACADE["libvctrl
facade (re-exports)"] + PL["libvctrl_plumbing
command-level library"] + PO["libvctrl_porcelain
high-level library"] + end + + subgraph Ref["Reference implementation"] + CORE["libvctrl_core
codec / builders / stores / hasher adapter"] + end + + subgraph Contracts["Contract layer"] + HANDLER["libvctrl_handler
traits / types / limits / validation"] + end + + subgraph Crypto["Cryptography"] + SHA["libvctrl_sha512
SHA-512 / HMAC / HKDF (+ SHA-384)"] + end + + FACADE --> HANDLER + FACADE --> CORE + FACADE --> SHA + PL --> CORE + PO --> CORE + CORE --> HANDLER + CORE --> SHA ``` +```` + +### End-to-end object lifecycle -Dependency direction: `libvctrl_handler` depends on nothing (except std). `libvctrl_sha512` depends on nothing. `libvctrl_core` depends on both handler and sha512. `libvctrl_plumbing` depends on handler and core. `libvctrl_porcelain` depends on plumbing. `libvctrl` (the main binary) depends on porcelain and core. +The layers collaborate to build, serialise, content-address, store, and later decode an +object. The decoder is the trust boundary: it treats every byte stream as untrusted and +re-validates structure, UTF-8, and system limits before constructing a handler type. + +```mermaid +sequenceDiagram + participant App as Application + participant B as Builder (core) + participant E as BinaryEncoder (core) + participant H as Sha512Hasher (core, via sha512) + participant S as MemoryStore (core) + participant D as BinaryDecoder (core) + + App->>B: Build object (Blob/Tree/Commit/Tag) + B->>B: Enforce handler limits and invariants + B-->>App: Validated immutable object + App->>E: encode_*(&object, &mut writer) + E-->>App: Deterministic, versioned bytes + App->>H: hash(&mut bytes.as_slice()) + H->>H: SHA-512 over the encoded payload + H-->>App: 64-byte Hash (content address) + App->>S: put(&hash, &bytes) + App->>S: get(&hash) + S-->>App: Stored bytes + App->>D: decode_*(reader) + D->>D: Defense-in-depth validation + D-->>App: Validated immutable object +``` --- ## Core Features -Across the workspace: - -- **Content-addressable object model** – Blob, Tree, Commit, Tag, Hash, UserID, TreeEntry. -- **Behavior traits** – ObjectStore, RefStore, Hasher, Encoder, Decoder, Signer, Verifier, Transport. -- **Binary serialization** – Deterministic, versioned, little-endian format. -- **SHA-512, HMAC-SHA-512, HKDF-SHA-512, SHA-384** – Zero-dependency cryptographic primitives. -- **In-memory stores** – ObjectStore and RefStore implementations for testing and ephemeral use. -- **Builder patterns** – Ergonomic construction of blobs, commits, tags, trees. -- **Validation utilities** – Path traversal and resource exhaustion prevention. -- **Strict linting and documentation** – Clippy all, pedantic, nursery, cargo denied; missing_docs denied. -- **Unified error handling** – `VctrlError` with source chaining and comparison. +- **Layered and modular.** Contracts, reference implementations, and crypto primitives are + isolated in separate crates with a one-way dependency flow. +- **Content-addressed.** Objects are serialised deterministically and addressed by SHA-512 + digests, so identical content always produces identical addresses. +- **Invalid states unrepresentable.** All domain types use fallible constructors that reject + malformed input at construction time; objects are immutable thereafter. +- **Defense-in-depth decoding.** The binary decoder bounds input, checks every offset, + validates UTF-8, and re-checks system limits — no slice indexing without a prior bounds + check. +- **Resource-exhaustion prevention.** Hard `MAX_*` limits act as fail-fast circuit breakers + during construction and decoding, bounding memory allocation against malicious input. +- **Zero-dependency cryptography.** SHA-512, HMAC-SHA512, HKDF-SHA512, and optional SHA-384 + are implemented in pure Rust over `core`, with constant-time verification and zeroization. +- **Single-dependency entry point.** The `libvctrl` facade re-exports the entire stack under + one ergonomic namespace. +- **Strict memory safety.** `#![forbid(unsafe_code)]` is enforced workspace-wide. --- ## Technology Stack -- **Language:** Rust (edition 2024) -- **Build tool:** Cargo -- **Dependencies:** - - `libvctrl_sha512` – zero external dependencies - - `libvctrl_handler` – no external dependencies - - `libvctrl_core` – uses `libvctrl_handler` and `libvctrl_sha512`; dev-dependency `proptest` - - Other crates build on top -- **Testing:** `proptest` for property-based tests, `criterion` for benchmarks -- **License:** MIT for most crates, ISC for `libvctrl_sha512` +- **Language:** Rust (edition 2024, MSRV 1.96.0) +- **Workspace licence:** MIT (`libvctrl_sha512` is ISC) +- **Authors:** `mroczect` +- **Resolver:** Cargo resolver v2 +- **Lint policy:** workspace-inherited (see [Contributing](#contributing)) +- **`no_std` status:** the workspace as a whole is **`std`-only**. The `no-std` keyword in + the workspace metadata applies only to `libvctrl_sha512` as a future-compatible goal; even + that crate is currently `std`-by-default (it uses only `core` APIs internally but does not + yet set `#![no_std]`). --- ## Project Structure ```text -libvcrtl/ -├── Cargo.toml # Workspace manifest -├── Cargo.lock -├── CONTRIBUTING.md -├── CODE_OF_CONDUCT.md -├── SECURITY.md -├── LICENSE -├── Makefile -├── scripts/ # Helper scripts -├── dev/ # Development utilities -├── libvctrl/ # Main CLI / executable crate -├── libvctrl_core/ # Reference implementations -├── libvctrl_docs/ # Documentation tooling -├── libvctrl_handler/ # Contracts, types, traits -├── libvctrl_plumbing/ # Low-level plumbing commands -├── libvctrl_porcelain/ # User-facing porcelain commands -└── libvctrl_sha512/ # SHA-512, HMAC, HKDF +libvctrl/ +├── Cargo.toml # workspace manifest +├── README.md # this file (workspace overview) +├── CONTRIBUTING.md # contribution guidelines +├── LICENSE # MIT (workspace); ISC under libvctrl_sha512/ +├── libvctrl/ # facade crate (v2.1.2) +├── libvctrl_handler/ # contract layer (v5.0.0) +├── libvctrl_core/ # reference implementations (v3.0.0) +├── libvctrl_sha512/ # crypto primitives (v3.0.0, ISC) +├── libvctrl_plumbing/ # command-level operations (v0.2.0) +└── libvctrl_porcelain/ # high-level operations (v0.1.0) ``` -Each crate has its own `README.md` with detailed documentation. Refer to those files for crate-specific information. +Each member crate contains its own `Cargo.toml`, `src/`, `README.md`, and tests. --- @@ -143,258 +169,228 @@ Each crate has its own `README.md` with detailed documentation. Refer to those f ### Prerequisites -- Rust toolchain 1.96.0 or newer (edition 2024) +- Rust toolchain **1.96.0** or newer (edition 2024 is required) - Cargo +- Git -No system libraries or external services are required to build and test the workspace. +No system libraries or external services are required. ### Installation -Clone the repository: +For most users, depend on the facade — it pulls the contracts, the reference implementation, +and the crypto primitives as a single dependency: -```sh -git clone https://github.com/mroczect/libvctrl.git -cd libvcrtl +```toml +[dependencies] +libvctrl = "2.1" ``` -Build the entire workspace: +Or via Cargo: -```sh -cargo build --workspace +```bash +cargo add libvctrl ``` -### Configuration - -No global configuration is required. Each crate may have its own feature flags: +To work on the workspace itself, clone the repository and build all members: -- `libvctrl_sha512` features: `default = ["sha384"]`, `sha384`, `opt_size`. -- Other crates do not expose feature flags. - -Environment variables are not used by the core libraries. The main CLI (`libvctrl`) may support configuration later. - ---- - -## Usage - -### Running the CLI - -If the main binary crate `libvctrl` is built, you can run: - -```sh -cargo run -p libvctrl -- --help +```bash +git clone https://github.com/mroczect/libvctrl.git +cd libvctrl +cargo build --workspace ``` -### Using the Libraries +### Configuration + +The workspace has no runtime configuration. Behavioural configuration of the cryptographic +backend is controlled through the facade's feature flags, which are forwarded to +`libvctrl_sha512`: -Add the desired crate as a dependency in your `Cargo.toml`: +- `sha384` (default) — enables SHA-384, HMAC-SHA-384, and HKDF-SHA-384. +- `opt_size` — favours smaller binary size over speed for embedded/WebAssembly/minimal-CLI + targets by de-inlining the SHA-512 compression round functions. ```toml -[dependencies] -libvctrl_handler = "4.4.0" -libvctrl_core = "2.0.1" -libvctrl_sha512 = "2.0.0" -``` +# Default (SHA-512 + SHA-384) +libvctrl = "2.1" -Then use the exported types and traits. Example combining handler and core: +# Minimal (SHA-512 only) +libvctrl = { version = "2.1", default-features = false } -```rust -use libvctrl_handler::{Blob, Encoder, Hasher, ObjectStore}; -use libvctrl_core::codec::BinaryEncoder; -use libvctrl_core::hash::Sha512Hasher; -use libvctrl_core::store::MemoryStore; -use std::io::Read; - -let blob = Blob::new(b"my content".to_vec()); -let encoder = BinaryEncoder; -let bytes = encoder.encode_blob(&blob).unwrap(); - -let hasher = Sha512Hasher; -let hash = hasher.hash(&bytes).unwrap(); - -let mut store = MemoryStore::new(); -store.put(&hash, &bytes).unwrap(); - -let mut reader = store.get(&hash).unwrap(); -let mut buf = Vec::new(); -reader.read_to_end(&mut buf).unwrap(); -assert_eq!(buf, bytes); +# Size-optimised, full crypto +libvctrl = { version = "2.1", features = ["opt_size"] } ``` --- -## Workspace Crates - -Detailed documentation for each crate is available in its respective `README.md`. Below is a summary. - -### libvctrl_handler - -**Contracts and Types** - -The foundational crate that defines: - -- Immutable data types: `Blob`, `Tree`, `TreeEntry`, `Commit`, `CommitMeta`, `Tag`, `Hash`, `UserID` -- Logical object kind enum: `EntryKind` -- System constants and limits -- Unified error type: `VctrlError` -- Behavior traits: `ObjectStore`, `RefStore`, `Hasher`, `Encoder`, `Decoder`, `Signer`, `Verifier`, `Transport` - -Contains no implementations. All public items are re-exported at crate root. - -### libvctrl_core - -**Reference Implementations** - -Provides production-ready implementations of the handler contracts: - -- `BinaryEncoder` and `BinaryDecoder` for serialization -- `Sha512Hasher` -- `MemoryStore` and `MemoryRefStore` -- Builder patterns: `BlobBuilder`, `CommitBuilder`, `TagBuilder`, `TreeBuilder`, `TreeEntryBuilder` -- Validation utilities for names and hashes - -This crate is intended as a quality exemplar for custom backend implementations. - -### libvctrl_sha512 - -**Cryptographic Primitives** - -Zero-dependency, `no_std`-compatible implementation of: - -- SHA-512 -- HMAC-SHA-512 -- HKDF-SHA-512 -- SHA-384 (feature-gated) -- HMAC-SHA-384 and HKDF-SHA-384 (feature-gated) - -Uses exported macros to generate HMAC and HKDF structs for different hash lengths. - -### libvctrl_plumbing +## Usage -**Low-level Commands** +### Quick start with the facade -Implements the plumbing (low-level) layer of the VCS, operating directly on object stores, refs, and codecs. Depends on `libvctrl_handler` and `libvctrl_core`. +```rust +use libvctrl::{ + Blob, Encoder, Hasher, ObjectStore, + BinaryEncoder, Sha512Hasher, MemoryStore, VctrlError, +}; + +fn main() -> Result<(), VctrlError> { + // 1. Build a validated blob. + let blob = Blob::new(b"hello world".to_vec())?; + + // 2. Encode it into deterministic, versioned bytes. + let mut encoded = Vec::new(); + BinaryEncoder.encode_blob(&blob, &mut encoded)?; + + // 3. Hash the encoded bytes to obtain a 64-byte content address. + let hash = Sha512Hasher.hash(&mut encoded.as_slice())?; + + // 4. Store the encoded object in memory and verify it exists. + let mut store = MemoryStore::new(); + store.put(&hash, &encoded)?; + assert!(store.exists(&hash)?); + Ok(()) +} +``` -### libvctrl_porcelain +### Workspace commands -**User-facing Commands** +```bash +# Build every member +cargo build --workspace -Implements the porcelain (user-friendly) layer, providing high-level commands such as commit, checkout, branch, tag, etc. Depends on `libvctrl_plumbing`. +# Run the entire test suite +cargo test --workspace -### libvctrl +# Run clippy across all members and targets +cargo clippy --workspace --all-targets -- -D warnings -all-in-one Version Control System (VCS) Software Development Kit. It aggregates the three foundational crates of the `libvcrtl` workspace into a single, coherent namespace, allowing developers to bootstrap a fully functional version control system without manually stitching together multiple dependencies. +# Build documentation for the whole workspace +cargo doc --workspace --no-deps -### libvctrl_docs +# Run benchmarks (criterion; sha384 bench requires the sha384 feature) +cargo bench --workspace +``` -**Documentation Tooling** +--- -Utilities and helpers for generating and maintaining project documentation. +## Crates + +The workspace publishes six crates. Each has its own README and docs.rs page. + +| Crate | Version | Licence | Role | Documentation | +| -------------------- | ------- | ------- | ------------------------------------------------------------------------ | ---------------------------------- | +| `libvctrl` | 2.1.2 | MIT | Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl | +| `libvctrl_handler` | 5.0.0 | MIT | Contract layer: traits, types, limits, validation | https://docs.rs/libvctrl_handler | +| `libvctrl_core` | 3.0.0 | MIT | Reference implementations: codec, builders, stores, hasher adapter | https://docs.rs/libvctrl_core | +| `libvctrl_sha512` | 3.0.0 | ISC | Zero-dependency SHA-512 / HMAC / HKDF (+ optional SHA-384) | https://docs.rs/libvctrl_sha512 | +| `libvctrl_plumbing` | 0.2.0 | MIT | Command-level VCS operations as a library (`cat_file`, `cat_file_batch`) | https://docs.rs/libvctrl_plumbing | +| `libvctrl_porcelain` | 0.1.0 | MIT | High-level, user-facing VCS operations as a library (early stage) | https://docs.rs/libvctrl_porcelain | + +### Layer roles + +- **`libvctrl_handler`** — the "constitution" layer. Defines _what_ a VCS object model looks + like: 17 backend contracts (`Encoder`, `Decoder`, `Hasher`, `ObjectStore`, `RefStore`, + `Transport`, `Signer`, `Verifier`, `Blame`, `ConfigStore`, etc.), 14 immutable data types + (`Blob`, `Tree`, `Commit`, `Tag`, `Hash`, `UserID`, ...), system limits, validation + functions, and the unified `VctrlError`. No implementations; `std`-only; zero dependencies. +- **`libvctrl_core`** — the reference implementation. Realises the handler contracts with a + deterministic, versioned binary codec (`BinaryEncoder`/`BinaryDecoder`), a SHA-512 hasher + adapter (`Sha512Hasher`), fluent builders, and in-memory stores (`MemoryStore`, + `MemoryRefStore`). `std`-only. +- **`libvctrl_sha512`** — the crypto engine. Pure-Rust SHA-512, HMAC-SHA512, HKDF-SHA512, + and optional SHA-384, with constant-time verification and zeroization. Zero external + dependencies; `std`-by-default but `core`-only internally. ISC-licensed. +- **`libvctrl`** — the facade. Re-exports `libvctrl_handler`, `libvctrl_core`, and + `libvctrl_sha512` under one namespace, lifting the most common items to the crate root. + The recommended single dependency for most users. +- **`libvctrl_plumbing`** — command-level VCS operations as a library (currently `cat_file` + and `cat_file_batch`). Built on `libvctrl_core`. +- **`libvctrl_porcelain`** — high-level, user-facing VCS operations as a library. Early + stage with a minimal public API. A future `vctrl` CLI could be built on top, but no binary + exists yet. --- ## Testing -Run all tests in the workspace: +Run the entire workspace test suite (unit tests, doctests, and property-based tests via +`proptest`): -```sh +```bash cargo test --workspace ``` -Run tests with all features enabled: - -```sh -cargo test --workspace --all-features -``` +`libvctrl_sha512` additionally ships `criterion` benchmarks under `benches/`: -Run doctests only: - -```sh -cargo test --workspace --doc -``` - -Run benchmarks (from `libvctrl_sha512`): - -```sh +```bash +# Run all benchmarks cargo bench --workspace -``` - ---- -## CI/CD Pipeline - -No CI/CD pipeline is currently configured in the repository. - -If one is added, it should include at least the following stages: - -```mermaid -graph LR - A[Push] --> B[Format Check] - B --> C[Clippy Lint] - C --> D[Run Tests] - D --> E[Build Docs] - E --> F[Publish] +# The SHA-384 benchmark requires the sha384 feature (on by default) +cargo bench --bench sha384_bench ``` -Recommended commands per stage: - -- Format: `cargo fmt --check` -- Lint: `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- Tests: `cargo test --workspace --all-features` -- Docs: `cargo doc --workspace --no-deps` -- Publish: `cargo publish` for each crate - --- -## Deployment / Distribution +## Contributing -Each crate is intended to be published to crates.io independently. Release process: +Contributions are welcome. The workspace enforces a shared lint policy inherited by all +members via `[lints] workspace = true`. -1. Update version in the crate's `Cargo.toml`. -2. Update its `CHANGELOG.md`. -3. Run `cargo publish --dry-run`. -4. Run `cargo publish` with a valid `CRATES_IO_TOKEN`. +### Workspace lint policy -Published crates: +**`rustc` lints:** -- `libvctrl_handler` -- `libvctrl_core` -- `libvctrl_sha512` -- Possibly others as they stabilize. +- `unsafe_code` and `macro_use_extern_crate` are **`forbid`** — non-overridable, hard + errors. No `unsafe` code is permitted anywhere in the workspace. +- A broad set of `rustc` lints (`missing_docs`, `dead_code`, `unused_imports`, + `unused_variables`, `unused_lifetimes`, `unused_macro_rules`, `unused_crate_dependencies`, + `unreachable_pub`, `rust_2018_idioms`, `rust_2021_compatibility`, `rust_2024_compatibility`, + `elided_lifetimes_in_paths`, `explicit_outlives_requirements`, `non_ascii_idents`, + `trivial_bounds`, `unit_bindings`, `single_use_lifetimes`, `redundant_lifetimes`, + `unused_qualifications`, `noop_method_call`, `unnameable_types`) are **`warn`** — they + surface diagnostics but do not fail the build. ---- +**`clippy` lints:** -## Security & Compliance +- `clippy::all` is **`warn`**. +- `clippy::pedantic`, `clippy::nursery`, and `clippy::cargo` are **`allow`** (effectively + disabled). +- A focused set of panic/unwrap-adjacent lints (`todo`, `unimplemented`, `unreachable`, + `unwrap_used`, `expect_used`, `panic`, `indexing_slicing`, `map_err_ignore`, + `wildcard_enum_match_arm`) are **`warn`**. +- Several style/portability lints are explicitly allowed (`doc_markdown`, + `doc_lazy_continuation`, `needless_return`, `match_same_arms`, `uninlined_format_args`, + `std_instead_of_core`, `std_instead_of_alloc`, `alloc_instead_of_core`). -The workspace enforces strict security practices: +> **Note on accuracy.** Earlier per-crate READMEs in this repository may have described +> `missing_docs`, `rust_2018_idioms`, and the `pedantic`/`nursery` groups as "denied." That +> was inaccurate: they are `warn` or `allow` as described above. Those per-crate sections +> should be corrected in a separate pass. The authoritative source is the +> `[workspace.lints]` table in the root `Cargo.toml`. -- **No unsafe code** except one reviewed block in `libvctrl_sha512::utils::verify`. -- **Denial-of-service prevention** via size limits and validation. -- **Path traversal prevention** in name validation. -- **Side-channel mitigation** via constant-ish time comparison. -- **Zeroization** of sensitive state in hash implementations. -- **Strict Clippy lints** including pedantic and nursery. +### Local development -Refer to `SECURITY.md` for reporting vulnerabilities and additional security guidelines. +```bash +# Format check +cargo fmt --all -- --check ---- +# Lint across the workspace (treat warnings as errors for CI) +cargo clippy --workspace --all-targets -- -D warnings -## Contributing - -Contributions are welcome. Please read `CONTRIBUTING.md` and follow the project's code of conduct. - -General guidelines: +# Documentation build +cargo doc --workspace --no-deps +``` -- All public items must have documentation with doctests. -- Run `cargo fmt`. -- Run `cargo clippy --workspace --all-targets --all-features -- -D warnings`. -- Run `cargo test --workspace --all-features`. -- Avoid unsafe code; if necessary, justify it thoroughly. +For contribution guidelines, code style, and the full lint configuration, see +`CONTRIBUTING.md` and this README. When contributing, preserve the layered invariants: new +contracts and types belong in `libvctrl_handler`; new reference implementations belong in +`libvctrl_core`; new user-facing commands belong in `libvctrl_plumbing` or +`libvctrl_porcelain`; and no `unsafe` code may be introduced in any member. --- -## License - -- Most crates in this workspace are licensed under the **MIT License**. -- `libvctrl_sha512` is licensed under the **ISC License**. +## Licence -See the `LICENSE` file in each crate for details. +The workspace is licensed under the **MIT** licence, except for `libvctrl_sha512`, which is +licensed under the **ISC** licence. See each crate's `LICENSE` file for the authoritative +text. diff --git a/libvctrl/Cargo.toml b/libvctrl/Cargo.toml index 556c66de..6eccb760 100644 --- a/libvctrl/Cargo.toml +++ b/libvctrl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libvctrl" -version = "2.1.2" +version = "2.1.3" edition = "2024" description = "A precision toolkit for building custom version control systems" license = "MIT" diff --git a/libvctrl/README.md b/libvctrl/README.md index 75f7366d..68b74b2e 100644 --- a/libvctrl/README.md +++ b/libvctrl/README.md @@ -1,175 +1,180 @@ # libvctrl -**Version:** 2.1.1 -**License:** MIT -**Crate type:** Rust library (facade / SDK) -**Workspace:** libvcrtl - -`libvctrl` is the **all-in-one Version Control System (VCS) Software Development Kit**. It aggregates the three foundational crates of the `libvcrtl` workspace into a single, coherent namespace, allowing developers to bootstrap a fully functional version control system without manually stitching together multiple dependencies. - -The crate itself contains almost no new logic. Instead, it re-exports the essential types, traits, implementations, and cryptographic primitives from: - -- `libvctrl_handler` – pure contracts and data types -- `libvctrl_core` – ready-to-use reference implementations -- `libvctrl_sha512` – zero-dependency cryptographic primitives - -This facade design gives downstream users one convenient entry point while preserving strict separation of concerns internally. - ---- - -## Table of Contents - -- [Overview](#overview) -- [System Architecture](#system-architecture) -- [Core Features](#core-features) -- [Technology Stack](#technology-stack) -- [Project Structure](#project-structure) -- [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Configuration](#configuration) -- [Usage](#usage) -- [API Reference](#api-reference) - - [Sub-crate Modules](#sub-crate-modules) - - [handler](#handler) - - [reference](#reference) - - [crypto](#crypto) - - [Root Re-exports: Contracts](#root-re-exports-contracts) - - [Root Re-exports: Reference Implementations](#root-re-exports-reference-implementations) -- [Testing](#testing) -- [CI/CD Pipeline](#cicd-pipeline) -- [Deployment / Distribution](#deployment--distribution) -- [Security & Compliance](#security--compliance) -- [Contributing](#contributing) -- [License](#license) -- [Changelog](#changelog) +A precision toolkit for building custom version control systems. `libvctrl` is the +all-in-one facade crate of the `libvctrl` workspace: it re-exports the contract layer, +the reference implementation, and the cryptographic primitives of a content-addressable +VCS into a single, ergonomic namespace. + +- **Crate:** `libvctrl` 2.1.3 (library-only, `std`-only) +- **Language:** Rust, edition 2024 — MSRV **1.96.0** +- **License:** MIT +- **Repository:** https://github.com/mroczect/libvctrl +- **Documentation:** https://docs.rs/libvctrl + +> `libvctrl` is a **library facade**. It contains no logic of its own; every public +> item is a compile-time re-export of one of three underlying workspace crates. A +> future binary crate (for example, a `vctrl` CLI) may be built on top of this facade, +> but no such binary exists at the 2.1.2 release. --- ## Overview -`libvctrl` is designed for developers who want to build a custom version control system, or use an existing VCS core, without needing to know the exact module paths of each underlying crate. +`libvctrl` aggregates the three foundational crates of the `libvctrl` ecosystem into one +dependency. Integrators who need a complete, batteries-included VCS stack can depend on +a single crate instead of stitching together the contract, reference, and crypto layers +manually. -It provides: +The facade exposes three top-level namespaces and lifts the most commonly used items to +the crate root: -- **Contracts**: Immutable domain types like `Blob`, `Commit`, `Tree`, `Hash`, and behavior traits like `ObjectStore`, `Encoder`, `Hasher`, and `Transport`. -- **Reference implementations**: In-memory stores (`MemoryStore`, `MemoryRefStore`), binary serialization (`BinaryEncoder`, `BinaryDecoder`), SHA-512 hasher adapter (`Sha512Hasher`), and ergonomic builders. -- **Cryptography**: Full SHA-512, HMAC-SHA-512, HKDF-SHA-512, and optional SHA-384 implementations. +- `handler` — re-export of `libvctrl_handler`: abstract traits, immutable data types, + system limits, and validation functions. +- `reference` — re-export of `libvctrl_core`: production-ready implementations of the + handler contracts (binary codec, SHA-512 hasher adapter, builders, in-memory stores). +- `crypto` — re-export of `libvctrl_sha512`: zero-dependency SHA-512, HMAC-SHA512, + HKDF-SHA512, and optional SHA-384 primitives. -All public items are re-exported at the crate root for maximum ergonomics, while the original sub-crate modules remain accessible for explicit use. +All re-exports are zero-cost aliases. There is no runtime overhead and no duplicated +code; the only cost is a larger public API surface. --- -## System Architecture +## Architecture -`libvctrl` acts as a facade. It re-exports three underlying crates into top-level modules and then lifts many of their types to the root. +`libvctrl` is a classic facade over a strictly layered dependency graph. The contract +layer (`libvctrl_handler`) depends only on the Rust standard library and defines _what_ +a VCS object model looks like. The reference layer (`libvctrl_core`) depends on the +contract layer and provides _how_ those contracts are realised. The crypto layer +(`libvctrl_sha512`) provides the hashing primitive and is wired into the facade with +`default-features = false` so the facade controls which crypto symbols are visible. + +The `crypto` namespace alias exists for a deliberate reason: it isolates the +cryptographic `Hash` hasher type from the VCS `Hash` content-address type, preventing a +name collision when both are imported into the same scope. ```mermaid -graph TD - LIBVCTRL[libvctrl facade crate] - HANDLER[libvctrl_handler
Contracts & Types] - CORE[libvctrl_core
Reference Implementations] - SHA512[libvctrl_sha512
Cryptography] - - LIBVCTRL -->|pub use as handler| HANDLER - LIBVCTRL -->|pub use as reference| CORE - LIBVCTRL -->|pub use as crypto| SHA512 - - subgraph Root Re-exports - ROOT_TYPES[Blob, Tree, Commit, Tag, Hash, UserID, TreeEntry, CommitMeta] - ROOT_TRAITS[ObjectStore, RefStore, Hasher, Encoder, Decoder, Signer, Verifier, Transport] - ROOT_CONST[constants, HASH_LENGTH, MAX_*] - ROOT_ENUMS[EntryKind] - ROOT_ERRORS[VctrlError] - ROOT_MACROS[vctrl_error_other] - ROOT_BUILDERS[BlobBuilder, CommitBuilder, TagBuilder, TreeBuilder, TreeEntryBuilder] - ROOT_STORES[MemoryStore, MemoryRefStore] - ROOT_CODEC[BinaryEncoder, BinaryDecoder] - ROOT_HASHER[Sha512Hasher] - ROOT_VALIDATE[validate_name, validate_hash_bytes] +flowchart TD + subgraph Facade["libvctrl facade crate (std-only, library)"] + Root["Root re-exports
Blob, Commit, Tree, Tag, Hash, UserID...
Encoder, Decoder, Hasher, ObjectStore, RefStore...
BinaryEncoder, BinaryDecoder, Sha512Hasher
Builders, MemoryStore, MemoryRefStore
constants, validation, VctrlError"] + HandlerNS["handler namespace
libvctrl_handler"] + RefNS["reference namespace
libvctrl_core"] + CryptoNS["crypto namespace
libvctrl_sha512"] + Root --> HandlerNS + Root --> RefNS + Root --> CryptoNS end - HANDLER --> ROOT_TYPES - HANDLER --> ROOT_TRAITS - HANDLER --> ROOT_CONST - HANDLER --> ROOT_ENUMS - HANDLER --> ROOT_ERRORS - HANDLER --> ROOT_MACROS - CORE --> ROOT_BUILDERS - CORE --> ROOT_STORES - CORE --> ROOT_CODEC - CORE --> ROOT_HASHER - CORE --> ROOT_VALIDATE -``` + subgraph Contracts["Contract layer"] + Handler["libvctrl_handler
traits, types, constants,
validation, errors, enums"] + end -This architecture ensures that users can choose between: + subgraph Reference["Reference implementation"] + Core["libvctrl_core
codec, builders, stores,
Sha512Hasher adapter"] + Core -->|depends on| Handler + end -- **Implicit, root-level imports** for rapid development: `use libvctrl::*;` -- **Explicit, module-qualified imports** for clarity: `use libvctrl::handler::Blob;` + subgraph CryptoLayer["Cryptography"] + Sha["libvctrl_sha512
SHA-512, HMAC-SHA512,
HKDF-SHA512, optional SHA-384"] + end ---- + HandlerNS --> Handler + RefNS --> Core + CryptoNS --> Sha +``` -## Core Features +The crate is `std`-only. The facade and its immediate dependencies rely on `String`, +`Vec`, `HashMap`, `std::io`, and `std::error::Error`. Although the underlying +`libvctrl_sha512` core may be `no_std`-compatible, the facade re-exports the full SDK, +including the `std`-dependent stores and codecs, so `libvctrl` itself cannot be built +without the standard library. -- **Facade pattern** - A single crate exposes the entire VCS stack, reducing dependency management burden. +### Object lifecycle workflow -- **Namespace isolation** - The cryptographic primitives are grouped under `crypto` to avoid conflicts between the VCS `Hash` type and the SHA-512 `Hash` hasher. +A typical interaction with the facade follows the same pattern as a Git-like +content-addressable store: build a validated object, encode it to deterministic bytes, +hash the encoded bytes to obtain a content address, store the bytes under that address, +and later retrieve and verify them. -- **Batteries-included** - In-memory storage, binary serialization, SHA-512 hashing, and builders are all available out of the box. +```mermaid +sequenceDiagram + participant App as Application + participant B as Builder + participant E as BinaryEncoder + participant H as Sha512Hasher + participant S as MemoryStore + + App->>B: Build object (Blob / Tree / Commit / Tag) + B->>B: Validate fields and limits + B-->>App: Validated immutable object + App->>E: encode_*(&object, &mut buf) + E->>E: Produce deterministic, versioned bytes + E-->>App: Encoded payload + App->>H: hash(&mut encoded.as_slice()) + H-->>App: Hash (64-byte content address) + App->>S: put(&hash, &encoded) + S-->>App: () + App->>S: exists(&hash) + S-->>App: true + App->>S: get(&hash) + S-->>App: Reader over stored bytes + App->>App: Decode / verify round-trip +``` -- **Strict safety** - `#![forbid(unsafe_code)]` and a comprehensive set of denied Clippy lints ensure memory safety and code quality. +--- -- **Root-level ergonomics** - Frequently used types and traits are re-exported at the crate root, enabling `use libvctrl::Blob;` instead of deep paths. +## Core Features -- **Feature forwarding** - Features for SHA-384 and code-size optimization are forwarded to `libvctrl_sha512`, allowing users to configure the underlying crypto implementation. +- **Facade pattern.** One crate exposes the entire VCS stack, removing the need to + manage three separate dependencies. +- **Namespace isolation.** The cryptographic primitives live under `crypto` to avoid a + name collision between the VCS `Hash` content-address type and the SHA-512 `Hash` + hasher type. +- **Batteries included.** In-memory storage, binary serialization, a SHA-512 hasher + adapter, and fluent builders are available out of the box. +- **Root-level ergonomics.** Frequently used types and traits are re-exported at the + crate root, so `use libvctrl::Blob;` works without a long path. +- **Strict safety.** `#![forbid(unsafe_code)]` is enforced, and the crate inherits + strict, denied Clippy and rustc documentation lints from the workspace. +- **Feature forwarding.** The `sha384` and `opt_size` features are forwarded to + `libvctrl_sha512`, letting integrators configure the cryptographic backend without + touching the underlying crate directly. --- ## Technology Stack -- **Language:** Rust (edition 2024) +- **Language:** Rust (edition 2024, MSRV 1.96.0) - **Dependencies:** - - `libvctrl_handler` 4.4.0 – contracts and types - - `libvctrl_core` 2.0.1 – reference implementations - - `libvctrl_sha512` 2.0.0 – cryptography (with `default-features = false`) -- **Dev dependencies:** - - `proptest` 1.11.0 -- **Features:** - - `default = ["sha384"]` - - `sha384` – enables `libvctrl_sha512/sha384` - - `opt_size` – enables `libvctrl_sha512/opt_size` -- **Strict lints:** - - Clippy: `all`, `pedantic`, `nursery`, `cargo`, `missing_const_for_fn`, `redundant_clone`, `unwrap_used`, `expect_used`, `panic` (all denied) - - Rust: `unsafe_code` forbidden, `missing_docs` denied, `rust_2018_idioms` denied, `unreachable_pub` denied, `unused_qualifications` denied + - `libvctrl_handler` 5.0.0 — contracts and types (path dependency) + - `libvctrl_core` 3.0.0 — reference implementations (path dependency) + - `libvctrl_sha512` 3.0.0 — cryptography, `default-features = false` (path dependency) +- **Dev-dependencies:** `proptest` 1.11.0 +- **Lint policy:** workspace-inherited, `#![forbid(unsafe_code)]`, denied missing-docs, + rust-2018-idioms, and a broad set of Clippy lints (including pedantic and nursery + groups). See the repository for the authoritative lint configuration. --- ## Project Structure -The crate consists of a single source file that re-exports the underlying crates. +The facade crate consists of a single source file. All functionality is provided +through re-exports; no additional modules are defined. ```text libvctrl/ ├── Cargo.toml └── src/ - └── lib.rs + └── lib.rs # Re-exports only; no runtime logic ``` -No additional modules are defined. All functionality is provided through re-exports. - --- ## Getting Started ### Prerequisites -- Rust toolchain 1.96.0 or newer (edition 2024) +- Rust toolchain **1.96.0** or newer (edition 2024 is required) - Cargo No system libraries or external services are required. @@ -180,83 +185,123 @@ Add `libvctrl` to your `Cargo.toml`: ```toml [dependencies] -libvctrl = "2.1.1" +libvctrl = "2.1.2" ``` -Or use Cargo: +Or use Cargo directly: -```sh +```bash cargo add libvctrl ``` -This will automatically pull the required `libvctrl_handler`, `libvctrl_core`, and `libvctrl_sha512` dependencies. +This will automatically pull `libvctrl_handler`, `libvctrl_core`, and `libvctrl_sha512` +as transitive dependencies. ### Configuration -No runtime configuration is required. Available feature flags: +No runtime configuration is required. The behaviour of the cryptographic backend is +controlled through Cargo features. + +| Use case | Configuration | +| ----------------------------- | --------------------------------------------------- | +| Default full functionality | `default` (includes `sha384`) | +| Minimal SHA-512 only | `default-features = false` | +| Size-optimised build | `features = ["opt_size"]` | +| Size-optimised + SHA-512 only | `default-features = false, features = ["opt_size"]` | ```toml -[dependencies] -libvctrl = { version = "2.1.1", features = ["sha384", "opt_size"] } -``` +# Default (SHA-512 + SHA-384) +libvctrl = "2.1.2" -- `sha384` is enabled by default and adds SHA-384, HMAC-SHA-384, and HKDF-SHA-384. -- `opt_size` favors smaller binary size over speed by changing inline attributes in the SHA-512 implementation. +# Minimal: SHA-512 only +libvctrl = { version = "2.1.2", default-features = false } -Disable default features if you do not need SHA-384: +# Size-optimised, full crypto +libvctrl = { version = "2.1.2", features = ["opt_size"] } -```toml -[dependencies] -libvctrl = { version = "2.1.1", default-features = false } +# Size-optimised, SHA-512 only +libvctrl = { version = "2.1.2", default-features = false, features = ["opt_size"] } ``` +- **`sha384`** (default): enables SHA-384, HMAC-SHA-384, and HKDF-SHA-384 in the `crypto` + namespace. Disable it to reduce compile time and code size when only SHA-512 is needed. +- **`opt_size`**: favours smaller binary size over speed by selecting smaller, slower + SHA-512 variants. Intended for binary-size-sensitive targets such as embedded devices, + WebAssembly, or minimal CLI builds. It does **not** enable `no_std`. + +> `libvctrl` is `std`-only. `opt_size` is a code-size optimisation, not a `no_std` +> switch. If you need a `no_std`-compatible hashing core, depend on `libvctrl_sha512` +> directly rather than through this facade. + --- ## Usage -### Build, Encode, Hash, and Store a Tree +### Build, encode, hash, and store a Blob ```rust use libvctrl::{ - EntryKind, Hash, TreeBuilder, TreeEntryBuilder, BinaryEncoder, Sha512Hasher, - MemoryStore, Encoder, Hasher, ObjectStore, VctrlError, + Blob, Encoder, Hasher, ObjectStore, + BinaryEncoder, Sha512Hasher, MemoryStore, VctrlError, }; -use std::io::Read; - -// 1. Build a Tree containing a single file entry -let blob_hash = Hash::from_bytes(&[0xAB; 64])?; -let entry = TreeEntryBuilder::new("file.txt".to_string(), EntryKind::Blob, blob_hash).build()?; -let tree = TreeBuilder::new().entry(entry).build()?; -// 2. Encode the Tree into binary format -let encoder = BinaryEncoder; -let encoded_bytes = encoder.encode_tree(&tree)?; +fn main() -> Result<(), VctrlError> { + // 1. Create a validated blob. + let blob = Blob::new(b"my content".to_vec())?; -// 3. Hash the encoded bytes to get an address -let hasher = Sha512Hasher; -let tree_hash = hasher.hash(&encoded_bytes)?; + // 2. Encode the blob into deterministic, versioned bytes. + let mut encoded = Vec::new(); + BinaryEncoder.encode_blob(&blob, &mut encoded)?; -// 4. Store the encoded object in memory -let mut store = MemoryStore::new(); -store.put(&tree_hash, &encoded_bytes)?; + // 3. Hash the encoded bytes to obtain a content address. + let hash = Sha512Hasher.hash(&mut encoded.as_slice())?; -// 5. Retrieve and verify the object -assert!(store.exists(&tree_hash)?); -let mut reader = store.get(&tree_hash)?; -let mut buf = Vec::new(); -reader.read_to_end(&mut buf).map_err(VctrlError::IoError)?; -assert_eq!(buf, encoded_bytes); + // 4. Store the encoded object in memory. + let mut store = MemoryStore::new(); + store.put(&hash, &encoded)?; -# Ok::<(), VctrlError>(()) + // 5. Verify the object exists. + assert!(store.exists(&hash)?); + Ok(()) +} ``` -### Using Namespace-Isolated Cryptography +### Build, encode, hash, and store a Tree ```rust -use libvctrl::crypto::Hash as Sha512Hasher; +use libvctrl::{ + EntryKind, Hash, TreeBuilder, TreeEntryBuilder, + BinaryEncoder, Sha512Hasher, MemoryStore, + Encoder, Hasher, ObjectStore, VctrlError, +}; +use std::io::Read; -let digest = Sha512Hasher::hash(b"hello world"); -assert_eq!(digest.len(), 64); +fn main() -> Result<(), VctrlError> { + // 1. Build a Tree containing a single file entry. + let blob_hash = Hash::from_bytes(&[0xAB; 64])?; + let entry = TreeEntryBuilder::new("file.txt".to_string(), EntryKind::Blob, blob_hash).build()?; + let tree = TreeBuilder::new().entry(entry).build()?; + + // 2. Encode the Tree into binary format. + let encoder = BinaryEncoder; + let encoded_bytes = encoder.encode_tree(&tree)?; + + // 3. Hash the encoded bytes to get an address. + let hasher = Sha512Hasher; + let tree_hash = hasher.hash(&encoded_bytes)?; + + // 4. Store the encoded object in memory. + let mut store = MemoryStore::new(); + store.put(&tree_hash, &encoded_bytes)?; + + // 5. Retrieve and verify the object. + assert!(store.exists(&tree_hash)?); + let mut reader = store.get(&tree_hash)?; + let mut buf = Vec::new(); + reader.read_to_end(&mut buf).map_err(VctrlError::IoError)?; + assert_eq!(buf, encoded_bytes); + Ok(()) +} ``` ### Building a Commit @@ -264,314 +309,159 @@ assert_eq!(digest.len(), 64); ```rust use libvctrl::{CommitBuilder, Hash, UserID}; -let tree = Hash::from_bytes(&[0; 64]).unwrap(); -let user = UserID::new("Alice".to_owned(), "alice@example.com".to_owned()).unwrap(); - -let commit = CommitBuilder::new() - .tree(tree) - .author(user.clone()) - .committer(user) - .message("Initial commit") - .build() - .unwrap(); - -assert_eq!(commit.message(), "Initial commit"); -``` - ---- - -## API Reference - -### Sub-crate Modules - -The three underlying crates are re-exported as top-level modules. - -#### handler - -Full namespace: `libvctrl::handler` - -Re-exports `libvctrl_handler` – the pure contracts crate. - -Contains: - -- Data types: `Blob`, `Tree`, `TreeEntry`, `Commit`, `CommitMeta`, `Tag`, `Hash`, `UserID` -- Enum: `EntryKind` -- Error type: `VctrlError` -- Traits: `ObjectStore`, `RefStore`, `Hasher`, `Encoder`, `Decoder`, `Signer`, `Verifier`, `Transport` -- Constants and limits -- Macros - -**Example:** - -```rust -use libvctrl::handler::Blob; - -let blob = Blob::new(b"hello".to_vec()); -``` - -#### reference - -Full namespace: `libvctrl::reference` - -Re-exports `libvctrl_core` – the reference implementation crate. - -Contains: - -- Codec: `BinaryEncoder`, `BinaryDecoder` -- Hash adapter: `Sha512Hasher` -- Stores: `MemoryStore`, `MemoryRefStore` -- Builders: `BlobBuilder`, `CommitBuilder`, `TagBuilder`, `TreeBuilder`, `TreeEntryBuilder` -- Validation utilities - -**Example:** - -```rust -use libvctrl::reference::MemoryStore; +fn main() -> Result<(), Box> { + let tree = Hash::from_bytes(&[0; 64])?; + let user = UserID::new("Alice".to_owned(), "alice@example.com".to_owned())?; + let commit = CommitBuilder::new() + .tree(tree) + .author(user.clone()) + .committer(user) + .message("Initial commit") + .build()?; + assert_eq!(commit.message(), "Initial commit"); + Ok(()) +} ``` -#### crypto - -Full namespace: `libvctrl::crypto` - -Re-exports `libvctrl_sha512` – the cryptographic primitives crate. - -Contains: +### Namespace-isolated cryptography -- SHA-512 hasher -- HMAC-SHA-512 -- HKDF-SHA-512 -- Optional SHA-384 and HMAC/HKDF-SHA-384 (feature-gated) - -**Example:** +Because the VCS `Hash` type and the SHA-512 `Hash` hasher share a name, use the `crypto` +namespace to access raw hashing primitives unambiguously: ```rust -use libvctrl::crypto::Hash as Sha512Hash; +use libvctrl::crypto::Hash as Sha512Hasher; -let digest = Sha512Hash::hash(b"data"); +let digest = Sha512Hasher::hash(b"hello world"); +assert_eq!(digest.len(), 64); ``` --- -### Root Re-exports: Contracts - -These items are available directly at `libvctrl::`. - -#### Constants - -| Item | Description | -| -------------------- | ----------- | -| `HASH_LENGTH` | 64 bytes | -| `MAX_NAME_LENGTH` | 255 bytes | -| `MAX_BLOB_SIZE` | 100 MiB | -| `MAX_TREE_ENTRIES` | 100,000 | -| `MAX_MESSAGE_LENGTH` | 1 MiB | +## API Reference / Core Modules -Also available: `libvctrl::constants::{HASH_LENGTH, ...}`. +Full API documentation is published at . The summary below +describes the module organisation and the most important re-exports. -#### Enums +### Sub-crate namespaces -| Item | Description | -| ----------- | ------------------------------------------------------------------------ | -| `EntryKind` | Logical entry kind: `Blob`, `Executable`, `Symlink`, `Tree`, `Submodule` | +| Namespace | Underlying crate | Contents | +| ----------- | ------------------ | ---------------------------------------------------------------- | +| `handler` | `libvctrl_handler` | Traits, immutable types, constants, validation, errors, enums | +| `reference` | `libvctrl_core` | Binary codec, builders, in-memory stores, SHA-512 hasher adapter | +| `crypto` | `libvctrl_sha512` | SHA-512, HMAC-SHA512, HKDF-SHA512, optional SHA-384 | -#### Errors +### Root re-exports: contracts (from `handler`) -| Item | Description | -| ------------ | ---------------------------------------------- | -| `VctrlError` | Unified error type for all fallible operations | +**Traits:** `Encoder`, `Decoder`, `Hasher`, `ObjectStore`, `RefStore`, `Signer`, +`Verifier`, `Transport`. -#### Traits +**Types:** `Blob`, `Tree`, `TreeEntry`, `Commit`, `CommitMeta`, `Tag`, `Hash`, +`UserID`, `EntryKind`. -| Item | Description | -| ------------- | ---------------------------------- | -| `ObjectStore` | Content-addressable object storage | -| `RefStore` | Named reference management | -| `Hasher` | Cryptographic hashing | -| `Encoder` | Serialization to bytes | -| `Decoder` | Deserialization from bytes | -| `Signer` | Cryptographic signing | -| `Verifier` | Signature verification | -| `Transport` | Remote object synchronization | +**Error:** `VctrlError` — the unified error type returned by every fallible operation +across the ecosystem. -#### Types +**Constants:** `HASH_LENGTH`, `MAX_BLOB_SIZE`, `MAX_MESSAGE_LENGTH`, `MAX_NAME_LENGTH`, +`MAX_PARENT_COUNT`, `MAX_TREE_ENTRIES`. -| Item | Description | -| ------------ | -------------------------------------- | -| `Blob` | Immutable raw byte content | -| `Tree` | Sorted list of `TreeEntry` | -| `TreeEntry` | Name, kind, and hash | -| `Commit` | Snapshot with parents and metadata | -| `CommitMeta` | Timestamp, timezone, encoding | -| `Tag` | Named reference with optional metadata | -| `Hash` | 64-byte content address | -| `UserID` | Name and email identity | +**Validation functions:** `validate_hash_bytes`, `validate_name`, `validate_ref_name`, +`validate_tree_entry_name`. -#### Macros - -| Item | Description | -| -------------------- | -------------------------------------------------- | -| `vctrl_error_other!` | Creates `VctrlError::Other` with formatted message | - ---- +**Modules:** `constants`, `enums`, `errors`, `macros`, `traits`, `types`, `validation`. -### Root Re-exports: Reference Implementations +### Root re-exports: reference implementations (from `reference`) -These items are available directly at `libvctrl::`. +**Codec:** `BinaryEncoder`, `BinaryDecoder` — deterministic, versioned binary payloads +with strict bounds checking. -#### Codec +**Hasher:** `Sha512Hasher` — implements the `Hasher` trait and produces 64-byte content +addresses. -| Item | Description | -| --------------- | ---------------------- | -| `BinaryEncoder` | Implements `Encoder` | -| `BinaryDecoder` | Implements `Decoder` | -| `codec` | Module containing both | +**Builders:** `BlobBuilder`, `CommitBuilder`, `TagBuilder`, `TreeBuilder`, +`TreeEntryBuilder` — fluent APIs for constructing validated objects. -#### Hash +**Stores:** `MemoryStore` (implements `ObjectStore` via `HashMap`) and `MemoryRefStore` +(implements `RefStore` via `HashMap`). -| Item | Description | -| -------------- | ----------------------------------------- | -| `Sha512Hasher` | Adapter for SHA-512 implementing `Hasher` | +**Modules:** `codec`, `object`, `store`. -#### Stores +### Root re-exports: cryptography (from `crypto`) -| Item | Description | -| ---------------- | ----------------------- | -| `MemoryStore` | In-memory `ObjectStore` | -| `MemoryRefStore` | In-memory `RefStore` | -| `store` | Module containing both | - -#### Builders - -| Item | Description | -| ------------------ | ------------------------------ | -| `BlobBuilder` | Builder for `Blob` | -| `CommitBuilder` | Builder for `Commit` | -| `TagBuilder` | Builder for `Tag` | -| `TreeBuilder` | Builder for `Tree` | -| `TreeEntryBuilder` | Builder for `TreeEntry` | -| `object` | Module containing all builders | - -#### Validation - -| Item | Description | -| --------------------- | --------------------------------------- | -| `validate_name` | Validates a name against security rules | -| `validate_hash_bytes` | Validates hash byte length | -| `validate` | Module containing validation utilities | +The `crypto` namespace exposes the `libvctrl_sha512` crate directly. SHA-384 symbols are +available only when the `sha384` feature is enabled (the default). --- ## Testing -The crate itself contains only re-exports and no new logic, so most tests reside in the underlying crates. However, doctests in `libvctrl` validate the re-exported API. - -Run all tests for this crate: +Run the crate's test suite with Cargo: -```sh -cargo test -p libvctrl +```bash +cargo test ``` -Run all tests with default features: +Property-based tests use `proptest` (a dev-dependency). Because the facade crate contains +only re-exports, most behavioural tests live in the underlying workspace crates +(`libvctrl_handler`, `libvctrl_core`, `libvctrl_sha512`). To run the entire workspace +test suite from the repository root: -```sh -cargo test -p libvctrl --all-features +```bash +cargo test --workspace ``` -Run doctests only: +To verify that the strict lint policy is satisfied: -```sh -cargo test -p libvctrl --doc -``` - -Run strict Clippy checks: - -```sh -cargo clippy -p libvctrl --all-targets --all-features -- -D warnings +```bash +cargo clippy --workspace --all-targets -- -D warnings +cargo doc --workspace --no-deps ``` --- -## CI/CD Pipeline - -No CI/CD pipeline is currently configured in the repository. - -If one is added, it should include at least the following stages: - -```mermaid -graph LR - A[Push] --> B[Format Check] - B --> C[Clippy Lint] - C --> D[Run Tests] - D --> E[Build Docs] - E --> F[Publish] -``` - -Recommended commands per stage: - -- Format: `cargo fmt --check` -- Lint: `cargo clippy -p libvctrl --all-targets --all-features -- -D warnings` -- Tests: `cargo test -p libvctrl --all-features` -- Docs: `cargo doc -p libvctrl --no-deps` -- Publish: `cargo publish` - ---- - -## Deployment / Distribution +## Contributing -The crate is intended to be published to crates.io. +Contributions are welcome. The crate enforces `#![forbid(unsafe_code)]` and inherits a +strict, denied Clippy and documentation-lint policy from the workspace; all public items +must be documented. -Release process: +For contribution guidelines, code style, and the full lint configuration, see the +repository's `CONTRIBUTING.md` and the workspace root `README.md`: -1. Update version in `Cargo.toml`. -2. Update `CHANGELOG.md`. -3. Run `cargo publish --dry-run`. -4. Run `cargo publish`. +- Repository: https://github.com/mroczect/libvctrl -After publication, documentation will be available at `https://docs.rs/libvctrl`. +When contributing to this facade crate specifically, keep in mind that it must remain a +pure re-export layer: no new logic, types, or `unsafe` code should be introduced here. +New functionality belongs in the appropriate underlying crate. --- -## Security & Compliance +## Workspace -`libvctrl` inherits the strict security posture of its underlying crates: +`libvctrl` is the facade crate of a larger workspace. The sibling crates are listed +below; each has its own documentation and may be depended on directly when only a subset +of the stack is required. -- **No unsafe code** - `#![forbid(unsafe_code)]` is set at the crate level. The only `unsafe` in the dependency tree is a single reviewed block in `libvctrl_sha512::utils::verify`. +| Crate | Role | Documentation | +| -------------------- | -------------------------------------------------- | ---------------------------------- | +| `libvctrl_handler` | Contract layer: traits, types, limits, validation | https://docs.rs/libvctrl_handler | +| `libvctrl_core` | Reference implementations: codec, builders, stores | https://docs.rs/libvctrl_core | +| `libvctrl_sha512` | Zero-dependency SHA-512 / HMAC / HKDF primitives | https://docs.rs/libvctrl_sha512 | +| `libvctrl_plumbing` | Command-level VCS operations built on `core` | https://docs.rs/libvctrl_plumbing | +| `libvctrl_porcelain` | High-level, user-facing VCS operations | https://docs.rs/libvctrl_porcelain | -- **Strict linting** - Clippy `all`, `pedantic`, `nursery`, `cargo`, and additional lints like `unwrap_used`, `expect_used`, and `panic` are denied, reducing the chance of accidental panics. +The dependency flow is strictly one-way: -- **DoS protection** - Underlying validators and decoders enforce size limits (`MAX_BLOB_SIZE`, `MAX_MESSAGE_LENGTH`, `MAX_TREE_ENTRIES`) and validate UTF-8. - -- **Path traversal prevention** - `validate_name` rejects `/`, `.`, and `..` in names used for refs and tree entries. - -- **Side-channel mitigation** - SHA-512 verification and HMAC comparison use non-short-circuiting XOR accumulation. - -- **Zeroization** - Hash and HMAC implementations clear internal state on drop or explicit call. - -Refer to `SECURITY.md` in the workspace root for reporting vulnerabilities and additional security guidelines. - ---- - -## Contributing - -Contributions are welcome. Follow the workspace `CONTRIBUTING.md`. - -For this crate, ensure: - -- All public items have documentation with doctests. -- Do not introduce new logic unless it is strictly necessary for the facade. -- Run `cargo fmt`. -- Run `cargo clippy -p libvctrl --all-targets --all-features -- -D warnings`. -- Run `cargo test -p libvctrl --all-features`. -- Avoid unsafe code. +```mermaid +flowchart LR + H[libvctrl_handler
contracts] --> C[libvctrl_core
reference impl] + C --> PL[libvctrl_plumbing] + C --> PO[libvctrl_porcelain] + H --> F[libvctrl
facade] + C --> F +``` --- ## License -This project is licensed under the MIT License. See the `LICENSE` file in the workspace root for details. +Licensed under the MIT License. See the repository for the full license text. diff --git a/libvctrl_core/Cargo.toml b/libvctrl_core/Cargo.toml index 0b4a89a7..6db201c0 100644 --- a/libvctrl_core/Cargo.toml +++ b/libvctrl_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libvctrl_core" -version = "3.0.0" +version = "3.0.1" edition = "2024" description = "Reference implementations of the libvctrl contracts (in-memory store, SHA-512 hasher, binary codec)" license = "MIT" diff --git a/libvctrl_core/README.md b/libvctrl_core/README.md index 771587cd..e2e1190d 100644 --- a/libvctrl_core/README.md +++ b/libvctrl_core/README.md @@ -1,210 +1,192 @@ # libvctrl_core -**Version:** 2.0.1 -**Crate type:** Rust library (reference implementations) -**Workspace:** libvcrtl - -`libvctrl_core` is the **batteries-included reference implementation layer** for the abstract contracts defined in [`libvctrl_handler`](https://docs.rs/libvctrl_handler). It provides production-ready, safe implementations of hashing, binary serialization, in-memory storage, reference management, and builder utilities. By consuming `libvctrl_handler` as its first downstream crate, `libvctrl_core` validates the contracts and gives developers a complete, working VCS backend stack out of the box. - -The crate enforces the same strict code quality standards as `libvctrl_handler`: - -- `#![forbid(unsafe_code)]` -- `#![deny(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]` -- `#![deny(missing_docs)]` -- `#![deny(rust_2018_idioms, unreachable_pub, unused_crate_dependencies, unused_qualifications)]` - ---- - -## Table of Contents - -- [Overview](#overview) -- [System Architecture](#system-architecture) -- [Core Features](#core-features) -- [Technology Stack](#technology-stack) -- [Project Structure](#project-structure) -- [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Configuration](#configuration) -- [Usage](#usage) -- [API Reference](#api-reference) - - [Codec Module](#codec-module) - - [BinaryEncoder](#binaryencoder) - - [BinaryDecoder](#binarydecoder) - - [Binary Format Specifications](#binary-format-specifications) - - [Hash Module](#hash-module) - - [Sha512Hasher](#sha512hasher) - - [Object Module](#object-module) - - [BlobBuilder](#blobbuilder) - - [CommitBuilder](#commitbuilder) - - [TagBuilder](#tagbuilder) - - [TreeBuilder](#treebuilder) - - [TreeEntryBuilder](#treeentrybuilder) - - [Store Module](#store-module) - - [MemoryStore](#memorystore) - - [MemoryRefStore](#memoryrefstore) - - [Validate Module](#validate-module) - - [validate_hash_bytes](#validate_hash_bytes) - - [validate_name](#validate_name) -- [Testing](#testing) -- [CI/CD Pipeline](#cicd-pipeline) -- [Deployment / Distribution](#deployment--distribution) -- [Security & Compliance](#security--compliance) -- [Contributing](#contributing) -- [License](#license) -- [Changelog](#changelog) +Reference implementations of the `libvctrl` contracts: a deterministic binary codec, a +SHA-512 content-addressing hasher, fluent object builders, and in-memory storage backends. +`libvctrl_core` is the layer that turns the abstract `libvctrl_handler` traits and immutable +types into working, production-ready components. + +- **Crate:** `libvctrl_core` 3.0.1 (library, `std`-only) +- **Language:** Rust, edition 2024 — MSRV **1.96.0** +- **License:** MIT +- **Repository:** https://github.com/mroczect/libvctrl +- **Documentation:** https://docs.rs/libvctrl_core + +> Most users should depend on the **`libvctrl` facade** rather than this crate directly. +> The facade re-exports everything in `libvctrl_core` plus the contracts and the crypto +> primitives under a single, ergonomic namespace. Reach for `libvctrl_core` directly only +> when you need the codec, builders, or in-memory stores without pulling in the facade's +> `crypto` namespace alias. --- ## Overview -`libvctrl_core` is the first concrete consumer of the `libvctrl_handler` traits. It transforms the abstract contracts into a runnable foundation for version control systems by providing: - -- **Binary codec** for deterministic serialization and deserialization. -- **SHA-512 hasher** for content addressing. -- **In-memory object and reference stores** for ephemeral storage. -- **Builder patterns** for ergonomic object construction. -- **Validation utilities** for names and hashes. +`libvctrl_handler` defines _what_ a version control object model looks like — the traits +(`Encoder`, `Decoder`, `Hasher`, `ObjectStore`, `RefStore`) and the immutable data types +(`Blob`, `Tree`, `TreeEntry`, `Commit`, `CommitMeta`, `Tag`, `Hash`, `UserID`). +`libvctrl_core` defines _how_ those contracts are realised: -Because `libvctrl_core` implements every key trait from `libvctrl_handler`, it also serves as a quality exemplar for downstream developers who need to write custom backends. All code is safe, strictly linted, heavily documented, and thoroughly tested. +- A **binary codec** that serialises objects into a deterministic, versioned byte stream + and parses untrusted byte streams back into validated objects. +- A **SHA-512 hasher** that bridges the raw `libvctrl_sha512` digest engine to the handler + `Hash` type used for content addressing. +- **Fluent builders** for constructing validated `Blob`, `Tree`, `TreeEntry`, `Commit`, + and `Tag` objects. +- **In-memory stores** implementing `ObjectStore` and `RefStore` over `HashMap`. -This crate intentionally does not perform persistent disk I/O or network operations; it focuses on core VCS logic that can be embedded in larger systems. +The crate is `std`-only: it relies on `std::io`, `std::sync::Arc`, `HashMap`, `String`, and +`Vec`. There is no `no_std` build path. The crate exposes **no crate-level feature flags**; +all feature configuration (`sha384`, `opt_size`) happens at the facade or +`libvctrl_sha512` level. --- -## System Architecture - -### Workspace Context - -Within the `libvcrtl` workspace, `libvctrl_core` sits directly above `libvctrl_handler` and below higher-level crates like `libvctrl_plumbing` and `libvctrl_porcelain`. - -```mermaid -graph TD - HANDLER[libvctrl_handler
Contracts and Types] - CORE[libvctrl_core
Reference Implementations] - PLUMBING[libvctrl_plumbing] - PORCELAIN[libvctrl_porcelain] - SHA512[libvctrl_sha512
Hash Implementation] - LIBVCTRL[libvctrl CLI] - - HANDLER --> CORE - SHA512 --> CORE - CORE --> PLUMBING - CORE --> PORCELAIN - PLUMBING --> LIBVCTRL - PORCELAIN --> LIBVCTRL -``` - -`libvctrl_core` depends on: +## Architecture -- `libvctrl_handler` version 4.4.0 for all contracts and data types. -- `libvctrl_sha512` version 2.0.0 for the raw SHA-512 hash algorithm. - -### Internal Module Architecture - -The crate is organized by domain responsibility: +`libvctrl_core` sits in the middle of a strictly layered, one-way dependency graph. The +contract layer (`libvctrl_handler`) depends only on the standard library. The reference +layer (`libvctrl_core`) depends on the contracts and on the raw crypto engine +(`libvctrl_sha512`). Above it sit the facade and the higher-level command crates. ```mermaid -graph LR - ROOT[libvctrl_core] - CODEC[codec] - HASH[hash] - OBJECT[object] - STORE[store] - VALIDATE[validate] - - ROOT --> CODEC - ROOT --> HASH - ROOT --> OBJECT - ROOT --> STORE - ROOT --> VALIDATE - - CODEC --> HANDLER[libvctrl_handler] +flowchart TD + subgraph Apps["Application layer"] + FACADE["libvctrl
facade (re-exports)"] + PL["libvctrl_plumbing"] + PO["libvctrl_porcelain"] + end + + subgraph Core["Reference implementation layer — libvctrl_core"] + CODEC["codec
BinaryEncoder / BinaryDecoder"] + HASH["hash
Sha512Hasher (adapter)"] + OBJ["object
builders"] + STORE["store
MemoryStore / MemoryRefStore"] + end + + subgraph Foundation["Foundation"] + HANDLER["libvctrl_handler
contracts & types"] + SHA["libvctrl_sha512
raw crypto engine"] + end + + FACADE --> Core + PL --> Core + PO --> Core + CODEC --> HANDLER HASH --> HANDLER - HASH --> SHA[libvctrl_sha512] - OBJECT --> HANDLER + HASH --> SHA + OBJ --> HANDLER STORE --> HANDLER - VALIDATE --> HANDLER ``` -Each module isolates a single responsibility: - -- **`codec`**: `BinaryEncoder` and `BinaryDecoder` for binary serialization. -- **`hash`**: `Sha512Hasher` bridging `libvctrl_sha512` to `Hasher`. -- **`object`**: Builder structs for ergonomic construction. -- **`store`**: In-memory `ObjectStore` and `RefStore` implementations. -- **`validate`**: Validation helpers for names and hashes. - -### Object Lifecycle Data Flow +Internally, each module implements a specific handler trait against the contract types: -The following sequence shows how a `Blob` is encoded, hashed, stored, and retrieved using `libvctrl_core`. +```mermaid +flowchart LR + subgraph Handler["libvctrl_handler contracts"] + ENC[Encoder trait] + DEC[Decoder trait] + HAS[Hasher trait] + OS[ObjectStore trait] + RS[RefStore trait] + TYPES[Blob / Tree / Commit / Tag / Hash / UserID] + end + + subgraph Core["libvctrl_core modules"] + CODEC[codec
BinaryEncoder + BinaryDecoder] + HASHMOD[hash
Sha512Hasher] + OBJMOD[object
builders] + STOREMOD[store
MemoryStore + MemoryRefStore] + end + + CODEC -.implements.-> ENC + CODEC -.implements.-> DEC + HASHMOD -.implements.-> HAS + STOREMOD -.implements.-> OS + STOREMOD -.implements.-> RS + CODEC -.consumes.-> TYPES + HASHMOD -.produces.-> TYPES + OBJMOD -.constructs.-> TYPES +``` + +### Codec round-trip + +A typical interaction encodes an object, hashes the encoded bytes to obtain a content +address, stores the bytes, and later decodes them back. The decoder is the trust boundary: +it treats every input as untrusted and validates structure, UTF-8, and system limits before +constructing a handler type. ```mermaid sequenceDiagram - participant App as Downstream App - participant Enc as BinaryEncoder - participant Hash as Sha512Hasher - participant Store as MemoryStore - - App->>Enc: encode_blob(&blob) - Enc-->>App: Vec - App->>Hash: hash(&encoded_bytes) - Hash-->>App: Hash - App->>Store: put(&hash, &encoded_bytes) - App->>Store: get(&hash) - Store-->>App: Box + participant App as Application + participant E as BinaryEncoder + participant H as Sha512Hasher + participant S as MemoryStore + participant D as BinaryDecoder + + App->>E: encode_*(&object, &mut writer) + E-->>App: Deterministic, versioned bytes + App->>H: hash(&mut bytes.as_slice()) + H->>H: Stream SHA-512 in fixed chunks + H-->>App: 64-byte Hash (content address) + App->>S: put(&hash, &bytes) + App->>S: get(&hash) + S-->>App: Stored bytes + App->>D: decode_*(reader) + D->>D: read_bounded (hard size cap) + D->>D: check_version (byte == 3) + D->>D: require_byte / require_slice + D->>D: Validate UTF-8 + re-check limits + D-->>App: Validated immutable object ``` --- ## Core Features -- **Binary serialization/deserialization** - Compact, deterministic, little-endian binary format with versioning and strict bounds checks. - -- **SHA-512 content addressing** - Produces 64-byte digests matching `HASH_LENGTH`, using an audited pure-Rust backend. - -- **Streaming object reads** - `MemoryStore::get` returns `Box`, enabling incremental consumption without large contiguous allocations. - -- **In-memory reference store** - `MemoryRefStore` supports branch and tag management with deterministic sorted iteration. - -- **Ergonomic builder patterns** - Fluent APIs for constructing blobs, commits, tags, trees, and tree entries. - -- **Defensive validation** - Prevents path traversal, empty names, and invalid hash lengths. - -- **Full POSIX tree fidelity** - Encoder and decoder support all five `EntryKind` variants: `Blob`, `Executable`, `Symlink`, `Tree`, `Submodule`. - -- **Thread-safe and allocation-efficient** - All concrete types are `Send + Sync`; builders transfer ownership without cloning. +- **Deterministic serialization.** The same object always produces the same bytes: + fixed version byte, little-endian integers, strict field order, no platform-specific + layouts. Determinism is required for content addressing to be stable. +- **Defense-in-depth decoding.** `BinaryDecoder` bounds the input before parsing, checks + every offset before slicing, validates all strings as UTF-8, and re-checks system limits + after numeric conversion. No slice indexing occurs without a preceding bounds check. +- **DoS-resistant reads.** `read_bounded` uses a 4 KiB chunk buffer and refuses to allocate + beyond a conservative per-object maximum, preventing allocation-based denial of service. +- **Versioned format.** Every encoded object begins with a version byte (`3`). The decoder + rejects any input whose first byte does not match, allowing the format to evolve safely. +- **Thin hasher adapter.** `Sha512Hasher` is a zero-sized, stateless, thread-safe adapter + that bridges the raw `libvctrl_sha512` engine to the handler `Hash` type, producing + 64-byte addresses matching `HASH_LENGTH`. +- **Fluent builders.** Construct validated `Blob`, `Tree`, `TreeEntry`, `Commit`, and `Tag` + objects through ergonomic builder APIs. +- **In-memory stores.** `MemoryStore` and `MemoryRefStore` provide ready-to-use + `ObjectStore` and `RefStore` implementations backed by `HashMap`. +- **Strict safety.** `#![forbid(unsafe_code)]` and a comprehensive set of denied Clippy and + rustc documentation lints are inherited from the workspace. --- ## Technology Stack -- **Language:** Rust (edition 2024) +- **Language:** Rust (edition 2024, MSRV 1.96.0) - **Dependencies:** - - `libvctrl_handler` 4.4.0 — contracts and types - - `libvctrl_sha512` 2.0.0 — SHA-512 implementation -- **Dev dependencies:** - - `proptest` 1.11.0 — property-based testing -- **Standard library:** - - `std::collections::HashMap` - - `std::io::{Cursor, Read}` - - `std::str` -- **Lints:** Clippy all, pedantic, nursery, cargo (all denied) + - `libvctrl_handler` 5.0.0 — contracts, types, constants, validation (path dependency) + - `libvctrl_sha512` 3.0.0 — raw SHA-512 / HMAC / HKDF engine, **with default features** + (SHA-384 enabled) +- **Dev-dependencies:** `proptest` 1.11.0 +- **Lint policy:** workspace-inherited, `#![forbid(unsafe_code)]`, denied missing-docs, + rust-2018-idioms, and a broad set of Clippy lints (including pedantic and nursery + groups). See the repository for the authoritative lint configuration. +- **Feature flags:** none at the crate level. + +> Note on the MSRV field: this crate's `Cargo.toml` does not yet declare +> `rust-version = "1.96"`. The workspace standard is Rust 1.96.0, and this README +> documents that as the MSRV. Adding `rust-version = "1.96"` to +> `libvctrl_core/Cargo.toml` is recommended in a future maintenance pass. --- ## Project Structure -Within the `libvctrl_core` crate: - ```text libvctrl_core/ ├── Cargo.toml @@ -223,14 +205,10 @@ libvctrl_core/ │ ├── commit.rs │ ├── tag.rs │ └── tree.rs - ├── store/ - │ ├── mod.rs - │ ├── memory.rs - │ └── ref_store.rs - └── validate/ + └── store/ ├── mod.rs - ├── hash.rs - └── name.rs + ├── memory.rs + └── ref_store.rs ``` --- @@ -239,605 +217,402 @@ libvctrl_core/ ### Prerequisites -- Rust toolchain 1.96.0 or newer (edition 2024 required) +- Rust toolchain **1.96.0** or newer (edition 2024 is required) - Cargo -- No external services or system dependencies + +No system libraries or external services are required. ### Installation -Add `libvctrl_core` to your `Cargo.toml`: +For most users, depend on the facade instead: + +```toml +[dependencies] +libvctrl = "2.1" +``` + +To depend on `libvctrl_core` directly (codec/builders/stores only, without the facade's +`crypto` namespace alias): ```toml [dependencies] -libvctrl_core = "2.0.1" +libvctrl_core = "3.0" ``` -Or use Cargo: +Or via Cargo: -```sh +```bash cargo add libvctrl_core ``` -This will automatically pull the required `libvctrl_handler` and `libvctrl_sha512` dependencies. +This will pull `libvctrl_handler` and `libvctrl_sha512` as transitive dependencies. ### Configuration -No configuration is required. The crate is a pure library with no environment variables or runtime configuration. +`libvctrl_core` exposes **no crate-level feature flags**. Feature configuration +(`sha384`, `opt_size`) is controlled at the `libvctrl` facade or `libvctrl_sha512` level. +Because this crate depends on `libvctrl_sha512` **with default features**, SHA-384 support +is enabled transitively. If you need to control the crypto feature set, use the facade, +which wires `libvctrl_sha512` with `default-features = false` and re-enables only what it +needs. --- ## Usage -### Quick Start: Encode, Hash, Store, Retrieve - -```rust -use libvctrl_handler::{Blob, Encoder, Hasher, ObjectStore}; -use libvctrl_core::codec::BinaryEncoder; -use libvctrl_core::hash::Sha512Hasher; -use libvctrl_core::store::MemoryStore; -use std::io::Read; - -// 1. Create content -let blob = Blob::new(b"my content".to_vec()); - -// 2. Encode to deterministic bytes -let encoder = BinaryEncoder; -let bytes = encoder.encode_blob(&blob).unwrap(); - -// 3. Hash the bytes to get a content address -let hasher = Sha512Hasher; -let hash = hasher.hash(&bytes).unwrap(); - -// 4. Store the encoded bytes in memory -let mut store = MemoryStore::new(); -store.put(&hash, &bytes).unwrap(); - -// 5. Read back via streaming interface -let mut reader = store.get(&hash).unwrap(); -let mut buf = Vec::new(); -reader.read_to_end(&mut buf).unwrap(); -assert_eq!(buf, bytes); -``` - -### Building a Commit Using Builders +### Encode, hash, store, and decode a Blob ```rust -use libvctrl_core::object::CommitBuilder; -use libvctrl_handler::{Hash, UserID}; - -let tree = Hash::from_bytes(&[0; 64]).unwrap(); -let author = UserID::new("Alice".to_owned(), "alice@example.com".to_owned()).unwrap(); -let committer = UserID::new("Bob".to_owned(), "bob@example.com".to_owned()).unwrap(); - -let commit = CommitBuilder::new() - .tree(tree) - .author(author) - .committer(committer) - .message("Initial commit") - .build() - .unwrap(); - -assert_eq!(commit.message(), "Initial commit"); -``` - ---- - -## API Reference - -All public items are exported from their respective modules. The recommended import paths are shown in each section. - -### Codec Module - -Module path: `libvctrl_core::codec` - -Contains the binary encoder and decoder. - -#### BinaryEncoder - -```rust -pub struct BinaryEncoder; -``` - -Implements `libvctrl_handler::Encoder`. - -| Method | Description | -| ---------------------------------------------------------------------- | -------------------------------------------------------- | -| `encode_blob(&self, blob: &Blob) -> Result, VctrlError>` | Encodes a `Blob` into versioned, length-prefixed binary. | -| `encode_tree(&self, tree: &Tree) -> Result, VctrlError>` | Encodes a `Tree` with entries, kinds, and hashes. | -| `encode_commit(&self, commit: &Commit) -> Result, VctrlError>` | Encodes a `Commit` with metadata and parents. | -| `encode_tag(&self, tag: &Tag) -> Result, VctrlError>` | Encodes a `Tag` with optional tagger. | - -**Example:** - -```rust -use libvctrl_handler::{Blob, Encoder}; -use libvctrl_core::codec::BinaryEncoder; - -let encoder = BinaryEncoder; -let blob = Blob::new(b"hello".to_vec()); -let bytes = encoder.encode_blob(&blob).unwrap(); - -assert_eq!(bytes[0], 2); // version byte -``` - -#### BinaryDecoder - -```rust -pub struct BinaryDecoder; -``` - -Implements `libvctrl_handler::Decoder`. - -| Method | Description | -| ----------------------------------------------------------------- | ----------------------------------------- | -| `decode_blob(&self, data: &[u8]) -> Result` | Parses a binary blob. | -| `decode_tree(&self, data: &[u8]) -> Result` | Parses a binary tree with sorted entries. | -| `decode_commit(&self, data: &[u8]) -> Result` | Parses a binary commit with all fields. | -| `decode_tag(&self, data: &[u8]) -> Result` | Parses a binary tag. | - -**Example:** - -```rust -use libvctrl_handler::{Blob, Encoder, Decoder}; +use std::io::Cursor; +use libvctrl_handler::{Blob, Decoder, Encoder, Hasher, ObjectStore}; use libvctrl_core::codec::{BinaryEncoder, BinaryDecoder}; - -let original = Blob::new(b"data".to_vec()); -let bytes = BinaryEncoder.encode_blob(&original).unwrap(); -let decoded = BinaryDecoder.decode_blob(&bytes).unwrap(); -assert_eq!(decoded, original); -``` - -#### Binary Format Specifications - -All payloads start with a version byte (`VERSION = 2`), followed by little-endian integers and length-prefixed strings. - -**Blob format:** - -| Offset | Size | Field | -| ------ | ---------- | ------------------- | -| 0 | 1 | Version | -| 1 | 8 | `data_len` (u64 LE) | -| 9 | `data_len` | `data` | - -**Tree format:** - -| Offset | Size | Field | -| ------ | ------ | ------------------------------------------------------------------------- | -| 0 | 1 | Version | -| 1 | 4 | `entry_count` (u32 LE) | -| 5 | varies | Repeated entries: `name_len` (u8), `name`, `kind` (u8), `hash` (64 bytes) | - -**Commit format:** - -| Field | Size | -| --------------------------- | ---------- | -| Version | 1 | -| Tree hash | 64 | -| Parent count | 1 | -| Parent hashes | 64 * count | -| Author name len + name | 1 + len | -| Author email len + email | 1 + len | -| Committer name len + name | 1 + len | -| Committer email len + email | 1 + len | -| Message len | 4 | -| Message | len | -| Timestamp | 8 | -| Timezone offset | 2 | -| Encoding len | 1 | -| Encoding (if len > 0) | len | - -**Tag format:** - -Similar to commit, but starts with name and target hash, then optional tagger. - -The decoder enforces all system limits (`MAX_BLOB_SIZE`, `MAX_MESSAGE_LENGTH`, `MAX_TREE_ENTRIES`) and validates UTF-8 to prevent denial-of-service attacks. - ---- - -### Hash Module - -Module path: `libvctrl_core::hash` - -#### Sha512Hasher - -```rust -#[derive(Debug, Default, Clone)] -pub struct Sha512Hasher; -``` - -Implements `libvctrl_handler::Hasher`. - -**Methods:** - -- `hash(&self, data: &[u8]) -> Result` - -Computes a SHA-512 digest of the input using the `libvctrl_sha512` crate and wraps it in a `Hash`. The digest length is always 64 bytes, so conversion cannot fail. - -**Example:** - -```rust -use libvctrl_handler::Hasher; use libvctrl_core::hash::Sha512Hasher; +use libvctrl_core::store::MemoryStore; -let hasher = Sha512Hasher; -let hash = hasher.hash(b"hello world".as_ref()).unwrap(); -assert_eq!(hash.as_bytes().len(), 64); -``` - ---- - -### Object Module - -Module path: `libvctrl_core::object` - -Contains builders for ergonomic object construction. - -#### BlobBuilder - -```rust -#[derive(Debug, Default)] -pub struct BlobBuilder { /* private */ } - -impl BlobBuilder { - pub const fn new() -> Self; - pub fn with_data(self, data: Vec) -> Self; - pub fn build(self) -> Blob; -} -``` - -**Example:** - -```rust -use libvctrl_core::object::BlobBuilder; - -let blob = BlobBuilder::new() - .with_data(b"file content".to_vec()) - .build(); - -assert_eq!(blob.size(), 12); -``` - -#### CommitBuilder - -```rust -#[derive(Debug, Default)] -pub struct CommitBuilder { /* private */ } - -impl CommitBuilder { - pub const fn new() -> Self; - pub const fn tree(self, tree: Hash) -> Self; - pub fn parent(self, parent: Hash) -> Self; - pub fn author(self, author: UserID) -> Self; - pub fn committer(self, committer: UserID) -> Self; - pub fn message(self, msg: impl Into) -> Self; - pub fn meta(self, meta: CommitMeta) -> Self; - pub fn build(self) -> Result; -} -``` - -`build()` returns `VctrlError::Other` if any required field is missing. +fn main() -> Result<(), Box> { + // 1. Create a validated blob. + let blob = Blob::new(b"hello world".to_vec())?; -**Example:** + // 2. Encode into a deterministic, versioned byte stream. + let mut encoded = Vec::new(); + BinaryEncoder.encode_blob(&blob, &mut encoded)?; -```rust -use libvctrl_core::object::CommitBuilder; -use libvctrl_handler::{Hash, UserID}; - -let tree = Hash::from_bytes(&[0; 64]).unwrap(); -let user = UserID::new("Alice".to_owned(), "a@b.com".to_owned()).unwrap(); - -let commit = CommitBuilder::new() - .tree(tree) - .author(user.clone()) - .committer(user) - .message("Initial commit") - .build() - .unwrap(); -``` + // 3. Hash the encoded bytes to obtain a 64-byte content address. + let hash = Sha512Hasher.hash(&mut encoded.as_slice())?; -#### TagBuilder + // 4. Store the encoded object in memory. + let mut store = MemoryStore::new(); + store.put(&hash, &encoded)?; -```rust -#[derive(Debug, Default)] -pub struct TagBuilder { /* private */ } - -impl TagBuilder { - pub const fn new() -> Self; - pub fn name(self, name: impl Into) -> Self; - pub const fn target(self, target: Hash) -> Self; - pub fn tagger(self, tagger: UserID) -> Self; - pub fn message(self, msg: impl Into) -> Self; - pub fn meta(self, meta: CommitMeta) -> Self; - pub fn build(self) -> Result; + // 5. Retrieve and decode back into a validated object. + let reader = store.get(&hash)?; + let decoded = BinaryDecoder.decode_blob(reader)?; + assert_eq!(decoded, blob); + Ok(()) } ``` -**Example:** +### Encode and decode a Tree ```rust -use libvctrl_core::object::TagBuilder; -use libvctrl_handler::Hash; - -let target = Hash::from_bytes(&[0; 64]).unwrap(); -let tag = TagBuilder::new() - .name("v1.0.0") - .target(target) - .build() - .unwrap(); - -assert_eq!(tag.name(), "v1.0.0"); -``` - -#### TreeBuilder - -```rust -#[derive(Debug, Default)] -pub struct TreeBuilder { /* private */ } - -impl TreeBuilder { - pub const fn new() -> Self; - pub fn entry(self, entry: TreeEntry) -> Self; - pub fn add_entry(self, name: String, kind: EntryKind, hash: Hash) -> Result; - pub fn build(self) -> Result; -} -``` - -`build()` delegates to `Tree::new`, enforcing sorted entry order. - -**Example:** - -```rust -use libvctrl_core::object::TreeBuilder; -use libvctrl_handler::{EntryKind, Hash}; - -let hash = Hash::from_bytes(&[0; 64]).unwrap(); -let tree = TreeBuilder::new() - .add_entry("a.txt".to_owned(), EntryKind::Blob, hash)? - .add_entry("b.txt".to_owned(), EntryKind::Blob, hash)? - .build() - .unwrap(); -# Ok::<(), libvctrl_handler::VctrlError>(()) -``` +use std::io::Cursor; +use libvctrl_handler::{Encoder, Decoder, EntryKind, Hash, Tree, TreeEntry}; +use libvctrl_core::codec::{BinaryEncoder, BinaryDecoder}; -#### TreeEntryBuilder +fn main() -> Result<(), Box> { + let hash = Hash::from_bytes(&[0u8; 64])?; + let entry = TreeEntry::new("a.txt".to_owned(), EntryKind::Blob, hash)?; + let tree = Tree::new(vec![entry])?; -```rust -#[derive(Debug)] -pub struct TreeEntryBuilder { /* private */ } + let mut encoded = Vec::new(); + BinaryEncoder.encode_tree(&tree, &mut encoded)?; -impl TreeEntryBuilder { - pub const fn new(name: String, kind: EntryKind, hash: Hash) -> Self; - pub fn build(self) -> Result; + let decoded = BinaryDecoder.decode_tree(Cursor::new(encoded.as_slice()))?; + assert_eq!(decoded, tree); + Ok(()) } ``` -**Example:** +### Encode and decode a Commit ```rust -use libvctrl_core::object::TreeEntryBuilder; -use libvctrl_handler::{EntryKind, Hash}; - -let hash = Hash::from_bytes(&[0; 64]).unwrap(); -let entry = TreeEntryBuilder::new("file.txt".to_owned(), EntryKind::Blob, hash) - .build() - .unwrap(); -``` - ---- - -### Store Module - -Module path: `libvctrl_core::store` - -#### MemoryStore +use std::io::Cursor; +use libvctrl_handler::{Commit, Decoder, Encoder, Hash, UserID}; +use libvctrl_core::codec::{BinaryEncoder, BinaryDecoder}; -```rust -#[derive(Debug, Default)] -pub struct MemoryStore { /* private */ } +fn main() -> Result<(), Box> { + let tree = Hash::from_bytes(&[1u8; 64])?; + let author = UserID::new("Alice".to_owned(), "alice@example.com".to_owned())?; + let committer = UserID::new("Bob".to_owned(), "bob@example.com".to_owned())?; + let commit = Commit::new(tree, vec![], author, committer, "Initial commit".to_owned())?; -impl MemoryStore { - pub fn new() -> Self; -} + let mut encoded = Vec::new(); + BinaryEncoder.encode_commit(&commit, &mut encoded)?; -impl ObjectStore for MemoryStore { - fn put(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError>; - fn get(&self, hash: &Hash) -> Result, VctrlError>; - fn delete(&mut self, hash: &Hash) -> Result<(), VctrlError>; - fn exists(&self, hash: &Hash) -> Result; + let decoded = BinaryDecoder.decode_commit(Cursor::new(encoded.as_slice()))?; + assert_eq!(decoded, commit); + Ok(()) } ``` -Uses a `HashMap>` internally. `get` clones the stored bytes and wraps them in a `std::io::Cursor`, enabling streaming reads. - -**Example:** +### Hash a streaming input ```rust -use libvctrl_core::store::MemoryStore; -use libvctrl_handler::{Hash, ObjectStore}; -use std::io::Read; - -let mut store = MemoryStore::new(); -let hash = Hash::from_bytes(&[0; 64]).unwrap(); -store.put(&hash, b"my data").unwrap(); - -let mut reader = store.get(&hash).unwrap(); -let mut buf = Vec::new(); -reader.read_to_end(&mut buf).unwrap(); -assert_eq!(buf, b"my data"); -``` - -#### MemoryRefStore - -```rust -#[derive(Debug, Default)] -pub struct MemoryRefStore { /* private */ } - -impl MemoryRefStore { - pub fn new() -> Self; -} - -impl RefStore for MemoryRefStore { - type RefsIterator = std::vec::IntoIter>; +use libvctrl_core::hash::Sha512Hasher; +use libvctrl_handler::Hasher; - fn set_ref(&mut self, name: &str, hash: &Hash) -> Result<(), VctrlError>; - fn get_ref(&self, name: &str) -> Result; - fn delete_ref(&mut self, name: &str) -> Result<(), VctrlError>; - fn list_refs(&self) -> Result; +fn main() -> Result<(), Box> { + let hash = Sha512Hasher.hash(b"hello world".as_ref())?; + assert_eq!(hash.as_bytes().len(), 64); + Ok(()) } ``` -Enforces name length limits and returns sorted reference names. +### Building objects with fluent builders -**Example:** +The `object` module provides ergonomic builders that wrap the handler constructors with +validation. The builder surface mirrors the facade re-exports +(`BlobBuilder`, `CommitBuilder`, `TagBuilder`, `TreeBuilder`, `TreeEntryBuilder`): ```rust -use libvctrl_core::store::MemoryRefStore; -use libvctrl_handler::{Hash, RefStore}; - -let mut store = MemoryRefStore::new(); -let hash = Hash::from_bytes(&[0; 64]).unwrap(); -store.set_ref("refs/heads/main", &hash).unwrap(); -assert_eq!(store.get_ref("refs/heads/main").unwrap(), hash); +// Conceptual — see docs.rs/libvctrl_core for the exact builder API. +// use libvctrl_core::object::{BlobBuilder, CommitBuilder, TreeBuilder, TreeEntryBuilder}; +// use libvctrl_handler::{EntryKind, Hash, UserID}; +// +// let blob = BlobBuilder::new().data(b"content".to_vec()).build()?; +// let entry = TreeEntryBuilder::new("file.txt".to_owned(), EntryKind::Blob, hash).build()?; +// let tree = TreeBuilder::new().entry(entry).build()?; +// let user = UserID::new("Alice".to_owned(), "alice@example.com".to_owned())?; +// let commit = CommitBuilder::new() +// .tree(tree_hash) +// .author(user.clone()) +// .committer(user) +// .message("Initial commit") +// .build()?; ``` --- -### Validate Module +## API Reference / Core Modules -Module path: `libvctrl_core::validate` +Full API documentation is published at . The summary below +describes each module and, for the codec, the canonical binary format. -#### validate_hash_bytes +### `codec` — Binary codec -```rust -pub const fn validate_hash_bytes(bytes: &[u8]) -> Result<(), VctrlError>; -``` +Implements the `Encoder` and `Decoder` traits. Two zero-sized types: -Checks that a byte slice is exactly `HASH_LENGTH` (64) bytes long. +- **`BinaryEncoder`** — writes objects to any `std::io::Write` sink. Stateless; streams + fields directly without allocating the entire payload upfront. All length conversions are + checked with `try_from` so impossible lengths are reported as + `VctrlError::SerializationError` rather than causing silent truncation. +- **`BinaryDecoder`** — reads objects from any `std::io::Read` source. The trust boundary + for untrusted input. -**Example:** +The current format version is `VERSION = 3` (`pub const VERSION: u8 = 3`). The decoder +rejects any input whose first byte does not match `EXPECTED_VERSION = 3`. -```rust -use libvctrl_core::validate::hash::validate_hash_bytes; -use libvctrl_handler::HASH_LENGTH; - -let valid = [0u8; HASH_LENGTH]; -assert!(validate_hash_bytes(&valid).is_ok()); -``` +#### Decoder validation pipeline -#### validate_name +The decoder follows a defense-in-depth strategy. No slice indexing is performed without a +preceding bounds check. -```rust -pub fn validate_name(name: &str) -> Result<(), VctrlError>; +```mermaid +flowchart TD + R[Untrusted Read stream] --> RB[read_bounded
4 KiB chunks, hard size cap] + RB -->|exceeds cap| ERR1[VctrlError::CorruptedData] + RB --> CV[check_version
first byte == 3] + CV -->|wrong/missing| ERR2[VctrlError::CorruptedData] + CV -->|ok| PARSE[Parse with require_byte / require_slice
overflow-safe slicing] + PARSE -->|truncated / unknown kind / trailing bytes| ERR3[VctrlError::CorruptedData] + PARSE --> UTF[Validate all strings as UTF-8] + UTF -->|invalid| ERR4[VctrlError::CorruptedData] + UTF --> LIM[Re-check system limits
MAX_BLOB_SIZE / MAX_TREE_ENTRIES / MAX_MESSAGE_LENGTH] + LIM -->|exceeds limit| ERR5[VctrlError::CorruptedData / SerializationError] + LIM --> CONSTRUCT[Construct handler type
Blob / Tree / Commit / Tag] + CONSTRUCT --> OBJ[Immutable validated object] ``` -Validates that a name is: +#### Binary format layouts -- Non-empty -- Not longer than `MAX_NAME_LENGTH` -- Does not contain `/` -- Is not `.` or `..` +All integers are little-endian. Strings are `u8` length-prefixed. Larger payloads use +`u32` or `u64` length prefixes. Every object begins with a one-byte version field (`3`). -**Example:** +**Blob** -```rust -use libvctrl_core::validate::name::validate_name; - -assert!(validate_name("feature_branch").is_ok()); -assert!(validate_name("../invalid").is_err()); -``` +| Offset | Size | Field | +| ------ | ---------- | ------------------- | +| 0 | 1 | Version byte | +| 1 | 8 | `data_len` (u64 LE) | +| 9 | `data_len` | Raw blob data | + +**Tree** + +| Offset | Size | Field | +| ------ | ------ | ---------------------------- | +| 0 | 1 | Version byte | +| 1 | 4 | `entry_count` (u32 LE) | +| 5 | varies | Repeated entries (see below) | + +Each tree entry: + +| Field | Size | +| ----------- | ---------- | +| `name_len` | 1 (u8) | +| `name` | `name_len` | +| `kind_byte` | 1 (u8) | +| `hash` | 64 | + +`EntryKind` discriminants: + +| Byte | Kind | +| ---- | ---------- | +| 0 | Blob | +| 1 | Executable | +| 2 | Symlink | +| 3 | Tree | +| 4 | Submodule | + +**Commit** + +| Field | Size | +| ---------------------- | ----------- | +| Version | 1 | +| Tree hash | 64 | +| Parent count | 2 (u16 LE) | +| Parent hashes | 64 * count | +| Author name length | 1 | +| Author name | length | +| Author email length | 1 | +| Author email | length | +| Committer name length | 1 | +| Committer name | length | +| Committer email length | 1 | +| Committer email | length | +| Message length | 4 (u32 LE) | +| Message | length | +| Timestamp | 8 (i64 LE) | +| Timezone offset | 2 (i16 LE) | +| Encoding length | 1 | +| Encoding | length or 0 | + +**Tag** + +| Field | Size | +| ------------------- | -------------- | +| Version | 1 | +| Name length | 1 | +| Name | length | +| Target hash | 64 | +| Tagger presence | 1 | +| Tagger name length | 1 (if present) | +| Tagger name | length | +| Tagger email length | 1 (if present) | +| Tagger email | length | +| Message length | 4 (u32 LE) | +| Message | length | +| Timestamp | 8 (i64 LE) | +| Timezone offset | 2 (i16 LE) | +| Encoding length | 1 | +| Encoding | length or 0 | + +#### Error mapping + +The codec surfaces three `VctrlError` variants: + +- `VctrlError::CorruptedData` — version mismatch, missing/truncated length prefix, + length mismatch, unknown entry-kind byte, trailing bytes, invalid UTF-8, or input + exceeding the bounded size cap. The dominant failure mode on the decode path. +- `VctrlError::SerializationError` — length overflow that cannot be represented in the + prefix integer (e.g. a name longer than `u8::MAX`, a message longer than `u32::MAX`), + or a message exceeding `MAX_MESSAGE_LENGTH`. The dominant failure mode on the encode + path. +- `VctrlError::IoError` — underlying `Read`/`Write` failure, wrapped via + `std::sync::Arc` so the error remains cloneable. + +### `hash` — Content addressing + +- **`Sha512Hasher`** — a zero-sized, stateless, thread-safe type implementing the `Hasher` + trait. It is a **thin adapter** that bridges the raw `libvctrl_sha512` digest engine to + the handler `Hash` type. The `hash` method reads from any `std::io::Read` in fixed-size + chunks, feeds each chunk into the SHA-512 engine, and finalises into a 64-byte `Hash` + whose length always matches `HASH_LENGTH`. + +### `object` — Fluent builders + +Builders for `Blob`, `Commit`, `Tag`, `Tree`, and `TreeEntry`. Each builder wraps the +corresponding handler constructor with validation and a fluent API: + +- `BlobBuilder` +- `CommitBuilder` +- `TagBuilder` +- `TreeBuilder` +- `TreeEntryBuilder` + +See for the exact builder method surface. The builders are +also re-exported at the root of the `libvctrl` facade. + +### `store` — In-memory storage + +Two `HashMap`-backed implementations: + +- **`MemoryStore`** — implements `ObjectStore`. Stores encoded object bytes keyed by `Hash`. +- **`MemoryRefStore`** — implements `RefStore`. Stores named references (e.g. branch and + tag names) pointing at `Hash` values. + +Both are intended for testing, prototyping, and ephemeral in-process VCS sessions. For +persistent or remote backends, implement `ObjectStore` and `RefStore` directly. --- ## Testing -The crate includes unit tests and doctests. Run all tests with: +Run the crate's test suite with Cargo: -```sh -cargo test --all-features +```bash +cargo test ``` -Run only doctests: +Property-based tests use `proptest` (a dev-dependency). To run the entire workspace test +suite from the repository root: -```sh -cargo test --doc +```bash +cargo test --workspace ``` -Run property-based tests (using `proptest`): +To verify the strict lint policy is satisfied: -```sh -cargo test --test proptest -``` - -Run Clippy with strict lints: - -```sh -cargo clippy --all-targets --all-features -- -D warnings +```bash +cargo clippy --workspace --all-targets -- -D warnings +cargo doc --workspace --no-deps ``` --- -## CI/CD Pipeline - -No CI/CD pipeline is currently configured in the repository. - -If one is added, it should include the following stages: - -```mermaid -graph LR - A[Push] --> B[Format Check] - B --> C[Clippy Lint] - C --> D[Run Tests] - D --> E[Build Docs] - E --> F[Publish to crates.io] -``` - ---- - -## Deployment / Distribution +## Contributing -The crate is intended to be published to crates.io. +Contributions are welcome. The crate enforces `#![forbid(unsafe_code)]` and inherits a +strict, denied Clippy and documentation-lint policy from the workspace; all public items +must be documented. -Release process: +For contribution guidelines, code style, and the full lint configuration, see the +repository's `CONTRIBUTING.md` and the workspace root `README.md`: -1. Update `version` in `Cargo.toml`. -2. Update `CHANGELOG.md`. -3. Run `cargo publish --dry-run`. -4. Run `cargo publish`. +- Repository: https://github.com/mroczect/libvctrl -After publication, documentation will be available at `https://docs.rs/libvctrl_core`. +When contributing to `libvctrl_core`, keep the contract/implementation boundary clean: new +behaviour belongs here, new contracts belong in `libvctrl_handler`, and new user-facing +commands belong in `libvctrl_plumbing` or `libvctrl_porcelain`. --- -## Security & Compliance - -`libvctrl_core` is a foundational layer for version control systems and adheres to strict security practices: - -- **No unsafe code:** `#![forbid(unsafe_code)]` guarantees memory safety. -- **DoS protection:** Binary decoder enforces `MAX_BLOB_SIZE`, `MAX_TREE_ENTRIES`, and `MAX_MESSAGE_LENGTH` before allocation. -- **Strict UTF-8 validation:** All decoded strings are checked for valid UTF-8. -- **Path traversal prevention:** `validate_name` rejects `/`, `.`, and `..`. -- **Deterministic serialization:** Binary format ensures reproducible hashes. -- **Streaming reads:** `MemoryStore::get` returns `Box` to avoid loading large objects entirely into memory. -- **Audited cryptography:** `Sha512Hasher` delegates to `libvctrl_sha512`, which is pure Rust and auditable. +## Ecosystem -Downstream implementations must follow the guidelines in `SECURITY.md` at the workspace root. +`libvctrl_core` is one layer of a larger workspace. The related crates are listed below; +each has its own documentation. ---- - -## Contributing +| Crate | Role | Documentation | +| -------------------- | -------------------------------------------------------- | ---------------------------------- | +| `libvctrl` | Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl | +| `libvctrl_handler` | Contract layer: traits, types, limits, validation | https://docs.rs/libvctrl_handler | +| `libvctrl_sha512` | Zero-dependency SHA-512 / HMAC / HKDF primitives | https://docs.rs/libvctrl_sha512 | +| `libvctrl_plumbing` | Command-level VCS operations built on `libvctrl_core` | https://docs.rs/libvctrl_plumbing | +| `libvctrl_porcelain` | High-level, user-facing VCS operations | https://docs.rs/libvctrl_porcelain | -Contributions are welcome. Follow the workspace `CONTRIBUTING.md`. +The dependency flow is strictly one-way: `handler` is the foundation, `core` implements the +contracts, the facade re-exports both, and `plumbing`/`porcelain` build on `core`. -For this crate, ensure: - -- All public items have documentation with doctests. -- No `unsafe` code. -- Run `cargo fmt`. -- Run `cargo clippy --all-targets --all-features -- -D warnings`. -- All tests pass with `cargo test --all-features`. +```mermaid +flowchart LR + H[libvctrl_handler
contracts] --> C[libvctrl_core
reference impl] + C --> PL[libvctrl_plumbing] + C --> PO[libvctrl_porcelain] + H --> F[libvctrl
facade] + C --> F +``` --- ## License -This project is licensed under the MIT License. See the `LICENSE` file in the workspace root for details. +Licensed under the MIT License. See the repository for the full license text. diff --git a/libvctrl_handler/Cargo.toml b/libvctrl_handler/Cargo.toml index 9283dbe1..dcde7cb9 100644 --- a/libvctrl_handler/Cargo.toml +++ b/libvctrl_handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libvctrl_handler" -version = "5.0.0" +version = "5.0.1" edition = "2024" description = "Fundamental contracts for building a version control system – no implementations, only traits and types" license = "MIT" diff --git a/libvctrl_handler/README.md b/libvctrl_handler/README.md index c52d1c5b..259ba0da 100644 --- a/libvctrl_handler/README.md +++ b/libvctrl_handler/README.md @@ -1,152 +1,182 @@ # libvctrl_handler -**Fundamental contracts for building a version control system – no implementations, only traits and types** - -_Constitution (handler), reference implementation (core), and plumbing commands_ +Fundamental contracts for building a version control system — no implementations, only +traits, types, and validation. `libvctrl_handler` is the "constitution" layer of the +`libvctrl` ecosystem: it defines _what_ a VCS object model looks like without prescribing +_how_ it is stored, serialised, signed, or transported. Every other crate in the workspace +implements or consumes these contracts. + +- **Crate:** `libvctrl_handler` 5.0.1 (library, `std`-only, zero external dependencies) +- **Language:** Rust, edition 2024 — MSRV **1.96.0** +- **License:** MIT +- **Repository:** https://github.com/mroczect/libvctrl +- **Documentation:** https://docs.rs/libvctrl_handler + +> Most users should depend on the **`libvctrl` facade**, which re-exports this crate +> alongside the reference implementation and the crypto primitives. Reach for +> `libvctrl_handler` directly only when you are implementing a custom backend or need the +> contract surface without the reference implementation. --- ## Overview -`libvctrl` is a modular framework for building version control systems, structured around a strict separation between **contracts** and **implementations**. This repository contains the foundational contract layer, packaged as the crate `libvctrl_handler`. - -The handler crate defines: +`libvctrl_handler` is a pure contracts crate. It contains no concrete implementations — +no storage backends, no serializers, no hashing engines. Instead it provides: -- A complete set of **traits** for every major VCS operation: object storage, encoding, decoding, hashing, indexing, reference management, reflogs, remote transport, packing, signing, verification, revision walking, diffing, blame, and configuration. -- **Data types** representing Git objects (`Blob`, `Tree`, `Commit`, `Tag`) and supporting structures (`Hash`, `UserID`, `TreeEntry`, deltas, merges, reflog entries). -- **Constants** enforcing safe size and count limits. -- **Validation functions** that define the security boundary for names, references, tree entries, and hashes. -- A unified **error type** (`VctrlError`) that all trait implementations must use. +- **Traits** defining every major VCS operation: object storage, encoding, decoding, + hashing, indexing, reference management, reflogs, remote transport, packing, signing, + verification, revision walking, diffing, blame, and configuration. +- **Immutable data types** representing Git objects (`Blob`, `Tree`, `Commit`, `Tag`) and + supporting structures (`Hash`, `UserID`, `TreeEntry`, `CommitMeta`, deltas, merges, + reflog entries). +- **Constants** enforcing safe size and count limits to prevent resource exhaustion. +- **Validation functions** that define the security boundary for names, references, tree + entries, and hashes. +- A unified **error type** (`VctrlError`) that all trait implementations must return. -`libvctrl_handler` contains **no concrete implementations**. It is the "constitution" upon which all other crates in the `libvctrl` ecosystem are built. The reference implementation `libvctrl_core` provides working implementations of the traits (in-memory object store, binary encoder/decoder, SHA-512 hasher, etc.). Higher-level crates `libvctrl_plumbing` and `libvctrl_porcelain` build on top of `libvctrl_core`. - -The primary facade crate is `libvctrl`, which re-exports the handler types and the reference implementation so integrators can use the whole framework with a single dependency. +The crate depends only on the Rust standard library. It is `std`-only and exposes no +crate-level feature flags. There is no `no_std` build path at present. --- ## Architecture -The architecture enforces a one-way dependency flow: - -- **Handler (contract layer)** — this crate — depends only on the Rust standard library. -- **Core (reference implementation)** depends on the handler and provides concrete implementations. -- **Plumbing/Porcelain** depend on the core and provide command-level or high-level VCS operations. - -The following diagram illustrates the crate dependency structure: +`libvctrl_handler` is the foundation of a strictly layered, one-way dependency graph. It +sits at the bottom; the reference implementation (`libvctrl_core`) implements its traits; +the facade (`libvctrl`) re-exports both; and the command crates (`libvctrl_plumbing`, +`libvctrl_porcelain`) build on top. ```mermaid -graph TD - subgraph Contract Layer - H[libvctrl_handler] - H --> C[constants] - H --> E[enums] - H --> ER[errors] - H --> M[macros] - H --> T[traits] - H --> TY[types] - H --> V[validation] +flowchart TD + subgraph Apps["Application layer"] + FACADE["libvctrl
facade (re-exports)"] + PL["libvctrl_plumbing"] + PO["libvctrl_porcelain"] + end + + subgraph Ref["Reference implementation"] + CORE["libvctrl_core
codec, builders, stores, hasher"] end - subgraph Reference Implementation - CORE[libvctrl_core] - CORE --> H + subgraph Contracts["Contract layer — libvctrl_handler (this crate)"] + HANDLER["traits + types + constants
enums + errors + macros + validation
std-only, zero dependencies"] end - subgraph Applications - PL[libvctrl_plumbing] - PO[libvctrl_porcelain] - PL --> CORE - PO --> CORE + subgraph Crypto["Cryptography"] + SHA["libvctrl_sha512
raw SHA-512 engine"] end - FACADE[libvctrl facade crate] - FACADE --> H + FACADE --> HANDLER FACADE --> CORE + FACADE --> SHA + PL --> CORE + PO --> CORE + CORE --> HANDLER + CORE --> SHA ``` -Within the handler crate, the module organization is as follows: +### Internal module organization + +The crate is separated into seven modules, each with a single responsibility. The crate +root (`lib.rs`) re-exports the most commonly used items for ergonomic access. + +```mermaid +flowchart LR + LIB["lib.rs
crate root + root re-exports"] + LIB --> CONST["constants
limits + entry_mode"] + LIB --> ENUMS["enums
EntryKind"] + LIB --> ERRORS["errors
VctrlError"] + LIB --> MACROS["macros
vctrl_error_other!"] + LIB --> TRAITS["traits/core
17 contracts"] + LIB --> TYPES["types/core
14 immutable data types"] + LIB --> VAL["validation
4 input checks"] + + VAL -.gates construction.-> TYPES + TRAITS -.consume / return.-> TYPES + TYPES -.return.-> ERRORS + TRAITS -.return.-> ERRORS + ENUMS -.used by.-> TYPES + CONST -.used by.-> TYPES + CONST -.used by.-> VAL +``` + +### Invalid states are unrepresentable + +A core design idiom is that all public data types are constructed via **fallible +constructors** that enforce invariants at construction time. Once built, objects are +immutable and expose only `&self` accessors. This pushes validation to the boundary of the +system and guarantees that invalid objects cannot exist at runtime. ```mermaid -graph TD - ROOT[lib.rs] - ROOT --> CONST[constants] - ROOT --> ENUMS[enums] - ROOT --> ERR[errors] - ROOT --> MAC[macros] - ROOT --> TRAITS[traits] - ROOT --> TYPES[types] - ROOT --> VAL[validation] - - ENUMS --> EK[enums::core::entry_kind] - TRAITS --> TC[traits::core] - TC --> BLAME[blame] - TC --> CFG[config] - TC --> DEC[decoder] - TC --> DIFF[diff] - TC --> ENC[encoder] - TC --> HASHER[hasher] - TC --> IDX[index] - TC --> OS[object_store] - TC --> PACK[pack] - TC --> RF[ref_store] - TC --> RL[reflog] - TC --> REM[remote] - TC --> RW[revwalk] - TC --> SIGN[signer] - TC --> TRAN[transport] - TC --> VER[verifier] - - TYPES --> TYC[types::core] - TYC --> BLOB[blob] - TYC --> COMMIT[commit] - TYC --> DELTA[delta] - TYC --> HASH[hash] - TYC --> MERGE[merge] - TYC --> REFLOG[reflog] - TYC --> TAG[tag] - TYC --> TREE[tree] - TYC --> UID[user_id] +flowchart LR + INPUT[Raw input
name / hash bytes / ref name / tree entry] + INPUT --> VALFN[validation function
validate_name / validate_hash_bytes / ...] + VALFN -->|invalid| ERR1[VctrlError
InvalidName / InvalidHashLength / ...] + VALFN -->|valid| CTOR[Fallible constructor
Blob::new / Tree::new / UserID::new / ...] + CTOR -->|invariant violated| ERR2[VctrlError] + CTOR -->|ok| OBJ[Immutable validated object
only &self accessors] + OBJ -.Send + Sync.-> THREADS[Cross-thread consumers] ``` --- ## Core Features -- **Trait-only contracts** — Every VCS subsystem is expressed as a `trait` with clear method signatures, error returns, and `Send + Sync` bounds. Implementations are decoupled from consumers. -- **Git-compatible data types** — `Blob`, `Tree`, `TreeEntry`, `Commit`, `Tag`, `UserID`, and their supporting types mirror the Git object model and enforce Git-like invariants. -- **Fixed-size hash** — `Hash` is a newtype over `[u8; 64]` (SHA-512 length), with hexadecimal parsing, display, and ordering. -- **Validation as a security boundary** — Dedicated functions validate names, references, tree entry names, and hash lengths. Implementation crates must call these functions, never duplicate the logic. -- **Comprehensive error hierarchy** — `VctrlError` covers invalid lengths, missing objects, corrupted data, I/O failures, serialization issues, tree structure errors, duplicate parents, size limit breaches, and invalid blame ranges. -- **Strict compile-time guarantees** — The crate uses `#![forbid(unsafe_code)]`, `#![deny(missing_docs)]`, and a suite of Clippy lints to ensure safe, well-documented, idiomatic code. +- **Pure contracts.** No implementations — only traits, types, constants, and + validation. Backend authors implement the traits; tool authors depend on the types. +- **Invalid states unrepresentable.** All data types use fallible constructors that reject + malformed input at construction time; objects are immutable thereafter. +- **Resource-exhaustion prevention.** Hard `MAX_*` limits act as fail-fast circuit + breakers during construction, bounding memory allocation against malicious input. +- **Strong typing over raw integers.** Git mode bits are represented by the `EntryKind` + enum with `const fn` conversions to and from raw `u32` modes, preventing invalid + combinations at compile time. +- **Forward-compatible enums and errors.** `EntryKind` and `VctrlError` are + `#[non_exhaustive]`, allowing new variants without breaking API compatibility. +- **Thread-safe contracts.** Traits carry `Send + Sync` bounds so backends can be shared + across threads; `&mut self` is required only for write operations. +- **Cloneable I/O errors.** `std::io::Error` is wrapped in `Arc` inside `VctrlError` so + the error remains `Clone` despite `io::Error` not being `Clone`. +- **Compile-time computation.** `const fn` is used where possible (`EntryKind::mode`, + `from_mode`) to shift work to compile time. +- **Strict safety.** `#![forbid(unsafe_code)]` and a comprehensive set of denied Clippy + and documentation lints are inherited from the workspace. --- ## Technology Stack -- **Language:** Rust (edition 2024) -- **Standard library only:** No external dependencies. The handler crate uses only `std` types (`std::io`, `std::collections::HashSet`, `std::path`, etc.). -- **Frameworks/Libraries:** None. This crate is a pure contract definition. -- **Toolchain:** Requires Rust 1.85 or newer (for edition 2024 support). +- **Language:** Rust (edition 2024, MSRV 1.96.0) +- **Dependencies:** none (standard library only) +- **Dev-dependencies:** `proptest` 1.11.0 +- **Lint policy:** workspace-inherited, `#![forbid(unsafe_code)]`, denied `missing_docs`, + `rust_2018_idioms`, and a broad set of Clippy lints (including `pedantic` and `nursery` + groups). See the repository for the authoritative lint configuration. +- **Feature flags:** none. + +> Note on the MSRV field: this crate's `Cargo.toml` does not yet declare +> `rust-version = "1.96"`. The workspace standard is Rust 1.96.0, and this README +> documents that as the MSRV. Adding `rust-version = "1.96"` to +> `libvctrl_handler/Cargo.toml` is recommended in a future maintenance pass. --- ## Project Structure -The source tree of `libvctrl_handler` is organized as follows: - ```text libvctrl_handler/ ├── Cargo.toml └── src/ ├── lib.rs ├── constants.rs + ├── errors.rs + ├── macros.rs ├── enums/ │ ├── mod.rs │ └── core/ │ ├── mod.rs │ └── entry_kind.rs - ├── errors.rs - ├── macros.rs ├── traits/ │ ├── mod.rs │ └── core/ @@ -170,16 +200,7 @@ libvctrl_handler/ ├── types/ │ ├── mod.rs │ └── core/ - │ ├── mod.rs - │ ├── blob.rs - │ ├── commit.rs - │ ├── delta.rs - │ ├── hash.rs - │ ├── merge.rs - │ ├── reflog.rs - │ ├── tag.rs - │ ├── tree.rs - │ └── user_id.rs + │ └── ... (data type definitions) └── validation/ ├── mod.rs ├── hash.rs @@ -192,571 +213,360 @@ libvctrl_handler/ ### Prerequisites -- **Rust 1.85 or newer** — required for the 2024 edition. -- **Cargo** — the Rust package manager. -- No system-level dependencies are required; the handler crate is pure Rust. +- Rust toolchain **1.96.0** or newer (edition 2024 is required) +- Cargo + +No system libraries or external services are required. ### Installation -For most integrators, the recommended entry point is the `libvctrl` facade crate, which re-exports both the handler contracts and the reference implementation: +For most users, depend on the facade instead: ```toml [dependencies] -libvctrl = "4.4" +libvctrl = "2.1" ``` -If you are developing an implementation crate and need to depend directly on the contract layer: +To depend on `libvctrl_handler` directly (contracts only, no reference implementation): ```toml [dependencies] -libvctrl_handler = "4.4" +libvctrl_handler = "5.0" ``` -To use the handler from a local checkout or a Git repository: +Or via Cargo: -```toml -[dependencies] -libvctrl_handler = { git = "https://github.com/mroczect/libvctrl", branch = "master" } +```bash +cargo add libvctrl_handler ``` -### Configuration +Because the crate has no external dependencies, adding it is essentially zero-weight +beyond the standard library. -The handler crate itself requires no configuration or environment variables. All size and count limits are defined as constants in `libvctrl_handler::constants` and are enforced by the validation functions and type constructors. +### Configuration -However, the `ConfigStore` trait defines an abstraction for reading and writing configuration values. The reference implementation and higher-level crates may use this trait to handle user configuration. +`libvctrl_handler` exposes **no crate-level feature flags** and requires **no runtime +configuration**. The contract surface is fixed; behavioural configuration (such as the +`sha384` / `opt_size` crypto features) lives at the facade or `libvctrl_sha512` level. --- ## Usage -The handler crate is not meant to be used directly by end users; it is consumed by implementors and integrators. The following examples illustrate how the contracts are used. - -### Implementing a trait - -Implementations must satisfy the trait's method signatures and return `VctrlError` on failure. The following example shows a minimal in-memory object store: +### Create a Hash and inspect an EntryKind ```rust -use libvctrl_handler::{ObjectStore, Hash, VctrlError}; -use std::collections::HashMap; -use std::io::{self, Read}; +use libvctrl_handler::{EntryKind, Hash}; -pub struct MemoryStore { - objects: HashMap>, -} +// Hash requires exactly 64 bytes (SHA-512). +let raw_bytes = [0_u8; 64]; +let hash = Hash::from_bytes(&raw_bytes); +assert!(hash.is_ok()); -impl MemoryStore { - pub fn new() -> Self { - Self { objects: HashMap::new() } - } -} - -impl ObjectStore for MemoryStore { - fn put(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError> { - self.objects.insert(*hash, data.to_vec()); - Ok(()) - } - - fn get(&self, hash: &Hash) -> Result, VctrlError> { - match self.objects.get(hash) { - Some(data) => Ok(Box::new(io::Cursor::new(data.clone()))), - None => Err(VctrlError::ObjectNotFound(*hash)), - } - } - - fn delete(&mut self, hash: &Hash) -> Result<(), VctrlError> { - self.objects.remove(hash).map(|_| ()).ok_or(VctrlError::ObjectNotFound(*hash)) - } - - fn exists(&self, hash: &Hash) -> Result { - Ok(self.objects.contains_key(hash)) - } -} -``` - -### Constructing a validated data type - -Data types enforce validation in their constructors. For example, creating a `Commit` with metadata: - -```rust -use libvctrl_handler::{Commit, CommitMeta, UserID, Hash}; -use libvctrl_handler::VctrlError; - -fn create_commit(tree_hash: Hash, parent: Hash, author: UserID, committer: UserID) -> Result { - let meta = CommitMeta::new( - 1_700_000_000, // timestamp - 0, // timezone offset (UTC) - Some("UTF-8".into()), - )?; - - Commit::with_meta( - tree_hash, - vec![parent], - author, - committer, - "Initial commit".to_string(), - meta, - ) -} +// Git object modes are accessible via the strongly-typed EntryKind enum. +let blob_mode = EntryKind::Blob.mode(); +assert_eq!(blob_mode, 0o100_644); ``` -### Encoder/Decoder roundtrip - -The `Encoder` and `Decoder` traits define how objects are serialized and deserialized. Implementations must adhere to the binary format contract: +### Convert between EntryKind and raw mode bits ```rust -use libvctrl_handler::{Encoder, Decoder, Commit, VctrlError}; -use std::io::{Cursor, Read, Write}; - -fn roundtrip_commit( - decoder: &D, - encoder: &E, - commit: &Commit, -) -> Result { - let mut buffer = Vec::new(); - encoder.encode_commit(commit, &mut buffer)?; - - let mut reader = Cursor::new(buffer); - decoder.decode_commit(reader) -} -``` - ---- - -## API Reference / Core Modules - -This section documents the public API of the handler crate. - -### `constants` - -Defines size and count limits and Git entry mode bits. - -| Constant | Value | Description | -| -------------------- | ------------------- | --------------------------------------------------- | -| `HASH_LENGTH` | `64` | Length of a hash in bytes (SHA-512). | -| `MAX_NAME_LENGTH` | `255` | Maximum length for names, in bytes. | -| `MAX_BLOB_SIZE` | `100 * 1024 * 1024` | Maximum blob size in bytes (100 MiB). | -| `MAX_TREE_ENTRIES` | `100_000` | Maximum number of entries in a tree. | -| `MAX_MESSAGE_LENGTH` | `1024 * 1024` | Maximum commit/tag message length in bytes (1 MiB). | -| `MAX_PARENT_COUNT` | `65535` | Maximum number of parent commits. | - -`constants::entry_mode` provides Git mode bits: - -| Mode | Value (octal) | Description | -| ------------ | ------------- | ----------------- | -| `BLOB` | `0o100_644` | Regular file. | -| `EXECUTABLE` | `0o100_755` | Executable file. | -| `SYMLINK` | `0o120_000` | Symbolic link. | -| `TREE` | `0o40_000` | Directory (tree). | -| `SUBMODULE` | `0o160_000` | Submodule commit. | - -### `enums::EntryKind` - -Represents the kind of an entry in a Git tree. - -| Variant | Mode | Description | -| ------------ | ------------ | ----------------- | -| `Blob` | `BLOB` | Regular file. | -| `Executable` | `EXECUTABLE` | Executable file. | -| `Symlink` | `SYMLINK` | Symbolic link. | -| `Tree` | `TREE` | Directory. | -| `Submodule` | `SUBMODULE` | Submodule commit. | - -Methods: `mode()` returns the mode bits; `from_mode(mode: u32) -> Option` converts raw mode bits. - -### `errors::VctrlError` - -The unified error type for all operations. - -| Variant | Description | -| ------------------------------ | --------------------------------------------------------------- | -| `InvalidHashLength(usize)` | Hash length did not match expected 64 bytes. | -| `InvalidName(String)` | Name invalid (empty, too long, or contains control characters). | -| `InvalidEmail(String)` | Email invalid. | -| `ObjectNotFound(Hash)` | Object not found. | -| `RefNotFound(String)` | Reference not found. | -| `CorruptedData(String)` | Data corrupted or malformed. | -| `IoError(Arc)` | I/O error. | -| `SerializationError(String)` | Serialization/deserialization error. | -| `Other(String)` | Any other error. | -| `InvalidTreeStructure(String)` | Tree structure invalid (unsorted entries, duplicates). | -| `InvalidTimezoneOffset(i16)` | Timezone offset out of range (-1440 to 1440). | -| `DuplicateParent` | Commit contains duplicate parent hashes. | -| `ExceededMaxSize(String)` | Size or count limit exceeded. | -| `InvalidBlameRange` | Invalid blame range (zero line count). | - -`VctrlError` implements `Display`, `Error`, `PartialEq`, and `Eq`. It also provides `from_io(e: std::io::Error) -> Self` for canonical I/O error conversion. - -### `macros` - -- **`vctrl_error_other!`** — Constructs a `VctrlError::Other` from a format string and arguments. - -```rust -let err = vctrl_error_other!("Failed to parse {}", "object"); -``` - -### `traits::core` - -The following traits are defined. All are `Send + Sync`. - -#### `Blame` - -Computes blame information for files. - -```rust -fn blame_file(&self, path: &str) -> Result, VctrlError>; -``` - -`BlameEntry` represents a line range attributed to a commit. - -#### `ConfigStore` - -Reads and writes configuration values. - -```rust -fn get_string(&self, section: &str, key: &str) -> Result, VctrlError>; -fn set_string(&mut self, section: &str, key: &str, value: &str) -> Result<(), VctrlError>; -fn get_bool(&self, section: &str, key: &str) -> Result, VctrlError>; -fn set_bool(&mut self, section: &str, key: &str, value: bool) -> Result<(), VctrlError>; -fn remove(&mut self, section: &str, key: &str) -> Result<(), VctrlError>; -fn exists(&self, section: &str, key: &str) -> Result; -``` - -#### `Decoder` - -Decodes raw Git object bytes into structured types. - -```rust -fn decode_blob(&self, reader: R) -> Result; -fn decode_tree(&self, reader: R) -> Result; -fn decode_commit(&self, reader: R) -> Result; -fn decode_tag(&self, reader: R) -> Result; -``` - -#### `TreeDiffer` - -Computes differences between two trees. - -```rust -type TreeId: Send + Sync; -fn diff_trees(&self, old: &Self::TreeId, new: &Self::TreeId) -> Result; -``` - -#### `Encoder` +use libvctrl_handler::enums::EntryKind; -Encodes structured Git objects into raw bytes. +// Enum -> raw mode (const fn, usable in const contexts). +assert_eq!(EntryKind::Executable.mode(), 0o100_755); -```rust -fn encode_blob(&self, blob: &Blob, writer: &mut W) -> Result<(), VctrlError>; -fn encode_tree(&self, tree: &Tree, writer: &mut W) -> Result<(), VctrlError>; -fn encode_commit(&self, commit: &Commit, writer: &mut W) -> Result<(), VctrlError>; -fn encode_tag(&self, tag: &Tag, writer: &mut W) -> Result<(), VctrlError>; +// Raw mode -> Enum (graceful on invalid input). +assert_eq!(EntryKind::from_mode(0o120_000), Some(EntryKind::Symlink)); +assert_eq!(EntryKind::from_mode(0o000_000), None); ``` -#### `Hasher` - -Computes hash values. +### Construct ad-hoc errors with the macro ```rust -fn hash(&self, reader: R) -> Result; -``` - -#### `Index` +use libvctrl_handler::{VctrlError, vctrl_error_other}; -Manages a Git index (staging area). - -```rust -type Entry: Send + Sync; -type Path: Send + Sync; -type TreeId: Send + Sync; - -fn add(&mut self, entry: Self::Entry) -> Result<(), VctrlError>; -fn remove(&mut self, path: &Self::Path) -> Result<(), VctrlError>; -fn clear(&mut self) -> Result<(), VctrlError>; -fn get(&self, path: &Self::Path) -> Result, VctrlError>; -fn contains(&self, path: &Self::Path) -> Result; -fn len(&self) -> Result; -fn is_empty(&self) -> Result; -fn entries(&self) -> Result, VctrlError>; -fn write_tree(&self) -> Result; -fn read_tree(&mut self, tree: &Self::TreeId) -> Result<(), VctrlError>; +let err = vctrl_error_other!("missing configuration file: {} (code {})", "config.toml", 404); +assert_eq!( + err.to_string(), + "missing configuration file: config.toml (code 404)" +); ``` -#### `ObjectStore` - -Stores and retrieves Git objects. +### Wrap an I/O error ```rust -fn put(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError>; -fn get(&self, hash: &Hash) -> Result, VctrlError>; -fn delete(&mut self, hash: &Hash) -> Result<(), VctrlError>; -fn exists(&self, hash: &Hash) -> Result; -``` - -#### `PackWriter` / `PackReader` +use libvctrl_handler::VctrlError; +use std::io::{self, ErrorKind}; -Write and read Git pack files. +let io_err = io::Error::new(ErrorKind::NotFound, "file missing"); +let vctrl_err = VctrlError::from_io(io_err); -```rust -// PackWriter -type ObjectId: Send + Sync; -fn write_object(&mut self, id: &Self::ObjectId, data: &[u8]) -> Result<(), VctrlError>; -fn finish(&mut self) -> Result<(), VctrlError>; - -// PackReader -type ObjectId: Send + Sync; -fn read_object(&self, id: &Self::ObjectId) -> Result, VctrlError>; +// The error is cloneable despite wrapping std::io::Error. +let cloned = vctrl_err.clone(); +assert_eq!(vctrl_err, cloned); ``` -#### `RefStore` +### Implement a trait for a custom backend -Manages Git references (branches, tags, etc.). +The traits are designed to be implemented by backend authors. The `Blame` trait +illustrates the idiom: `Send + Sync` bounds for thread safety, `&self` for read +operations, and `VctrlError` as the unified failure type. ```rust -type RefsIterator: Iterator> + Send; - -fn set_ref(&mut self, name: &str, hash: &Hash) -> Result<(), VctrlError>; -fn get_ref(&self, name: &str) -> Result; -fn delete_ref(&mut self, name: &str) -> Result<(), VctrlError>; -fn list_refs(&self) -> Result; -``` +use libvctrl_handler::traits::core::blame::{Blame, BlameEntry}; +use libvctrl_handler::{Hash, VctrlError}; -#### `ReflogStore` +struct MockRepo; -Manages reflogs. +impl Blame for MockRepo { + fn blame_file(&self, path: &str) -> Result, VctrlError> { + let hash = Hash::from_bytes(&[0_u8; 64])?; + let entry = BlameEntry::new(hash, 1, 10, path.to_string(), None)?; + Ok(vec![entry]) + } +} -```rust -type RefName: Send + Sync; - -fn append( - &mut self, - reference: &Self::RefName, - old_hash: Option, - new_hash: Option, - reason: &str, - timestamp: i64, - timezone_offset: i16, -) -> Result<(), VctrlError>; - -fn entries(&self, reference: &Self::RefName) -> Result, VctrlError>; +let repo = MockRepo; +let entries = repo.blame_file("src/main.rs").unwrap(); +assert_eq!(entries.len(), 1); ``` -#### `Remote` - -Interacts with remote repositories. +--- -```rust -type RefSpec: Send + Sync; -type RemoteRef: Send + Sync; +## API Reference / Core Modules -fn list_refs(&self) -> Result, VctrlError>; -fn fetch(&mut self, refspecs: &[Self::RefSpec]) -> Result<(), VctrlError>; -fn push(&mut self, refspecs: &[Self::RefSpec]) -> Result<(), VctrlError>; -``` +Full API documentation is published at . The summary +below describes each module and its public surface. + +### `constants` — Limits and protocol values + +Centralises all magic numbers so that limits are uniformly enforced at the type +construction level. Two categories: resource-exhaustion circuit breakers and Git protocol +constants. + +| Constant | Value | Purpose | +| -------------------- | -------------- | -------------------------------------------------------- | +| `HASH_LENGTH` | `64` | SHA-512 hash length in bytes; enables fixed-size arrays. | +| `MAX_NAME_LENGTH` | `255` | Upper bound on file/directory/reference names (bytes). | +| `MAX_BLOB_SIZE` | `100 * 1024^2` | Maximum blob size (100 MiB); DoS circuit breaker. | +| `MAX_TREE_ENTRIES` | `100_000` | Maximum entries per tree; bounds parse/diff cost. | +| `MAX_MESSAGE_LENGTH` | `1024 * 1024` | Maximum commit/tag message length (1 MiB). | +| `MAX_PARENT_COUNT` | `0xFFFF` | Maximum parent commits (u16 range). | + +The `entry_mode` submodule exposes the Git protocol mode bits: + +| Constant | Value | Meaning | +| ------------ | ----------- | ---------------- | +| `BLOB` | `0o100_644` | Regular file | +| `EXECUTABLE` | `0o100_755` | Executable file | +| `SYMLINK` | `0o120_000` | Symbolic link | +| `TREE` | `0o40_000` | Directory (tree) | +| `SUBMODULE` | `0o160_000` | Submodule commit | -#### `RevWalk` +### `enums` — Strongly-typed protocol kinds -Walks commit history. +- **`EntryKind`** — a `#[non_exhaustive]` enum (`Blob`, `Executable`, `Symlink`, `Tree`, + `Submodule`) classifying tree entries. Provides `const fn mode() -> u32` and + `const fn from_mode(u32) -> Option` for lossless conversion to and from raw Git + mode bits. Consumers must include a `_` catch-all when matching. + +### `errors` — Unified error type + +- **`VctrlError`** — a `#[non_exhaustive]`, `Clone + Debug + PartialEq + Eq` enum that all + fallible operations across the ecosystem return. Implements `Display` and `std::error::Error`. + I/O errors are stored as `Arc` to make the error `Clone`. A manual + `PartialEq` compares `io::Error::kind()` and string representation. + +| Variant | Meaning | +| ------------------------------ | ------------------------------------------------------------ | +| `CorruptedData(String)` | Data was corrupted or malformed. | +| `DuplicateParent` | A commit contains duplicate parent hashes. | +| `ExceededMaxSize(String)` | A size or count limit was exceeded. | +| `InvalidBlameRange` | An invalid blame range was specified (zero start or count). | +| `InvalidEmail(String)` | An email address was invalid. | +| `InvalidHashLength(usize)` | Hash length did not match `HASH_LENGTH`. | +| `InvalidName(String)` | A name was empty, too long, or contained control characters. | +| `InvalidTimezoneOffset(i16)` | Timezone offset outside `-1440..=1440`. | +| `InvalidTreeStructure(String)` | Tree entries unsorted or duplicated. | +| `IoError(Arc)` | An I/O error occurred. | +| `ObjectNotFound(Hash)` | An object with the given hash was not found. | +| `Other(String)` | Any error not covered by the above. | +| `RefNotFound(String)` | A reference with the given name was not found. | +| `SerializationError(String)` | A serialization/deserialization error occurred. | + +`VctrlError::from_io(io::Error)` is the canonical constructor for I/O failures. + +### `macros` — Error-construction helpers + +- **`vctrl_error_other!`** — an exported declarative macro that wraps `format!` into + `VctrlError::Other`. Uses `$crate` for absolute path resolution so the macro remains + correct even when imported via glob from another crate. + +### `traits` — The 17 backend contracts + +All traits live under `traits::core` and are re-exported at the crate root. They are +grouped by domain: + +**Serialization and content addressing** + +| Trait | Purpose | +| --------- | ------------------------------------------------------------- | +| `Encoder` | Serialise objects (`Blob`, `Tree`, `Commit`, `Tag`) to bytes. | +| `Decoder` | Parse bytes back into validated objects; the trust boundary. | +| `Hasher` | Compute a content-address `Hash` from a byte stream. | + +**Object and reference storage** + +| Trait | Purpose | +| ------------- | ------------------------------------------------------------ | +| `ObjectStore` | Store and retrieve encoded object bytes keyed by `Hash`. | +| `RefStore` | Manage named references (branches, tags) pointing at hashes. | +| `ReflogStore` | Append and read reflog entries for a reference. | +| `Index` | Manage the staged index of path-to-hash mappings. | + +**History traversal and analysis** + +| Trait | Purpose | +| ------------ | ------------------------------------------------------------------- | +| `RevWalk` | Traverse the commit graph from a set of heads. | +| `Blame` | Attribute file line ranges to commits (`BlameEntry`). | +| `TreeDiffer` | Compute tree-level deltas (`TreeDelta`, `FileDelta`, `ChangeKind`). | + +**Remote and packfile** + +| Trait | Purpose | +| ------------ | ----------------------------------------------------- | +| `Transport` | Abstract network transport for remote operations. | +| `Remote` | Manage remote configuration and endpoint interaction. | +| `PackReader` | Decode Git packfile streams into objects. | +| `PackWriter` | Encode objects into Git packfile streams. | + +**Cryptographic integrity** + +| Trait | Purpose | +| ---------- | ---------------------------------------------- | +| `Signer` | Produce cryptographic signatures over objects. | +| `Verifier` | Verify cryptographic signatures over objects. | + +**Configuration** + +| Trait | Purpose | +| ------------- | ----------------------------------------------------------------------------------------------------------------- | +| `ConfigStore` | Read and write sectioned configuration values (`Option`-returning reads for sparse keys; `&mut self` for writes). | + +All traits require `Send + Sync`. Write operations take `&mut self`; read operations take +`&self`. `BlameEntry` is the companion struct for `Blame`, constructed via a fallible +`new()` that rejects zero start lines or counts. + +### `types` — The 14 immutable data types + +All types live under `types/core` and are re-exported at the crate root. Each is immutable +after construction and built through a fallible constructor. + +| Type | Purpose | +| ------------- | -------------------------------------------------------------------------- | +| `Blob` | A file's raw content, bounded by `MAX_BLOB_SIZE`. | +| `Tree` | A sorted collection of `TreeEntry` objects, bounded by `MAX_TREE_ENTRIES`. | +| `TreeEntry` | A single directory entry: name, `EntryKind`, and target `Hash`. | +| `Commit` | A commit object: tree hash, parents, author/committer, message, metadata. | +| `CommitMeta` | Timestamp, timezone offset, and optional encoding for a commit. | +| `Tag` | An annotated tag: name, target hash, optional tagger, message, metadata. | +| `Hash` | A fixed 64-byte SHA-512 content address (`Copy`, `Send`, `Sync`). | +| `UserID` | A name and email pair with validation. | +| `ReflogEntry` | A single reflog record (old/new hash, committer, message). | +| `ChangeKind` | The kind of change in a diff (added, modified, deleted, etc.). | +| `FileDelta` | A per-file change between two trees. | +| `TreeDelta` | An aggregate tree-level diff. | +| `Conflict` | A merge conflict representation. | +| `MergeResult` | The outcome of a merge operation. | + +### `validation` — Input checks + +Pure functions that define the security boundary for raw inputs, intended to be applied +_before_ attempting object construction so that malformed data fails fast. + +| Function | Validates | +| -------------------------- | ----------------------------------------------------------------------------------- | +| `validate_hash_bytes` | That a byte slice is exactly `HASH_LENGTH` bytes. | +| `validate_name` | That a name is non-empty, within `MAX_NAME_LENGTH`, and free of control characters. | +| `validate_ref_name` | That a reference name is structurally valid (e.g. `refs/heads/main`). | +| `validate_tree_entry_name` | That a tree entry name is a valid path component. | -```rust -type CommitId: Send + Sync; -fn walk(&self, start: &Self::CommitId) -> Result, VctrlError>; -``` +--- -#### `Signer` +## Testing -Signs data. +Run the crate's test suite with Cargo: -```rust -fn sign(&mut self, key_id: &str, data: &[u8]) -> Result, VctrlError>; +```bash +cargo test ``` -#### `Transport` - -Transports Git objects. +Property-based tests use `proptest` (a dev-dependency). Because the crate defines only +contracts and immutable types, its tests focus on construction invariants, validation +rejection of malformed input, and round-trip properties of `EntryKind` mode conversion. +To run the entire workspace test suite from the repository root: -```rust -fn fetch_object(&self, hash: &Hash) -> Result, VctrlError>; -fn push_object(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError>; +```bash +cargo test --workspace ``` -#### `Verifier` - -Verifies signatures. +To verify the strict lint policy is satisfied: -```rust -fn verify(&self, key_id: &str, data: &[u8], signature: &[u8]) -> Result; +```bash +cargo clippy --workspace --all-targets -- -D warnings +cargo doc --workspace --no-deps ``` -### `types::core` - -The following data types are defined: - -#### `Hash` - -Fixed-size hash of 64 bytes (SHA-512 length). - -- `from_bytes(bytes: &[u8]) -> Result` — validates length. -- `as_bytes() -> &[u8; 64]` — raw bytes. -- Implements `From<[u8; 64]>`, `TryFrom<&[u8]>`, `AsRef<[u8]>`, `FromStr` (hex string), `Display` (hex), `Debug` (truncated), `PartialOrd`, `Ord`. - -#### `Blob` - -Represents a Git blob (file content). - -- `new(data: Vec) -> Result` — enforces `MAX_BLOB_SIZE`. -- `data() -> &[u8]`, `size() -> usize`, `is_empty() -> bool`. - -#### `TreeEntry` - -Represents a single entry in a tree. - -- `new(name: String, kind: EntryKind, hash: Hash) -> Result` — validates tree entry name. -- Accessors: `name()`, `kind()`, `hash()`. - -#### `Tree` - -Represents a Git tree (directory listing). Entries are always sorted according to Git ordering rules (tree entries are compared as if their name has a trailing `/`). Duplicate names are rejected. - -- `new(entries: Vec) -> Result` — sorts and validates. -- Accessors: `entries() -> &[TreeEntry]`, `len()`, `is_empty()`, `get(name: &str) -> Option<&TreeEntry>`. - -#### `UserID` - -Represents a user identity (author or committer). - -- `new(name: String, email: String) -> Result` — validates name and email. -- Accessors: `name()`, `email()`. - -#### `CommitMeta` - -Metadata associated with a commit or tag. - -- `new(timestamp: i64, timezone_offset: i16, encoding: Option) -> Result` — validates timezone offset. -- Accessors: `timestamp()`, `timezone_offset()`, `encoding()`. - -#### `Commit` - -Represents a Git commit object. - -- `new(tree, parents, author, committer, message)` or `with_meta(...)` — validates parent count, message length, and duplicate parents. -- Accessors: `tree()`, `parents()`, `author()`, `committer()`, `message()`, `meta()`. - -#### `Tag` - -Represents a Git tag object. - -- `new(name, target, tagger, message)` or `with_meta(...)` — validates reference name and message length. -- Accessors: `name()`, `target()`, `tagger()`, `message()`, `meta()`. - -#### `ChangeKind` - -Enum describing the kind of change: `Added`, `Deleted`, `Modified`, `TypeChange`, `Renamed`, `Copied`. - -#### `FileDelta` - -Represents a single file delta between two trees. Provides constructor methods: `added`, `deleted`, `modified`, `type_change`, `renamed`, `copied`. Accessors for path, old path, old hash, new hash, kind, and convenience `is_*` methods. - -#### `TreeDelta` - -A collection of `FileDelta`. Supports `len`, `is_empty`, `iter`, `changes`, `IntoIterator`. - -#### `Conflict` - -Represents a merge conflict: path, ancestor blob hash, our blob hash, their blob hash. - -#### `MergeResult` - -Enum: `Success(Hash)` or `Conflicts(Vec)`. Provides `is_success`, `is_conflicts`, `conflicts`. - -#### `ReflogEntry` - -A single reflog entry: old id, new id, reason, timestamp, timezone offset. Constructor validates timezone offset. - -### `validation` - -Validation functions that define the security boundary. - -- `validate_hash_bytes(bytes: &[u8]) -> Result<(), VctrlError>` — ensures exactly 64 bytes. -- `validate_name(name: &str) -> Result<(), VctrlError>` — basic name validation. -- `validate_ref_name(name: &str) -> Result<(), VctrlError>` — strict Git reference name validation. -- `validate_tree_entry_name(name: &str) -> Result<(), VctrlError>` — strict tree entry name validation. - --- -## Validation Contract - -Validation is the single source of truth for security-critical rules. It is encapsulated in `libvctrl_handler::validation` and must be used by all implementation crates. Duplicating validation logic in `libvctrl_core` or elsewhere is forbidden. - -### Reference Names (`validate_ref_name`) - -Enforces Git-strict rules with additional security hardening. - -**Forbidden substrings:** - -- `..` (path traversal) -- `~`, `^`, `:`, `?`, `*`, `[`, `\`, space, `@{`, `//` -- `<`, `>`, `|`, `"` - -**Forbidden patterns:** - -- Leading `.` (hidden paths) -- Leading `/` (absolute paths) -- Trailing `/` -- Trailing `.` -- Extension `.lock` (case-insensitive) - -**Additional constraints:** - -- Length must be between 1 and 255 bytes. -- No ASCII control characters. - -### Tree Entry Names (`validate_tree_entry_name`) - -Stricter than reference names because tree entries map directly to filesystem entries. - -**Forbidden:** - -- `/` and `\` (path separators) -- Exact names `.` and `..` -- Length 0 or > 255 bytes -- ASCII control characters - -### Hash Length (`validate_hash_bytes`) +## Contributing -- Exactly 64 bytes, corresponding to SHA-512 output length. +Contributions are welcome. The crate enforces `#![forbid(unsafe_code)]` and inherits a +strict, denied Clippy and documentation-lint policy from the workspace; all public items +must be documented. -### Architectural Rule +For contribution guidelines, code style, and the full lint configuration, see the +repository's `CONTRIBUTING.md` and the workspace root `README.md`: -> `libvctrl_core` and all implementation crates MUST call -> `libvctrl_handler::validate_*` — never duplicate validation logic. +- Repository: https://github.com/mroczect/libvctrl -This rule prevents divergent validation implementations, which could lead to security vulnerabilities or interoperability issues. All type constructors in `libvctrl_handler::types` already call the appropriate validation functions, so any object created through the public API is guaranteed to be valid. +When contributing to `libvctrl_handler`, preserve the contracts-only invariant: no +concrete implementations belong here. New behaviours belong in `libvctrl_core`; new +contracts and types belong here; new user-facing commands belong in `libvctrl_plumbing` +or `libvctrl_porcelain`. --- -## Testing +## Ecosystem -The handler crate includes a small set of unit tests, primarily for tree ordering and duplicate rejection, located in `src/types/core/tree.rs`. +`libvctrl_handler` is the foundation of a larger workspace. The related crates are listed +below; each has its own documentation. -To run the tests: +| Crate | Role | Documentation | +| -------------------- | -------------------------------------------------------- | ---------------------------------- | +| `libvctrl` | Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl | +| `libvctrl_core` | Reference implementations of these contracts | https://docs.rs/libvctrl_core | +| `libvctrl_sha512` | Zero-dependency SHA-512 / HMAC / HKDF primitives | https://docs.rs/libvctrl_sha512 | +| `libvctrl_plumbing` | Command-level VCS operations built on `libvctrl_core` | https://docs.rs/libvctrl_plumbing | +| `libvctrl_porcelain` | High-level, user-facing VCS operations | https://docs.rs/libvctrl_porcelain | -```bash -cargo test -``` +The dependency flow is strictly one-way: `libvctrl_handler` is the foundation, +`libvctrl_core` implements its contracts, the facade re-exports both, and +`libvctrl_plumbing` / `libvctrl_porcelain` build on `libvctrl_core`. -When developing an implementation crate, you should write additional tests that exercise your concrete implementations against the trait contracts. The handler's validation functions can be used as property-based test oracles. +```mermaid +flowchart LR + H[libvctrl_handler
contracts] --> C[libvctrl_core
reference impl] + C --> PL[libvctrl_plumbing] + C --> PO[libvctrl_porcelain] + H --> F[libvctrl
facade] + C --> F +``` --- -## Contributing - -Contributions to `libvctrl` are welcome. Before submitting a pull request, please ensure: - -1. **Contract stability** — Any change to traits or types must be backward-compatible or clearly justified. The handler crate is a foundation; breaking changes propagate to all downstream crates. -2. **Validation rules** — If you modify validation functions, update the Validation Contract section of this README and the test suite accordingly. -3. **No unsafe code** — The crate uses `#![forbid(unsafe_code)]`. Do not introduce unsafe blocks. -4. **Documentation** — All public items must have doc comments due to `#![deny(missing_docs)]`. Ensure new code is documented at the same standard. -5. **Lint compliance** — Run `cargo clippy` with the project's lint configuration and fix all warnings. -6. **Tests** — Add unit tests for new logic and run `cargo test` to verify no regressions. +## License -For significant architectural changes, open an issue first to discuss the design with the maintainers. +Licensed under the MIT License. See the repository for the full license text. diff --git a/libvctrl_sha512/Cargo.toml b/libvctrl_sha512/Cargo.toml index 4ceb7f87..301ba8ea 100644 --- a/libvctrl_sha512/Cargo.toml +++ b/libvctrl_sha512/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libvctrl_sha512" -version = "3.0.0" +version = "3.0.1" edition = "2024" rust-version = "1.96" description = "Zero-dependency SHA512, HMAC-SHA512, HKDF-SHA512, and optional SHA384" diff --git a/libvctrl_sha512/README.md b/libvctrl_sha512/README.md index e7604330..c0412db5 100644 --- a/libvctrl_sha512/README.md +++ b/libvctrl_sha512/README.md @@ -1,172 +1,147 @@ # libvctrl_sha512 -**Version:** 2.0.0 -**License:** ISC -**Crate type:** Rust library (cryptographic primitives) -**Workspace:** libvcrtl - -`libvctrl_sha512` is a zero-dependency, `no_std`-compatible implementation of the SHA-512, HMAC-SHA-512, HKDF-SHA-512, and optional SHA-384 cryptographic algorithms. - -The crate is built for performance and minimal code size. All hash algorithms are implemented with careful attention to FIPS 180-4 and RFC 2104/5869. The API is designed for simplicity: one-shot convenience functions sit alongside incremental builders. - -The crate contains **no external dependencies** and can be used in both `std` and `no_std` environments. It is intended as the cryptographic foundation for the `libvcrtl` version control system, but it is fully general-purpose. - ---- - -## Table of Contents - -- [Overview](#overview) -- [System Architecture](#system-architecture) -- [Core Features](#core-features) -- [Technology Stack](#technology-stack) -- [Project Structure](#project-structure) -- [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Configuration](#configuration) -- [Usage](#usage) -- [API Reference](#api-reference) - - [Module: sha512](#module-sha512) - - [Module: sha384](#module-sha384) - - [Module: hmac](#module-hmac) - - [Module: hkdf](#module-hkdf) - - [Module: utils](#module-utils) - - [Macros](#macros) - - [Constants](#constants) -- [Testing](#testing) -- [CI/CD Pipeline](#cicd-pipeline) -- [Deployment / Distribution](#deployment--distribution) -- [Security & Compliance](#security--compliance) -- [Contributing](#contributing) -- [License](#license) -- [Changelog](#changelog) +Zero-dependency cryptographic primitives: SHA-512, HMAC-SHA512, HKDF-SHA512, and optional +SHA-384. A pure-Rust, auditable cryptography crate that serves as the content-addressing and +message-authentication backbone for the `libvctrl` workspace while remaining usable as a +standalone crypto library. + +- **Crate:** `libvctrl_sha512` 3.0.1 (library) +- **Language:** Rust, edition 2024 — MSRV **1.96.0** (declared via `rust-version`) +- **License:** ISC (distinct from the MIT license used by the rest of the workspace) +- **Repository:** https://github.com/mroczect/libvctrl +- **Documentation:** https://docs.rs/libvctrl_sha512 + +> The crate has **no external dependencies** and uses only `core` APIs internally. It is +> currently `std`-by-default (the published version does not yet disable the standard +> library), but adding `#![no_std]` would require no code changes. The core hash, HMAC, +> and HKDF types use fixed-size arrays and are allocation-free. --- ## Overview -`libvctrl_sha512` provides robust implementations of the following algorithms: - -- **SHA-512** as defined in FIPS 180-4. -- **HMAC-SHA-512** as defined in RFC 2104. -- **HKDF-SHA-512** as defined in RFC 5869. -- **SHA-384** as defined in FIPS 180-4, enabled via the `sha384` feature. -- **HMAC-SHA-384** and **HKDF-SHA-384** through the `sha384` feature. +`libvctrl_sha512` provides four cryptographic primitives in a single, dependency-free +crate: -The crate is designed with the following principles: +- **SHA-512** — the FIPS 180-4 hash function used for content addressing. +- **HMAC-SHA512** — keyed message authentication per RFC 2104. +- **HKDF-SHA512** — key derivation per RFC 5869. +- **SHA-384** (optional) — the FIPS 180-4 truncated variant of SHA-512, plus its + HMAC-SHA-384 and HKDF-SHA-384 companions. -- Zero external dependencies. -- `no_std` compatible; no heap allocations are required for hashing. -- Incremental and one-shot APIs. -- Constant-ish time comparison for verification where feasible. -- Strict Clippy and rustdoc linting with `#![deny]`. - -All code is written in safe Rust, with a single reviewed `unsafe` block in `utils::verify` used to prevent compiler optimizations from weakening the side-channel mitigation. +The implementation prioritises auditability (no external dependencies, readable code), +security (constant-time verification, zeroization of intermediate state), and performance +(aggressive inlining with an optional size-optimisation feature). The HMAC and HKDF types +are generated by exported macros, so downstream crates can instantiate them with other +hash functions. --- -## System Architecture - -### Module Organization +## Architecture -The crate is organized into logical modules: +The crate is organised as a thin layer over the SHA-512 core. The `sha512` module is the +foundation; `hmac` and `hkdf` are generated from it by macros; `sha384` wraps the SHA-512 +core with a different initialisation vector and truncates the output; `utils` provides +shared byte-order and constant-time comparison helpers. ```mermaid -graph TD - ROOT[libvctrl_sha512 root] - SHA512[sha512 module] - HMAC[hmac module] - HKDF[hkdf module] - SHA384[sha384 module
feature-gated] - UTILS[utils module] - - ROOT --> SHA512 - ROOT --> HMAC - ROOT --> HKDF - ROOT --> SHA384 - ROOT --> UTILS - - HMAC --> SHA512 - HKDF --> HMAC - SHA384 --> SHA512 - SHA384 --> UTILS +flowchart TD + LIB["lib.rs
crate root, macro exports, re-exports"] + LIB --> SHA512["sha512
SHA-512 (FIPS 180-4)
Hash struct"] + LIB --> HMAC["hmac
HMAC-SHA512 (RFC 2104)
via impl_hmac! macro"] + LIB --> HKDF["hkdf
HKDF-SHA512 (RFC 5869)
via impl_hkdf! macro"] + LIB --> UTILS["utils
load_be / store_be / verify
BLOCKBYTES / BYTES"] + LIB --> SHA384["sha384 (feature-gated)
SHA-384 + HMAC-SHA-384 + HKDF-SHA-384"] + + HMAC -.instantiated from.-> SHA512 + HKDF -.delegates to.-> HMAC + SHA384 -.wraps.-> SHA512 + SHA384 -.instantiates.-> HMAC + SHA384 -.instantiates.-> HKDF + HMAC -.uses.-> UTILS + SHA512 -.uses.-> UTILS ``` -### Macro-Generated Implementations +```` -HMAC and HKDF are not hand-written for each hash output size. Instead, two exported macros generate the necessary structs: +### HMAC-SHA512 construction (RFC 2104) -- `impl_hmac!` generates the `HMAC` struct with `new`, `update`, `finalize`, `mac`, `verify`, and `finalize_verify`. -- `impl_hkdf!` generates the `HKDF` struct with `extract` and `expand`. - -These macros are invoked in the `hmac` and `hkdf` modules for SHA-512, and again in the `sha384` module when the `sha384` feature is enabled. +HMAC normalises the key to the 128-byte block size, then computes the standard +inner/outer hash sandwich. A notable implementation detail: `finalize` transforms the +in-place `ipad` buffer into `opad` by XOR with `0x6a` (since `0x36 ^ 0x5c == 0x6a`), +avoiding a second key copy. The `HMAC` struct implements `Drop` to zeroize the inner +hasher and the padded key buffer. ```mermaid -graph LR - MACRO_HMAC[impl_hmac macro] --> HMAC_SHA512[HMAC-SHA-512] - MACRO_HMAC --> HMAC_SHA384[HMAC-SHA-384
feature-gated] - MACRO_HKDF[impl_hkdf macro] --> HKDF_SHA512[HKDF-SHA-512] - MACRO_HKDF --> HKDF_SHA384[HKDF-SHA-384
feature-gated] +flowchart LR + KEY[Secret key K] --> PREP[prepare_key
hash if len > 128, else pad to 128] + PREP --> KPAD["K' block-sized (128 bytes)"] + KPAD --> IPAD["XOR with 0x36 (ipad)"] + IPAD --> INNER[feed inner Hash with ipad] + MSG[Message m] --> INNER + INNER --> INNERD["inner digest H(ipad || m)"] + KPAD --> OPAD["XOR with 0x5c (opad)
via in-place 0x6a transform"] + OPAD --> OUTER[feed outer Hash with opad] + INNERD --> OUTER + OUTER --> TAG["Tag H(opad || H(ipad || m))
64 bytes"] + TAG --> DROP[Drop: zeroize key + buffers] ``` -### Feature Gate Mapping +### SHA-512 incremental pipeline + +The `Hash` struct maintains eight 64-bit working variables, a 128-byte block buffer, a +buffered-byte counter, and a `u128` total length. Input is buffered until a full 128-byte +block is available, at which point the block is processed through the message schedule and +80-round compression function. ```mermaid -graph TD - DEFAULT[default feature] --> SHA384_FEATURE[sha384] - SHA384_FEATURE --> SHA384_MODULE[sha384 module] - OPT_SIZE[opt_size feature] --> INLINE_CHANGE[Changes inline attributes
to favor size over speed] +flowchart LR + IN[Input bytes] --> UPD[update_inner
buffer into 128-byte block] + UPD -->|full block| BLK[State.blocks
process complete blocks] + UPD -->|partial| BUF[keep in buffer] + BLK --> SCH["W::new -> 16 words
W::expand -> 80-word schedule"] + SCH --> COMP["g x5 -> 80 rounds
Ch / Maj / Sigma / sigma + constants"] + COMP --> ADD[State.add
Merkle-Damgard feedback] + BUF --> FIN[finalize
pad 0x80 + 128-bit length] + FIN --> OUT["64-byte digest
big-endian state"] ``` --- ## Core Features -- **SHA-512** - One-shot and incremental hashing with 64-byte digests. - -- **SHA-384** - Optional feature-gated implementation with 48-byte digests, sharing the same compression core as SHA-512. - -- **HMAC-SHA-512** - Keyed-hash message authentication with support for keys longer than the block size, incremental updates, and constant-ish time verification. - -- **HMAC-SHA-384** - Feature-gated variant with 48-byte output. - -- **HKDF-SHA-512** - HMAC-based key derivation function following RFC 5869. Provides `extract` and `expand` steps. - -- **HKDF-SHA-384** - Feature-gated variant. - -- **Constant-ish time comparison** - `utils::verify` uses XOR accumulation and a `read_volatile` fence to reduce timing side-channel leakage. - -- **Zero dependencies** - No external crates are required. - -- **`no_std` compatible** - The core hashing logic does not require the standard library. - -- **Compile-time macro expansion** - `impl_hmac!` and `impl_hkdf!` reduce code duplication and ensure consistent behavior across hash functions. - -- **Strict linting** - `#![deny(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo, missing_docs)]`. +- **Zero dependencies.** No external crates; the entire stack is pure Rust over `core`. +- **SHA-512 (FIPS 180-4).** Merkle–Damgård construction, 128-byte block, 64-byte output, + 80 round constants. Incremental and one-shot APIs. +- **HMAC-SHA512 (RFC 2104).** One-shot and incremental APIs, key normalisation, in-place + ipad/opad transform, and `Drop`-based zeroization of key material. +- **HKDF-SHA512 (RFC 5869).** Extract-and-expand key derivation with enforced output + length limits. +- **Optional SHA-384.** Wraps the SHA-512 core with a different IV and truncates to 48 + bytes; also generates HMAC-SHA-384 and HKDF-SHA-384. +- **Constant-time verification.** `verify` accumulates XOR differences across all bytes + and uses `core::hint::black_box` to inhibit compiler short-circuiting; a WebAssembly + target receives an additional hash-based mask. +- **Zeroization.** `Hash::zeroize` and `HMAC`'s `Drop` overwrite sensitive state and key + material, with a compiler fence to prevent dead-store elimination. +- **Exported macros.** `impl_hmac!` and `impl_hkdf!` are `#[macro_export]`, allowing + downstream crates to instantiate HMAC and HKDF with their own hash structs. +- **Size optimisation.** The `opt_size` feature shrinks the binary by de-inlining the + compression round functions, for embedded, WebAssembly, and minimal-CLI targets. --- ## Technology Stack -- **Language:** Rust (edition 2024) -- **Dependencies:** None -- **Dev dependencies:** `criterion` 0.8 for benchmarks -- **Features:** - - `default = ["sha384"]` - - `sha384` – enables SHA-384, HMAC-SHA-384, HKDF-SHA-384 - - `opt_size` – favors smaller code size over speed by changing inline attributes -- **Targets:** `no_std` and `std` +- **Language:** Rust (edition 2024, MSRV 1.96.0 — explicitly declared) +- **Dependencies:** none (zero-dependency) +- **Dev-dependencies:** `criterion` 0.8 (`default-features = false`, with + `cargo_bench_support`) for benchmarks +- **Lint policy:** workspace-inherited. The crate locally allows + `clippy::indexing_slicing`, `clippy::unwrap_used`, and `clippy::expect_used` because the + performance-critical crypto paths use slice indexing and `Option::take().unwrap()` on + invariant-guaranteed states; it also allows `unused_crate_dependencies`. +- **Features:** `default = ["sha384"]`, `sha384`, `opt_size` --- @@ -175,395 +150,347 @@ graph TD ```text libvctrl_sha512/ ├── Cargo.toml +├── LICENSE +├── README.md ├── benches/ │ ├── sha512_bench.rs -│ └── sha384_bench.rs (requires sha384 feature) +│ └── sha384_bench.rs └── src/ ├── lib.rs - ├── hkdf.rs - ├── hmac.rs - ├── sha384.rs (feature-gated) ├── sha512.rs + ├── hmac.rs + ├── hkdf.rs + ├── sha384.rs └── utils.rs ``` +The published package includes `src/**/*`, `Cargo.toml`, `README.md`, and `LICENSE` +(per the `include` field); benchmarks remain in the repository for local use. + --- ## Getting Started ### Prerequisites -- Rust toolchain 1.96.0 or newer (edition 2024) +- Rust toolchain **1.96.0** or newer (edition 2024 is required) - Cargo No system libraries or external services are required. ### Installation -Add `libvctrl_sha512` to your `Cargo.toml`: +For most `libvctrl` workspace users, depend on the facade, which wires this crate with +`default-features = false` and re-exports it under the `crypto` namespace: ```toml [dependencies] -libvctrl_sha512 = "2.0.0" +libvctrl = "2.1" ``` -Or use Cargo: +To depend on `libvctrl_sha512` directly for standalone crypto use: -```sh -cargo add libvctrl_sha512 +```toml +[dependencies] +libvctrl_sha512 = "3.0" ``` -By default, the `sha384` feature is enabled. To disable it: +Or via Cargo: -```toml -[dependencies] -libvctrl_sha512 = { version = "2.0.0", default-features = false } +```bash +cargo add libvctrl_sha512 ``` ### Configuration -No configuration is required. Feature flags are the only configuration mechanism. +| Use case | Configuration | +| ---------------------------- | --------------------------------------------------- | +| Default (SHA-512 + SHA-384) | `default` (includes `sha384`) | +| SHA-512 only | `default-features = false` | +| Size-optimised, full crypto | `features = ["opt_size"]` | +| Size-optimised, SHA-512 only | `default-features = false, features = ["opt_size"]` | + +```toml +# Default: SHA-512 + SHA-384 +libvctrl_sha512 = "3.0" + +# Minimal: SHA-512 only +libvctrl_sha512 = { version = "3.0", default-features = false } + +# Size-optimised, full crypto +libvctrl_sha512 = { version = "3.0", features = ["opt_size"] } + +# Size-optimised, SHA-512 only +libvctrl_sha512 = { version = "3.0", default-features = false, features = ["opt_size"] } +``` + +- **`sha384`** (default): enables the `sha384` module, exposing `sha384::Hash`, + `sha384::HMAC`, and `sha384::HKDF` (SHA-384 plus HMAC-SHA-384 and HKDF-SHA-384). Disable + it to reduce compile time and code size when only SHA-512 is needed. +- **`opt_size`**: switches the SHA-512 compression round functions from `#[inline(always)]` + to `#[inline(never)]`. The result is smaller code size at the cost of slower hashing. + Intended for embedded, WebAssembly, and minimal-CLI targets. It does **not** enable + `no_std`; it is purely a code-size optimisation. --- ## Usage -### Compute a SHA-512 hash (one-shot) +### SHA-512 one-shot and incremental ```rust use libvctrl_sha512::Hash; +// One-shot let digest = Hash::hash(b"hello world"); assert_eq!(digest.len(), 64); -``` - -### Compute a SHA-512 hash (incremental) - -```rust -use libvctrl_sha512::Hash; +// Incremental let mut hasher = Hash::new(); hasher.update(b"hello "); hasher.update(b"world"); -let digest = hasher.finalize(); -assert_eq!(digest.len(), 64); +assert_eq!(hasher.finalize(), Hash::hash(b"hello world")); ``` -### Verify a hash in constant-ish time +### SHA-512 constant-time verification ```rust use libvctrl_sha512::Hash; -let expected = Hash::hash(b"verify this"); -let mut h = Hash::new(); -h.update(b"verify this"); -assert!(h.verify(&expected)); +let expected = Hash::hash(b"abc"); +let mut hasher = Hash::new(); +hasher.update(b"abc"); +assert!(hasher.verify(&expected)); ``` -### HMAC-SHA-512 one-shot +### HMAC-SHA512 authentication ```rust use libvctrl_sha512::HMAC; -let key = b"my secret"; -let tag = HMAC::mac(b"message", key); +// One-shot computation +let tag = HMAC::mac(b"message", b"secret-key"); assert_eq!(tag.len(), 64); -``` - -### HMAC-SHA-512 verification -```rust -use libvctrl_sha512::HMAC; - -let key = b"another key"; -let message = b"data to authenticate"; -let expected = HMAC::mac(message, key); - -let mut hmac = HMAC::new(key); -hmac.update(&message[..4]); -hmac.update(&message[4..]); -assert!(hmac.finalize_verify(&expected)); +// Constant-time verification +let key = b"secret-key"; +let tag = HMAC::mac(b"message", key); +assert!(HMAC::verify(b"message", key, &tag)); ``` -### HKDF-SHA-512 key derivation +### HKDF-SHA512 key derivation ```rust use libvctrl_sha512::HKDF; -let ikm = [0x0b; 22]; -let salt = [0x01; 13]; -let info = [0xf0; 10]; - -let prk = HKDF::extract(salt, ikm); -let mut okm = [0u8; 42]; -HKDF::expand(&mut okm, prk, info); - -assert_eq!(okm.len(), 42); +let prk = HKDF::extract(b"salt", b"input key material"); +let mut okm = [0u8; 32]; +HKDF::expand(&mut okm, prk, b"context-info"); +assert_eq!(okm.len(), 32); ``` -### SHA-384 (requires `sha384` feature) +### SHA-384 (requires the `sha384` feature) ```rust -use libvctrl_sha512::sha384::Hash; +use libvctrl_sha512::sha384::Hash as Sha384; -let digest = Hash::hash(b"hello world"); +let digest = Sha384::hash(b"abc"); assert_eq!(digest.len(), 48); ``` -### HMAC-SHA-384 (requires `sha384` feature) +### Instantiating the macros for a custom hash (downstream crates) -```rust -use libvctrl_sha512::sha384::HMAC; +The `impl_hmac!` and `impl_hkdf!` macros are exported so other crates can build HMAC and +HKDF on top of their own hash struct: -let key = b"secret"; -let tag = HMAC::mac(b"message", key); -assert_eq!(tag.len(), 48); +```rust,no_run +// Conceptual: assumes your crate provides a `MyHash` type with the same +// API surface as libvctrl_sha512::sha512::Hash (new / update / finalize / +// hash / zeroize), plus an output size and block size. +// impl_hmac!(MyHash, OUTPUT_SIZE, BLOCK_SIZE); +// impl_hkdf!(MyHash, OUTPUT_SIZE, BLOCK_SIZE); ``` --- -## API Reference - -### Module: sha512 - -#### Struct: `Hash` - -Represents the SHA-512 hasher state. Provides incremental and one-shot hashing. - -**Methods:** - -| Method | Signature | Description | -| ---------- | ---------------------------------------------------- | ------------------------------------------------------------------ | -| `new` | `pub fn new() -> Self` | Creates a new SHA-512 hasher with the standard IV. | -| `update` | `pub fn update>(&mut self, input: T)` | Feeds data into the hasher. | -| `finalize` | `pub fn finalize(self) -> [u8; 64]` | Consumes the hasher and returns the 64-byte digest. | -| `hash` | `pub fn hash>(input: T) -> [u8; 64]` | One-shot hash. | -| `verify` | `pub fn verify(self, expected: &[u8; 64]) -> bool` | Finalizes and compares in constant-ish time. | -| `zeroize` | `pub fn zeroize(&mut self)` | Overwrites internal state with zeros and inserts a compiler fence. | - -**Example:** - -```rust -use libvctrl_sha512::Hash; - -let mut h = Hash::new(); -h.update(b"abc"); -let digest = h.finalize(); -``` - -### Module: sha384 - -Available only with the `sha384` feature. - -#### Struct: `Hash` - -Thin wrapper around the SHA-512 core with a different IV and truncated 48-byte output. - -**Methods:** - -| Method | Signature | Description | -| ---------- | ---------------------------------------------------- | ----------------------------- | -| `new` | `pub fn new() -> Self` | Creates a new SHA-384 hasher. | -| `update` | `pub fn update>(&mut self, input: T)` | Feeds data. | -| `finalize` | `pub fn finalize(self) -> [u8; 48]` | Returns the 48-byte digest. | -| `hash` | `pub fn hash>(input: T) -> [u8; 48]` | One-shot hash. | -| `zeroize` | `pub fn zeroize(&mut self)` | Clears state. | - -Additionally, `HMAC` and `HKDF` structs are generated inside this module for 48-byte output and 128-byte block size. +## API Reference / Core Modules -### Module: hmac +Full API documentation is published at . The crate root +hosts the SHA-512 family; SHA-384 types live under the `sha384` module. -#### Struct: `HMAC` +### Root re-exports (SHA-512 family) -Generated by `impl_hmac!(crate::sha512::Hash, 64, 128)` for SHA-512. +| Item | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------------------ | +| `Hash` | struct | SHA-512 hasher (`new`, `update`, `finalize`, `hash`, `verify`, `zeroize`). | +| `HMAC` | struct | HMAC-SHA512 (`mac`, `new`, `update`, `finalize`, `finalize_verify`, `verify`). | +| `HKDF` | struct | HKDF-SHA512 (`extract`, `expand`). | +| `BYTES` | const | SHA-512 output size in bytes (`64`). | +| `BLOCKBYTES` | const | SHA-512 block size in bytes (`128`). | -**Methods:** +> `BYTES` and `BLOCKBYTES` refer to **SHA-512** sizes. SHA-384 shares the 128-byte block +> size but produces a 48-byte output. -| Method | Signature | Description | -| ----------------- | -------------------------------------------------------------------------------------------- | --------------------------- | -| `new` | `pub fn new(k: impl AsRef<[u8]>) -> Self` | Creates a new HMAC context. | -| `update` | `pub fn update(&mut self, input: impl AsRef<[u8]>)` | Feeds data. | -| `finalize` | `pub fn finalize(self) -> [u8; 64]` | Finalizes and returns tag. | -| `mac` | `pub fn mac, U: AsRef<[u8]>>(input: T, k: U) -> [u8; 64]` | One-shot HMAC. | -| `verify` | `pub fn verify, U: AsRef<[u8]>>(input: T, k: U, expected: &[u8; 64]) -> bool` | One-shot verification. | -| `finalize_verify` | `pub fn finalize_verify(self, expected: &[u8; 64]) -> bool` | Finalizes and verifies. | +### `sha512` — SHA-512 (FIPS 180-4) -### Module: hkdf +- **`Hash`** — the incremental hasher. Holds eight 64-bit working variables, a 128-byte + block buffer, a buffered-byte counter, and a `u128` total length. `Clone` so that + HMAC/HKDF can fork intermediate state. `finalize` applies standard padding (`0x80`, zero + fill, 128-bit big-endian length) and returns the 64-byte digest. `verify` finalises and + compares in constant time. `zeroize` overwrites state, buffer, and length, then emits a + compiler fence. +- Internal types `W` (message schedule) and `State` (eight `u64` working variables) + implement the `Ch`/`Maj`/`Σ0`/`Σ1`/`σ0`/`σ1` logical functions, the 80-word expansion, + and the 80-round compression function with the standard round constants. -#### Struct: `HKDF` +### `hmac` — HMAC-SHA512 (RFC 2104) -Generated by `impl_hkdf!(crate::sha512::Hash, 64, 128)`. +- **`HMAC`** — generated by `impl_hmac!(Hash, 64, 128)`. `prepare_key` normalises the key + to the 128-byte block size (hashing it if too long). The inner hash is seeded with + `ipad XOR key` (`0x36`); `finalize` transforms the buffer in place to + `opad XOR key` (`0x5c`) via XOR `0x6a`, then computes the outer hash. `Drop` zeroizes + the inner hasher and the padded key buffer. -**Methods:** +### `hkdf` — HKDF-SHA512 (RFC 5869) -| Method | Signature | Description | -| --------- | ------------------------------------------------------------------------------ | ------------------ | -| `extract` | `pub fn extract(salt: impl AsRef<[u8]>, ikm: impl AsRef<[u8]>) -> [u8; 64]` | HKDF-Extract step. | -| `expand` | `pub fn expand(out: &mut [u8], prk: impl AsRef<[u8]>, info: impl AsRef<[u8]>)` | HKDF-Expand step. | +- **`HKDF`** — generated by `impl_hkdf!(Hash, 64, 128)`. A zero-sized type. + `extract(salt, ikm)` returns a 64-byte PRK (HMAC with the salt as key). `expand(out, prk, info)` + fills the output buffer with OKM of arbitrary length, enforcing the RFC 5869 limit + (`< 0xff * output_size`) and requiring the PRK to be exactly `output_size` bytes. -**Panics:** +### `sha384` — SHA-384, HMAC-SHA-384, HKDF-SHA-384 (feature-gated) -- `expand` panics if `prk` length is not 64 bytes, or if `out.len()` is greater than `255 * 64 = 16320`. +Available only when the `sha384` feature is enabled. -### Module: utils +- **`sha384::Hash`** — wraps the SHA-512 core with the SHA-384 initialisation vector and + truncates the 64-byte digest to 48 bytes. Same incremental API as SHA-512. +- **`sha384::HMAC`** — HMAC-SHA-384, generated by `impl_hmac!(Hash, 48, 128)`. +- **`sha384::HKDF`** — HKDF-SHA-384, generated by `impl_hkdf!(Hash, 48, 128)`. -#### Functions: +### `utils` — Shared helpers -| Function | Signature | Description | -| ---------- | --------------------------------------------------------- | --------------------------------------- | -| `load_be` | `pub fn load_be(base: &[u8], offset: usize) -> u64` | Loads a big-endian u64 from a slice. | -| `store_be` | `pub fn store_be(base: &mut [u8], offset: usize, x: u64)` | Stores a u64 as big-endian bytes. | -| `verify` | `pub fn verify(x: &[u8], y: &[u8]) -> bool` | Compares slices with constant-ish time. | +| Item | Kind | Description | +| ------------ | ----- | ------------------------------------------------------------------ | +| `load_be` | fn | Loads a `u64` from a byte slice at an offset in big-endian order. | +| `store_be` | fn | Stores a `u64` into a byte slice at an offset in big-endian order. | +| `verify` | fn | Constant-time comparison of two byte slices; `black_box`-guarded. | +| `BLOCKBYTES` | const | `128` — SHA-512 block size. | +| `BYTES` | const | `64` — SHA-512 output size. | -`verify` uses XOR accumulation and `read_volatile` on the final result. This is not formally constant-time but significantly raises the bar for timing attacks. +### Exported macros -### Macros +| Macro | Purpose | +| ------------ | ------------------------------------------------------------------------------ | +| `impl_hmac!` | Generates an `HMAC` type for a given hash struct, output size, and block size. | +| `impl_hkdf!` | Generates an `HKDF` type for a given hash struct, output size, and block size. | -Both macros are exported at crate root. - -#### `impl_hmac!` - -```rust -macro_rules! impl_hmac { - ($hash_struct:ty, $output_size:expr, $block_size:expr) => { ... } -} -``` - -Generates an `HMAC` struct with methods listed above. - -#### `impl_hkdf!` - -```rust -macro_rules! impl_hkdf { - ($hash_struct:ty, $output_size:expr, $block_size:expr) => { ... } -} -``` - -Generates an `HKDF` struct with `extract` and `expand`. - -### Constants - -| Constant | Value | Description | -| ------------ | ----- | ----------------------------- | -| `BYTES` | 64 | SHA-512 output size in bytes. | -| `BLOCKBYTES` | 128 | SHA-512 block size in bytes. | - -Both are re-exported at crate root from `utils`. +Both are `#[macro_export]` and use `$crate` for path stability when invoked from +external crates. --- ## Testing -The crate includes unit tests, doctests, and benchmarks. - -Run unit tests: +The crate ships with known-answer tests for HMAC and HKDF (RFC test vectors) plus SHA-512 +and SHA-384 digest vectors. Run the test suite with: -```sh +```bash cargo test ``` -Run all tests including doctests: +To run only the SHA-512 or SHA-384 tests: -```sh -cargo test --all-features +```bash +cargo test --lib sha512 +cargo test --lib sha384 # requires the sha384 feature (on by default) ``` -Run doctests only: +### Benchmarks -```sh -cargo test --doc -``` - -Run benchmarks: +Benchmarks use `criterion` and live under `benches/`. Run them with: -```sh -cargo bench --all-features +```bash +cargo bench ``` -The test suite includes known-answer tests for HMAC-SHA-512 and HKDF-SHA-512 using RFC test vectors. - ---- - -## CI/CD Pipeline - -No CI/CD pipeline is currently configured in the repository. - -If one is added, the following stages are recommended: +The `sha384_bench` benchmark requires the `sha384` feature. With default features it runs +automatically; if you have disabled default features, enable it explicitly: -```mermaid -graph LR - A[Push] --> B[Format Check] - B --> C[Clippy Lint] - C --> D[Run Tests] - D --> E[Run Benchmarks] - E --> F[Publish to crates.io] +```bash +cargo bench --features sha384 ``` -Recommended commands: +To run a single benchmark: -- Format: `cargo fmt --check` -- Lint: `cargo clippy --all-targets --all-features -- -D warnings` -- Tests: `cargo test --all-features` -- Docs: `cargo doc --no-deps` +```bash +cargo bench --bench sha512_bench +cargo bench --bench sha384_bench # requires the sha384 feature +``` --- -## Deployment / Distribution +## Contributing -The crate is intended to be published to crates.io. +Contributions are welcome. The crate enforces `#![forbid(unsafe_code)]` and inherits the +workspace lint policy, with local allowances for `clippy::indexing_slicing`, +`clippy::unwrap_used`, and `clippy::expect_used` in the performance-critical crypto paths. +All public items must be documented. -Release process: +For contribution guidelines, code style, and the full lint configuration, see the +repository's `CONTRIBUTING.md` and the workspace root `README.md`: -1. Update `version` in `Cargo.toml`. -2. Update `CHANGELOG.md`. -3. Run `cargo publish --dry-run`. -4. Run `cargo publish`. +- Repository: https://github.com/mroczect/libvctrl -After publication, documentation will be available at `https://docs.rs/libvctrl_sha512`. +When contributing, preserve the zero-dependency invariant: no external crates may be +added to `[dependencies]`. New primitives should be implemented over `core` APIs only. --- -## Security & Compliance - -`libvctrl_sha512` is a cryptographic library. The following security practices are enforced: - -- **No unsafe code except one reviewed block** - The only `unsafe` usage is in `utils::verify` to call `core::ptr::read_volatile` and prevent compiler optimizations from weakening the side-channel mitigation. - -- **Constant-ish time comparison** - `verify` uses XOR accumulation and does not short-circuit, reducing timing side-channel leakage. - -- **Zeroization** - `Hash::zeroize` and `HMAC`'s `Drop` implementation clear internal state and use a compiler fence. +## Ecosystem -- **`no_std` compatibility** - The crate does not require the standard library for core hashing, reducing attack surface. +`libvctrl_sha512` is the crypto engine of the `libvctrl` workspace. The related crates +are listed below; each has its own documentation. -- **Audited algorithms** - Implementations follow FIPS 180-4, RFC 2104, and RFC 5869. +| Crate | Role | Documentation | +| -------------------- | ----------------------------------------------------------- | ---------------------------------- | +| `libvctrl` | Facade: re-exports contracts, reference impl, and crypto | https://docs.rs/libvctrl | +| `libvctrl_handler` | Contract layer: traits, types, limits, validation | https://docs.rs/libvctrl_handler | +| `libvctrl_core` | Reference implementations (codec, builders, stores, hasher) | https://docs.rs/libvctrl_core | +| `libvctrl_plumbing` | Command-level VCS operations built on `libvctrl_core` | https://docs.rs/libvctrl_plumbing | +| `libvctrl_porcelain` | High-level, user-facing VCS operations | https://docs.rs/libvctrl_porcelain | -- **Strict linting** - Clippy nursery and pedantic are denied, catching many potential bugs at compile time. - -This crate is not formally audited. For high-security applications, prefer a formally verified constant-time library. +The facade wires this crate with `default-features = false` and re-exports it under the +`crypto` namespace, so most workspace users never need to depend on `libvctrl_sha512` +directly. --- -## Contributing +## License -Contributions are welcome. Follow the workspace `CONTRIBUTING.md`. +Licensed under the **ISC License**. This differs from the rest of the `libvctrl` +workspace, which is MIT-licensed; the ISC license is a short, permissive license commonly +used for security-focused code. -For this crate, ensure: +Copyright (c) mroczect ``. The authoritative copyright notice and full +text are in the `LICENSE` file of the repository. The substantive terms of the ISC License +are: -- All public items have documentation with doctests. -- Unsafe code must be minimized and thoroughly reviewed. -- Run `cargo fmt`. -- Run `cargo clippy --all-targets --all-features -- -D warnings`. -- All tests pass with `cargo test --all-features`. -- Benchmark changes are benchmarked with `cargo bench`. +```txt +ISC License ---- +Copyright (c) 2020-2026, Frank Denis. +Copyright (c) 2026, mroczect -## License +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -This project is licensed under the ISC License. See the `LICENSE` file in the workspace root for details. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +``` +```` diff --git a/release.json b/release.json deleted file mode 100644 index f7a6865a..00000000 --- a/release.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "crates": [ - { "name": "libvctrl_sha512", "version": "3.0.0" }, - { "name": "libvctrl_handler", "version": "5.0.0" }, - { "name": "libvctrl_core", "version": "3.0.0" }, - { "name": "libvctrl", "version": "2.1.2" }, - { "name": "libvctrl_plumbing", "version": "0.2.0" }, - { "name": "libvctrl_porcelain", "version": "0.1.0" } - ] -}