libvctrl_core
Overview
libvctrl_core is the reference implementation crate for the libvctrl_handler trait contracts. It provides production-ready, fully safe, and strictly linted concrete implementations of the abstract interfaces required to build a modular, content-addressable version control system.
This crate exists for three reasons:
- Contract validation -- If a trait in
libvctrl_handleris too difficult or ambiguous to implement, the problem is discovered here first, before downstream consumers encounter it. - Batteries included -- Developers get a working VCS backend stack (hashing, storage, encoding, validation) immediately, without writing boilerplate implementations.
- Quality exemplar -- All code forbids unsafe (
#![forbid(unsafe_code)]), passesclippy::pedanticandclippy::nursery, is heavily tested, and extensively documented. It serves as the authoritative model for anyone writing custom backends.
Architecture
libvctrl_core is one crate inside the libvctrl Cargo workspace. It depends on two sibling crates -- libvctrl_handler (the trait contracts) and libvctrl_sha512 (a pure-Rust SHA-512 implementation) -- and implements their interfaces with concrete, production-ready types.
Workspace Crate Dependency Graph
graph TD
subgraph Workspace["libvctrl workspace"]
handler["libvctrl_handler<br/>(pure trait contracts,<br/>no dependencies)"]
sha512["libvctrl_sha512<br/>(SHA-512 / HMAC / HKDF,<br/>standalone)"]
core["libvctrl_core<br/>(reference implementations)"]
plumbing["libvctrl_plumbing<br/>(low-level CLI,<br/>not yet implemented)"]
porcelain["libvctrl_porcelain<br/>(high-level CLI,<br/>not yet implemented)"]
cli["libvctrl<br/>(CLI entry point,<br/>not yet implemented)"]
docs["libvctrl_docs<br/>(documentation)"]
end
core -->|depends on| handler
core -->|depends on| sha512
plumbing -.->|will depend on| handler
porcelain -.->|will depend on| handler
cli -.->|will depend on| handler
style core fill:#4a7c59,stroke:#2d4a33,color:#fff
style handler fill:#5b7daa,stroke:#3a5278,color:#fff
style sha512 fill:#8a6d3b,stroke:#5c4928,color:#fff
Internal Module Architecture
Each module in libvctrl_core corresponds to a domain responsibility defined in libvctrl_handler. The following diagram illustrates the internal structure and the trait-to-implementation mapping:
graph LR
subgraph Contracts["libvctrl_handler (traits)"]
Encoder["Encoder"]
Decoder["Decoder"]
Hasher["Hasher"]
ObjectStore["ObjectStore"]
RefStore["RefStore"]
end
subgraph Core["libvctrl_core (implementations)"]
BinaryEncoder["BinaryEncoder"]
BinaryDecoder["BinaryDecoder"]
Sha512Hasher["Sha512Hasher"]
MemoryStore["MemoryStore"]
MemoryRefStore["MemoryRefStore"]
end
BinaryEncoder -.->|impl| Encoder
BinaryDecoder -.->|impl| Decoder
Sha512Hasher -.->|impl| Hasher
MemoryStore -.->|impl| ObjectStore
MemoryRefStore -.->|impl| RefStore
Object Lifecycle and Data Flow
The following sequence diagram shows the complete lifecycle of a VCS object as it is created, validated, hashed, encoded, and stored:
sequenceDiagram
participant App as Application
participant Obj as object module
participant Val as validate module
participant Hash as Sha512Hasher
participant Enc as BinaryEncoder
participant Store as MemoryStore
App->>Obj: Construct Blob / Tree / Commit / Tag
Obj->>Val: validate_name() / validate_hash_bytes()
Val-->>Obj: Ok or VctrlError
App->>Hash: hash(&binary_data)
Hash-->>App: Hash (64-byte SHA-512 digest)
App->>Enc: encode_blob(&blob)
Enc-->>App: Vec<u8> (versioned binary payload)
App->>Store: put(hash, &mut Read)
Store-->>App: Ok(())
Note over App,Store: Round-trip: Store.get(hash) -> BinaryDecoder.decode_*() -> original object
Core Features
- Binary Codec -- Deterministic, versioned, little-endian binary wire format with length-prefixed variable-length fields. Supports full round-trip encode/decode for all four VCS object types (
Blob,Tree,Commit,Tag). - Defensive Decoding -- The
BinaryDecoderis panic-free. Every slice access is bounds-checked. Malformed or truncated payloads returnVctrlError::CorruptedDatarather than crashing. - DoS-Resistant Allocation -- Before allocating memory for variable-length fields (blob data, commit messages, tag messages), the decoder validates the requested length against system limits (
MAX_BLOB_SIZE,MAX_MESSAGE_LENGTH,MAX_TREE_ENTRIES). - SHA-512 Content Addressing --
Sha512Hasherdelegates to the audited, pure-Rustlibvctrl_sha512crate to produce 64-byte digests, providing a massive keyspace that makes accidental collisions practically impossible. - In-Memory Object Store --
MemoryStoreimplementsObjectStorewithBox<dyn Read>for lightweight testing, simulation, and prototyping without any filesystem or network dependency. - In-Memory Reference Store --
MemoryRefStoreimplementsRefStorewith aRefsIteratorfor managing named references (branches, tags) in memory. - Object Builders -- The
objectmodule provides constructors and builder logic forBlob,Commit,Tag,Tree, andTreeEntrydomain objects. - Input Validation -- The
validatemodule enforces hash byte length (validate_hash_bytes) and name safety (validate_name), including path traversal attack prevention. - Strict UTF-8 Enforcement -- All string fields in decoded payloads are validated with
str::from_utf8. Invalid sequences result in a corruption error. - Wire Format Versioning -- Every serialized payload begins with a version byte. Incompatible formats are rejected early by the decoder, enabling future breaking changes to the wire format without silent data corruption.
- Zero Unsafe -- The entire crate compiles with
#![forbid(unsafe_code)]and passesclippy::pedanticplusclippy::nursery.
Technology Stack
- Language: Rust (Edition 2024, minimum toolchain 1.85+)
- Core Dependency:
libvctrl_handlerv4.0.0 -- trait contracts (Encoder,Decoder,Hasher,ObjectStore,RefStore) and domain types (Blob,Tree,Commit,Tag,Hash,VctrlError, and limits) - Crypto Dependency:
libvctrl_sha512v2.0.0 -- pure-Rust SHA-512, HMAC, and HKDF implementation - Linting:
#![forbid(unsafe_code)],clippy::pedantic,clippy::nursery - Testing:
cargo testwith integration and unit tests covering all encode/decode round-trips, validation edge cases, and store operations
Project Structure
src/
├── codec/
│ ├── binary_decoder.rs # BinaryDecoder: panic-free deserialization
│ ├── binary_encoder.rs # BinaryEncoder: deterministic serialization
│ └── mod.rs # Re-exports BinaryEncoder, BinaryDecoder
├── hash/
│ ├── mod.rs # Re-exports Sha512Hasher
│ └── sha512.rs # Sha512Hasher: SHA-512 Hasher trait impl
├── lib.rs # Crate root: module declarations, re-exports, docs
├── object/
│ ├── blob.rs # Blob object builder/constructor
│ ├── commit.rs # Commit object builder/constructor
│ ├── mod.rs # Re-exports object types
│ ├── tag.rs # Tag object builder/constructor
│ └── tree.rs # Tree and TreeEntry object builders
├── store/
│ ├── memory.rs # MemoryStore: in-memory ObjectStore impl
│ ├── mod.rs # Re-exports store types
│ └── ref_store.rs # MemoryRefStore: in-memory RefStore impl
└── validate/
├── hash.rs # validate_hash_bytes: 64-byte hash enforcement
├── mod.rs # Re-exports validation functions
└── name.rs # validate_name: length + path traversal prevention
Module Responsibility Summary
| Module | Purpose | Key Types Implemented | Traits Satisfied |
|---|---|---|---|
codec |
Binary serialization and deserialization | BinaryEncoder, BinaryDecoder |
Encoder, Decoder |
hash |
Cryptographic content addressing | Sha512Hasher |
Hasher |
object |
Domain object construction | Blob, Tree, TreeEntry, Commit, Tag |
N/A (builders) |
store |
Object and reference persistence | MemoryStore, MemoryRefStore |
ObjectStore, RefStore |
validate |
Input sanitization and safety checks | validate_hash_bytes, validate_name |
N/A (free functions) |
Getting Started
Prerequisites
- Rust toolchain -- Stable Rust 1.85 or later (Edition 2024 is required).
- Cargo -- Included with the Rust toolchain.
- Access to crates.io -- For resolving
libvctrl_handlerandlibvctrl_sha512dependencies.
Install the toolchain if not already present:
Verify the version:
# Expect: rustc 1.85.0 (or later)
Installation
Add libvctrl_core to your Cargo.toml:
[]
= "4.0"
If you are working inside the libvctrl workspace, the dependency is already configured via the workspace Cargo.toml. Clone and build:
Configuration
There are no feature flags, environment variables, or runtime configuration files required by libvctrl_core at this time. All modules are compiled by default.
The relevant system limits are defined in libvctrl_handler and consumed by this crate:
| Constant | Purpose |
|---|---|
MAX_BLOB_SIZE |
Maximum allowed blob data length in bytes. Prevents DoS via oversized allocation. |
MAX_MESSAGE_LENGTH |
Maximum allowed commit/tag message length in bytes. |
MAX_TREE_ENTRIES |
Maximum allowed number of entries in a single Tree object. |
HASH_LENGTH |
Expected hash digest length (64 bytes for SHA-512). |
Usage
Encoding and Decoding Objects
All four VCS object types follow the same encode/decode pattern. The BinaryEncoder serializes objects into a versioned binary payload, and the BinaryDecoder deserializes them back.
use ;
use ;
// Construct a Blob
let blob = new;
// Encode to binary
let encoder = BinaryEncoder;
let bytes = encoder.encode_blob.expect;
// Decode back to a Blob
let decoder = BinaryDecoder;
let decoded = decoder.decode_blob.expect;
assert_eq!;
Hashing Objects
use Hasher;
use Sha512Hasher;
let hasher = Sha512Hasher;
let digest = hasher.hash;
// The digest is a 64-byte Hash value
assert_eq!;
Using the In-Memory Store
use ;
use Sha512Hasher;
use MemoryStore;
use Cursor;
let store = new;
let hasher = Sha512Hasher;
// Compute the content hash
let data = b"stored content";
let hash = hasher.hash;
// Put the object into the store
let mut reader = new;
store.put.expect;
// Retrieve the object
let mut retrieved = store.get.expect;
let mut buf = Vecnew;
retrieved.read_to_end.expect;
assert_eq!;
Validating Names
use validate_name;
// Valid name
assert!;
// Path traversal attack is rejected
assert!;
API Reference / Core Modules
codec::BinaryEncoder
A stateless unit struct implementing the Encoder trait. Serializes VCS objects into a compact, versioned, little-endian binary format.
| Method | Input | Output Format |
|---|---|---|
encode_blob |
&Blob |
VERSION(1B) + data_len(8B u64 LE) + data |
encode_tree |
&Tree |
VERSION(1B) + entry_count(4B u32 LE) + [entries] |
encode_commit |
&Commit |
VERSION(1B) + tree_hash(64B) + parent_count(1B) + ... + message + metadata |
encode_tag |
&Tag |
VERSION(1B) + name + target_hash(64B) + tagger? + message + metadata |
The wire format version is 2 (public constant codec::binary_encoder::VERSION).
codec::BinaryDecoder
A stateless unit struct implementing the Decoder trait. Performs panic-free, bounds-checked deserialization of binary payloads produced by BinaryEncoder.
| Method | Input | Error Conditions |
|---|---|---|
decode_blob |
&[u8] |
Truncated data, version mismatch, blob exceeds MAX_BLOB_SIZE, length mismatch |
decode_tree |
&[u8] |
Truncated data, version mismatch, entry count exceeds MAX_TREE_ENTRIES, invalid UTF-8, malformed hash |
decode_commit |
&[u8] |
Truncated data, version mismatch, message exceeds MAX_MESSAGE_LENGTH, invalid UTF-8 |
decode_tag |
&[u8] |
Truncated data, version mismatch, invalid tagger presence byte, invalid UTF-8, message exceeds limit |
hash::Sha512Hasher
A zero-sized type implementing the Hasher trait. Produces 64-byte SHA-512 digests by delegating to libvctrl_sha512.
| Method | Input | Output |
|---|---|---|
hash |
&[u8] |
Hash (64-byte digest) |
store::MemoryStore
An in-memory implementation of ObjectStore. Stores objects as Vec<u8> keyed by Hash. Suitable for testing and prototyping.
| Method | Description |
|---|---|
new() |
Creates an empty store |
put(&Hash, &mut dyn Read) |
Stores an object under the given hash |
get(&Hash) |
Retrieves a Box<dyn Read> for the object |
store::MemoryRefStore
An in-memory implementation of RefStore. Manages named references (branches, tags) as Hash values.
| Method | Description |
|---|---|
new() |
Creates an empty reference store |
resolve(&str) |
Resolves a reference name to its Hash |
set(&str, &Hash) |
Sets a reference name to point to a hash |
iter() |
Returns a RefsIterator over all references |
validate
| Function | Signature | Purpose |
|---|---|---|
validate_hash_bytes |
(&[u8]) -> Result<(), VctrlError> |
Ensures the slice is exactly 64 bytes (HASH_LENGTH) |
validate_name |
(&str) -> Result<(), VctrlError> |
Ensures the name is non-empty, within length limits, and does not contain path traversal sequences (..) |
Binary Wire Format Specification
All payloads share a common structure: a leading version byte followed by type-specific fields. Integers are little-endian. Variable-length data is length-prefixed.
Blob
Offset Size Field
0 1 VERSION (u8, always 2)
1 8 data_len (u64 LE)
9 N data (N = data_len bytes)
Tree
Offset Size Field
0 1 VERSION (u8, always 2)
1 4 entry_count (u32 LE)
5 ... entries (repeated entry_count times):
1 name_len (u8)
N name (UTF-8, N = name_len)
1 kind (0=Blob, 1=Executable, 2=Symlink, 3=Tree, 4=Submodule)
64 hash
Commit
Offset Size Field
0 1 VERSION (u8, always 2)
1 64 tree_hash
65 1 parent_count (u8)
66 P*64 parent_hashes
.. 1 author_name_len (u8)
.. N author_name (UTF-8)
.. 1 author_email_len (u8)
.. N author_email (UTF-8)
.. 1 committer_name_len (u8)
.. N committer_name (UTF-8)
.. 1 committer_email_len (u8)
.. N committer_email (UTF-8)
.. 4 msg_len (u32 LE)
.. N message (UTF-8, N = msg_len)
.. 8 timestamp (i64 LE)
.. 2 timezone_offset (i16 LE)
.. 1 encoding_len (u8)
.. N encoding (UTF-8, N = encoding_len; 0 means None)
Tag
Offset Size Field
0 1 VERSION (u8, always 2)
1 1 name_len (u8)
2 N name (UTF-8, N = name_len)
.. 64 target_hash
.. 1 has_tagger (0 or 1)
.. [if has_tagger == 1:]
1 tagger_name_len (u8)
N tagger_name (UTF-8)
1 tagger_email_len (u8)
N tagger_email (UTF-8)
.. 4 msg_len (u32 LE)
.. N message (UTF-8, N = msg_len)
.. 8 timestamp (i64 LE)
.. 2 timezone_offset (i16 LE)
.. 1 encoding_len (u8)
.. N encoding (UTF-8, N = encoding_len; 0 means None)
Testing
All tests are run via the standard Cargo test harness:
# Run all tests for this crate
# Run with output for individual tests
# Run only encode/decode round-trip tests
The test suite covers:
- Encode/decode round-trips for all four object types (
Blob,Tree,Commit,Tag). - Corrupted data rejection -- truncated payloads, wrong version bytes, invalid UTF-8, malformed hashes.
- DoS limit enforcement -- blobs exceeding
MAX_BLOB_SIZE, messages exceedingMAX_MESSAGE_LENGTH, trees exceedingMAX_TREE_ENTRIES. - Validation edge cases -- empty names, overly long names, path traversal attempts, incorrect hash lengths.
- Store operations -- put/get round-trips in
MemoryStore, reference resolution inMemoryRefStore.
Contributing
Contributions are welcome. Please adhere to the following standards:
- No unsafe code. The crate forbids it at the compiler level. Do not attempt to add
unsafeblocks. - Clippy compliance. All contributions must pass
cargo clippy -- -D warningswith the project's lint configuration (clippy::pedantic,clippy::nursery). - Documentation. Every public item must have a doc comment explaining its purpose, design rationale, and error conditions. Module-level doc comments must include at least one example.
- Tests. Every new feature or bug fix must include tests that cover both the happy path and failure modes (corrupted data, limit enforcement, invalid input).
- Format. Run
cargo fmtbefore committing. CI will reject unformatted code. - Wire format stability. Changes to the binary wire format must bump the
VERSIONconstant in bothbinary_encoder.rsandbinary_decoder.rs. Never change the encoding of an existing version. - Workspace consistency. This crate lives in the
libvctrlworkspace. Ensure that changes tolibvctrl_coredo not break the contract signatures inlibvctrl_handler. If a contract change is needed, updatelibvctrl_handlerfirst and version both crates together.
CI Checklist
Before opening a pull request, verify locally: