···11-# Bindle File Format (.bndl)
11+# Bindle File Format
2233-Bindle is a simple append-only binary archive format. It features a trailing index to support efficient writes and memory-mapped reads.
33+Bindle is an append-only binary archive format designed for efficient writes, logical updates via shadowing, and memory-mapped reads.
4455---
6677## 1. High-Level Layout
8899-The file contains an 8 byte signature, followed by the data and then the metadata map at the end of the file.
99+A Bindle file consists of a fixed header, a data payload area, a trailing index, and a fixed-size footer.
10101111| Offset | Component | Description |
1212| :--- | :--- | :--- |
1313-| `0x00` | **Header** | 8-byte magic identification string. |
1414-| `0x08` | **Data Payload** | Sequential blobs of raw or compressed data. |
1515-| `Variable` | **Index** | A sequence of metadata entries and filenames. |
1616-| `EOF - 16` | **Footer** | Pointer to the index and file count. |
1313+| `0x00` | **Header** | 8-byte magic identification string |
1414+| `0x08` | **Data Payload** | Sequential blobs of raw or compressed data |
1515+| `Variable` | **Index** | Sequence of `Entry` headers and filenames |
1616+| `EOF - 16` | **Footer** | 16-byte tail containing the Index pointer and count |
17171818---
19192020-## 2. Components
2020+## 2. Component Details
21212222### 2.1 Header
2323-Every Bindle file MUST begin with the following 8 bytes:
2424-`42 49 4e 44 4c 30 30 31` (ASCII: `BINDL001`)
2323+Every Bindle file MUST begin with exactly 8 bytes:
2424+`42 49 4e 44 4c 30 30 31` (ASCII: `BINDL001`).
25252626### 2.2 Data Segment
2727-Data blobs are stored starting at offset `0x08`.
2828-- Each blob SHOULD be aligned to an **8-byte boundary** to ensure optimal performance when memory-mapping the file.
2929-- Data can be stored as-is (Raw) or compressed using **Zstandard (zstd)**.
2727+Data blobs begin at offset `0x08`.
2828+- **Alignment:** Every data blob MUST be padded with null bytes to an **8-byte boundary**.
2929+- **Compression:** Blobs may be raw or compressed via Zstd.
3030+- **Shadowing:** New versions of existing files are simply appended to the end of the data segment. The file remains append-only until a vacuum operation is performed.
30313131-### 2.3 Index Entry (`Entry`)
3232-The index consists of a series of entries. Each entry is a fixed-size header followed immediately by a variable-length UTF-8 filename.
3232+### 2.3 Index Entry
3333+The index is a series of entries. Each entry consists of a fixed metadata block followed by a variable-length filename.
33343435| Field | Size | Type | Description |
3536| :--- | :--- | :--- | :--- |
3636-| `offset` | 8 bytes | u64 | Absolute file offset to start of data. |
3737-| `c_size` | 8 bytes | u64 | Compressed size on disk. |
3838-| `u_size` | 8 bytes | u64 | Original uncompressed size. |
3939-| `crc32` | 4 bytes | u32 | Checksum of the stored data. |
4040-| `name_len` | 2 bytes | u16 | Length of the following filename string. |
4141-| `comp_type` | 1 byte | u8 | `0` = Raw, `1` = Zstd. |
4242-| `reserved` | 1 byte | u8 | Alignment padding. |
4343-| `filename` | variable | utf8 | Filename string
3737+| `offset` | 8 bytes | u64 | Absolute file offset to the data blob |
3838+| `c_size` | 8 bytes | u64 | Compressed size on disk |
3939+| `u_size` | 8 bytes | u64 | Original uncompressed size |
4040+| `crc32` | 4 bytes | u32 | Checksum of the stored data |
4141+| `name_len` | 2 bytes | u16 | Length of the filename string |
4242+| `comp_type` | 1 byte | u8 | `0` = Raw, `1` = Zstandard |
4343+| `reserved` | 1 byte | u8 | Alignment padding |
4444+| `filename` | Variable | UTF-8 | The entry name |
44454545-**Padding:** After the filename string, the file MUST be padded with null bytes until the next 8-byte boundary is reached.
4646+**Padding:** After the filename, the file MUST be padded with null bytes (`\0`) to the next 8-byte boundary before the next entry begins.
46474748### 2.4 Footer
4848-The last 16 bytes of the file contain the lookup information required to parse the archive.
4949+The last 16 bytes of the file are used to locate the index. Both fields are stored in little-endian format.
49505051| Field | Size | Type | Description |
5152| :--- | :--- | :--- | :--- |
5252-| `index_offset` | 8 bytes | u64 | Absolute offset to the start of the Index. |
5353-| `entry_count` | 8 bytes | u32 | Total number of entries in the file. |
5353+| `index_offset` | 8 bytes | u64 | Absolute offset to the start of the index |
5454+| `entry_count` | 8 bytes | u64 | Total number of unique entries in the index |
54555556---
56575757-## 3. Implementation Guidelines
5858+## 3. Operational Logic
58595959-### 3.1 Reading Logic
6060-To read a Bindle file:
6161-1. Validate the file size (must be at least 28 bytes).
6262-2. Read the first 8 bytes and the last 8 bytes to verify the `BINDL001` magic.
6363-3. Read the `index_offset` from the footer (EOF - 16).
6464-4. Seek to `index_offset` and iterate `entry_count` times to populate an in-memory map of files.
6060+### 3.1 Shadowing & Atomic Updates
6161+To "update" a file or add new ones:
6262+1. Append new data starting at the current `index_offset`.
6363+2. Write a new Index. If a filename is repeated, the index points to the **newest** data offset.
6464+3. Write a new Footer.
6565+4. Old data remains in the file (unreferenced) until a vacuum occurs.
65666666-### 3.2 Writing Logic (Atomic Updates)
6767-To maintain an append-only structure:
6868-1. Seek to the `index_offset` found in the current footer (effectively overwriting the old index).
6969-2. Append new data blobs.
7070-3. Write a new Index containing all previous entries plus the new ones.
7171-4. Write a new Footer.
7272-5. Flush/Sync the file to disk.
7373-7474-### 3.3 Constraints
7575-- **Unique Keys:** Duplicate filenames are not permitted.
7676-- **Null Bytes:** Filenames MUST NOT contain internal null bytes (`\0`).
7777-- **Maximum Size:** File offsets are 64-bit, supporting archives up to 16 Exabytes.
6767+### 3.2 Vacuuming
6868+To reclaim space used by shadowed data:
6969+1. Create a temporary file and write the `BINDL001` header.
7070+2. Iterate through the **live** index entries only.
7171+3. Copy the referenced data blobs to the new file, updating their offsets in a new in-memory index.
7272+4. Write the new Index and Footer to the temporary file.
7373+5. Atomically replace the old file with the new one.
78747975---
80768177## 4. Design Rationale
8282-- **Trailing Index:** Allows files to be "updated" or added by simply appending to the end of the file and writing a new index.
8383-- **Alignment:** 8-byte alignment ensures that `u64` fields can be read directly from a memory-mapped pointer without unaligned access penalties on modern CPUs.
8484-- **Zero-Copy:** Raw entries can be used directly as slices from memory without decompression or copying.
7878+- **Trailing Index:** Enables "single-pass" appending. You don't need to shift existing data to grow the index.
7979+- **8-Byte Alignment:** Ensures that all 64-bit integers in the metadata and footer are naturally aligned, preventing performance penalties on architectures that dislike unaligned reads.
8080+- **Zero-Copy Potential:** Raw (uncompressed) data blobs can be used directly as memory slices via `mmap` without intermediate buffers.