libvctrl_core 2.0.1

Reference implementations of the libvctrl contracts (in-memory store, SHA-512 hasher, binary codec)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# 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_handler` is 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)]`), passes `clippy::pedantic` and `clippy::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

```mermaid
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:

```mermaid
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:

```mermaid
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&lt;u8&gt; (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 `BinaryDecoder` is panic-free. Every slice access is bounds-checked. Malformed or truncated payloads return `VctrlError::CorruptedData` rather 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** -- `Sha512Hasher` delegates to the audited, pure-Rust `libvctrl_sha512` crate to produce 64-byte digests, providing a massive keyspace that makes accidental collisions practically impossible.
- **In-Memory Object Store** -- `MemoryStore` implements `ObjectStore` with `Box<dyn Read>` for lightweight testing, simulation, and prototyping without any filesystem or network dependency.
- **In-Memory Reference Store** -- `MemoryRefStore` implements `RefStore` with a `RefsIterator` for managing named references (branches, tags) in memory.
- **Object Builders** -- The `object` module provides constructors and builder logic for `Blob`, `Commit`, `Tag`, `Tree`, and `TreeEntry` domain objects.
- **Input Validation** -- The `validate` module 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 passes `clippy::pedantic` plus `clippy::nursery`.

## Technology Stack

- **Language:** Rust (Edition 2024, minimum toolchain 1.85+)
- **Core Dependency:** `libvctrl_handler` v4.0.0 -- trait contracts (`Encoder`, `Decoder`, `Hasher`, `ObjectStore`, `RefStore`) and domain types (`Blob`, `Tree`, `Commit`, `Tag`, `Hash`, `VctrlError`, and limits)
- **Crypto Dependency:** `libvctrl_sha512` v2.0.0 -- pure-Rust SHA-512, HMAC, and HKDF implementation
- **Linting:** `#![forbid(unsafe_code)]`, `clippy::pedantic`, `clippy::nursery`
- **Testing:** `cargo test` with integration and unit tests covering all encode/decode round-trips, validation edge cases, and store operations

## Project Structure

```text
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_handler` and `libvctrl_sha512` dependencies.

Install the toolchain if not already present:

```bash
rustup install stable
rustup default stable
rustup update stable
```

Verify the version:

```bash
rustc --version
# Expect: rustc 1.85.0 (or later)
```

### Installation

Add `libvctrl_core` to your `Cargo.toml`:

```toml
[dependencies]
libvctrl_core = "4.0"
```

If you are working inside the `libvctrl` workspace, the dependency is already configured via the workspace `Cargo.toml`. Clone and build:

```bash
git clone <repository-url> libvctrl
cd libvctrl
cargo build -p libvctrl_core
```

### 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.

```rust
use libvctrl_handler::{Blob, Encoder, Decoder};
use libvctrl_core::codec::{BinaryEncoder, BinaryDecoder};

// Construct a Blob
let blob = Blob::new(b"file content here".to_vec());

// Encode to binary
let encoder = BinaryEncoder;
let bytes = encoder.encode_blob(&blob).expect("encoding failed");

// Decode back to a Blob
let decoder = BinaryDecoder;
let decoded = decoder.decode_blob(&bytes).expect("decoding failed");

assert_eq!(decoded, blob);
```

### Hashing Objects

```rust
use libvctrl_handler::Hasher;
use libvctrl_core::hash::Sha512Hasher;

let hasher = Sha512Hasher;
let digest = hasher.hash(b"content to address");

// The digest is a 64-byte Hash value
assert_eq!(digest.as_bytes().len(), 64);
```

### Using the In-Memory Store

```rust
use libvctrl_handler::{Hasher, ObjectStore};
use libvctrl_core::hash::Sha512Hasher;
use libvctrl_core::store::MemoryStore;
use std::io::Cursor;

let store = MemoryStore::new();
let hasher = Sha512Hasher;

// Compute the content hash
let data = b"stored content";
let hash = hasher.hash(data);

// Put the object into the store
let mut reader = Cursor::new(data.to_vec());
store.put(&hash, &mut reader).expect("put failed");

// Retrieve the object
let mut retrieved = store.get(&hash).expect("get failed");
let mut buf = Vec::new();
retrieved.read_to_end(&mut buf).expect("read failed");
assert_eq!(buf, data);
```

### Validating Names

```rust
use libvctrl_core::validate::validate_name;

// Valid name
assert!(validate_name("src/main.rs").is_ok());

// Path traversal attack is rejected
assert!(validate_name("../../etc/passwd").is_err());
```

## 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**

```text
Offset  Size    Field
0       1       VERSION (u8, always 2)
1       8       data_len (u64 LE)
9       N       data (N = data_len bytes)
```

**Tree**

```text
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**

```text
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**

```text
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:

```bash
# Run all tests for this crate
cargo test -p libvctrl_core

# Run with output for individual tests
cargo test -p libvctrl_core -- --nocapture

# Run only encode/decode round-trip tests
cargo test -p libvctrl_core round_trip
```

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 exceeding `MAX_MESSAGE_LENGTH`, trees exceeding `MAX_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 in `MemoryRefStore`.

## Contributing

Contributions are welcome. Please adhere to the following standards:

1. **No unsafe code.** The crate forbids it at the compiler level. Do not attempt to add `unsafe` blocks.
2. **Clippy compliance.** All contributions must pass `cargo clippy -- -D warnings` with the project's lint configuration (`clippy::pedantic`, `clippy::nursery`).
3. **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.
4. **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).
5. **Format.** Run `cargo fmt` before committing. CI will reject unformatted code.
6. **Wire format stability.** Changes to the binary wire format must bump the `VERSION` constant in both `binary_encoder.rs` and `binary_decoder.rs`. Never change the encoding of an existing version.
7. **Workspace consistency.** This crate lives in the `libvctrl` workspace. Ensure that changes to `libvctrl_core` do not break the contract signatures in `libvctrl_handler`. If a contract change is needed, update `libvctrl_handler` first and version both crates together.

### CI Checklist

Before opening a pull request, verify locally:

```bash
cargo fmt --check -p libvctrl_core
cargo clippy -p libvctrl_core -- -D warnings
cargo test -p libvctrl_core
cargo doc -p libvctrl_core --no-deps
```