···1717zerocopy = { version = "0.8.38", features = ["std", "derive"] }
1818zstd = "0.13.3"
1919clap = { version = "4.5", features = ["derive"], optional = true }
2020+fs2 = "0.4.3"
20212122[features]
2223default = ["cli"]
+15
LICENSE.md
···11+ISC License
22+33+Copyright (c) 2026, Zach Shipko
44+55+Permission to use, copy, modify, and/or distribute this software for any
66+purpose with or without fee is hereby granted, provided that the above
77+copyright notice and this permission notice appear in all copies.
88+99+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+4
README.md
···11+# bindle-file
22+33+`bindle` is a general purpose, append only archive file format
44+
+83
SPEC.md
···11+# Bindle File Format (.bdnl)
22+33+Bindle is a simple append-only binary archive format. It features a trailing index to support efficient writes and memory-mapped reads.
44+55+---
66+77+## 1. High-Level Layout
88+99+The file contains an 8 byte signature, followed by the data and then the metadata map at the end of the file.
1010+1111+| 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 - 20` | **Footer** | Pointer to the index and file count. |
1717+1818+---
1919+2020+## 2. Components
2121+2222+### 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`)
2525+2626+### 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)**.
3030+3131+### 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.
3333+3434+| Field | Size | Type | Description |
3535+| :--- | :--- | :--- | :--- |
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+4444+**Padding:** After the filename string, the file MUST be padded with null bytes until the next 8-byte boundary is reached.
4545+4646+### 2.4 Footer
4747+The last 20 bytes of the file contain the lookup information required to parse the archive.
4848+4949+| Field | Size | Type | Description |
5050+| :--- | :--- | :--- | :--- |
5151+| `index_offset` | 8 bytes | u64 | Absolute offset to the start of the Index. |
5252+| `entry_count` | 4 bytes | u32 | Total number of entries in the file. |
5353+5454+---
5555+5656+## 3. Implementation Guidelines
5757+5858+### 3.1 Reading Logic
5959+To read a Bindle file:
6060+1. Validate the file size (must be at least 28 bytes).
6161+2. Read the first 8 bytes and the last 8 bytes to verify the `BINDL001` magic.
6262+3. Read the `index_offset` from the footer (EOF - 20).
6363+4. Seek to `index_offset` and iterate `entry_count` times to populate an in-memory map of files.
6464+6565+### 3.2 Writing Logic (Atomic Updates)
6666+To maintain an append-only structure:
6767+1. Seek to the `index_offset` found in the current footer (effectively overwriting the old index).
6868+2. Append new data blobs.
6969+3. Write a new Index containing all previous entries plus the new ones.
7070+4. Write a new Footer.
7171+5. Flush/Sync the file to disk.
7272+7373+### 3.3 Constraints
7474+- **Unique Keys:** Duplicate filenames are not permitted.
7575+- **Null Bytes:** Filenames MUST NOT contain internal null bytes (`\0`).
7676+- **Maximum Size:** File offsets are 64-bit, supporting archives up to 16 Exabytes.
7777+7878+---
7979+8080+## 4. Design Rationale
8181+- **Trailing Index:** Allows files to be "updated" or added by simply appending to the end of the file and writing a new index.
8282+- **Alignment:** 8-byte alignment ensures that `u64` fields can be read directly from a memory-mapped pointer without unaligned access penalties on modern CPUs.
8383+- **Zero-Copy:** Raw entries can be used directly as slices from memory without decompression or copying.