{"id":51130139,"url":"https://github.com/williamwutq/bblock","last_synced_at":"2026-06-25T11:30:50.139Z","repository":{"id":354105820,"uuid":"1222176287","full_name":"williamwutq/bblock","owner":"williamwutq","description":"Persistent checksummed blocks built on top of bstack's allocators","archived":false,"fork":false,"pushed_at":"2026-06-10T22:11:37.000Z","size":201,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T00:05:42.533Z","etag":null,"topics":["allocation","binary","block","data","data-structures","database","rust","rust-crate","rust-library","serialization"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/bblock","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/williamwutq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-27T05:43:31.000Z","updated_at":"2026-06-10T22:11:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/williamwutq/bblock","commit_stats":null,"previous_names":["williamwutq/bblock"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/williamwutq/bblock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamwutq%2Fbblock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamwutq%2Fbblock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamwutq%2Fbblock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamwutq%2Fbblock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/williamwutq","download_url":"https://codeload.github.com/williamwutq/bblock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamwutq%2Fbblock/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34773841,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["allocation","binary","block","data","data-structures","database","rust","rust-crate","rust-library","serialization"],"created_at":"2026-06-25T11:30:49.296Z","updated_at":"2026-06-25T11:30:50.133Z","avatar_url":"https://github.com/williamwutq.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bblock\n\n`bblock` wraps [`bstack`](https://crates.io/crates/bstack) allocators to provide\npersistent, checksummed blocks. Two checksum strategies are available: **CRC32**\nfor stronger integrity guarantees and **XOR** for faster incremental updates.\nEvery allocation carries a 4-byte checksum trailer; `verify()` tells you at any\ntime whether the stored bytes match the checksum.\n\n[![Crates.io](https://img.shields.io/crates/v/bblock)](https://crates.io/crates/bblock)\n[![Docs.rs](https://img.shields.io/docsrs/bblock)](https://docs.rs/bblock)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)\n\n---\n\n## Features\n\n- **Transparent checksumming** — allocate as usual; the 4-byte checksum trailer is\n  managed automatically by the safe API.\n- **Two checksum strategies** — CRC32 (`crc` module / crate root) for stronger\n  error detection; XOR (`xor` module) for faster incremental updates on writes.\n- **Composability** — both allocator wrappers implement `BStackAllocator`\n  themselves, so they can be stacked. `BXorBlockAllocator\u003cBCrcBlockAllocator\u003cA\u003e\u003e`\n  gives XOR-checksummed allocations where each inner slot is also CRC32-protected.\n- **`guarded` feature** — `BCrcBlock` and `BXorBlock` implement\n  `bstack::BStackGuardedSlice`. `as_slice()` hides the checksum trailer;\n  `write()` and `zero()` keep the checksum consistent automatically.\n- **Sub-range views** — `BCrcBlockView::subview(start, end)` lets you operate on a\n  named field of a record; writes still update the full-block checksum.\n- **Cursor-based I/O** — `BCrcBlockReader` and `BCrcBlockWriter` implement\n  `io::Read`/`io::Write`/`io::Seek` with the same checksum guarantees.\n- **Allocator-agnostic** — `BCrcBlockAllocator\u003cA\u003e` works with any\n  `A: BStackAllocator`; no concrete allocator is imported by this crate.\n- **LZMA2 compression** — `compress::BLZMA2BlockAllocator` transparently\n  compresses every allocation, with an automatic raw-storage fallback for\n  incompressible data.\n\n---\n\n## Quick start\n\nAdd to `Cargo.toml`:\n\n```toml\n[dependencies]\nbblock = \"0.2\"\nbstack = { version = \"0.2\", features = [\"alloc\", \"set\", \"guarded\"] }\n```\n\n```rust,no_run\nuse bstack::{BStack, BStackGuardedSlice, LinearBStackAllocator};\nuse bblock::BCrcBlockAllocator;\n\n// Open (or create) a bstack file and wrap the allocator.\nlet stack = BStack::open(\"data.bstk\").unwrap();\nlet alloc = BCrcBlockAllocator::new(LinearBStackAllocator::new(stack));\n\n// Allocate a 16-byte block.  On disk it occupies 20 bytes (16 + 4 checksum).\nlet block = alloc.alloc(16).unwrap();\nlet view = block.view();\n\nview.write(b\"hello, bblock!!!\").unwrap();\nassert!(view.verify().unwrap()); // checksum is valid\n\n// Sub-range views update the checksum automatically.\nview.subview(0, 5).write(b\"world\").unwrap();\nassert!(block.verify().unwrap()); // still valid; full CRC32 was recomputed\n\n// Cursor-based writer.\nuse std::io::Write;\nlet mut w = block.writer();\nw.write_all(b\"cursor  write!!!\").unwrap();\nassert!(block.verify().unwrap());\n```\n\n---\n\n## XOR checksum (faster writes)\n\nXOR types are available at the crate root and in the `xor` module.\n\n```rust,no_run\nuse bstack::{BStack, BStackGuardedSlice, LinearBStackAllocator};\nuse bblock::BXorBlockAllocator;\n\n// Open (or create) a bstack file and wrap the allocator.\nlet stack = BStack::open(\"data.bstk\").unwrap();\nlet alloc = BXorBlockAllocator::new(LinearBStackAllocator::new(stack));\n\n// Allocate a 16-byte block.  On disk it occupies 20 bytes (16 + 4 checksum).\nlet block = alloc.alloc(16).unwrap();\nlet view = block.view();\n\nview.write(b\"hello, bblock!!!\").unwrap();\nassert!(view.verify().unwrap()); // checksum is valid\n\n// Subview writes update the checksum incrementally (reads only changed bytes).\nview.subview(0, 5).write(b\"world\").unwrap();\nassert!(block.verify().unwrap()); // still valid; checksum updated incrementally\n\n// Cursor-based writer.\nuse std::io::Write;\nlet mut w = block.writer();\nw.write_all(b\"cursor  write!!!\").unwrap();\nassert!(block.verify().unwrap());\n```\n\n---\n\n## Compression (LZMA2)\n\n`compress::BLZMA2BlockAllocator` transparently compresses every allocation\nwith LZMA2. Since compressed size is data-dependent, `alloc(n)` declares an\non-disk payload capacity `n` and reserves `n + LZMA2_OVERHEAD` (13) bytes on\ndisk. A write is stored compressed if the compressed stream fits in `n`\nbytes, otherwise stored raw if the plaintext itself fits in `n` bytes,\notherwise the write fails.\n\n```rust,no_run\nuse bstack::{BStack, BStackAllocator, BStackGuardedSlice, LinearBStackAllocator};\nuse bblock::compress::BLZMA2BlockAllocator;\nuse std::io::Write;\n\n// Open (or create) a bstack file and wrap the allocator.\n// `6` is the LZMA2 compression preset (0-9).\nlet stack = BStack::open(\"data.bstk\").unwrap();\nlet alloc = BLZMA2BlockAllocator::new(LinearBStackAllocator::new(stack), 6);\n\n// Allocate a block with 256 bytes of on-disk payload capacity.\n// On disk it occupies 256 + 13 = 269 bytes.\nlet block = alloc.alloc(256).unwrap();\n\n// Writes are compressed transparently and verified on read.\nblock.write(b\"hello, compressed bblock!\").unwrap();\nassert!(block.verify().unwrap());\nassert_eq!(block.read().unwrap(), b\"hello, compressed bblock!\");\n\n// The buffered writer compresses on flush/drop and does not silently\n// truncate; it errors if the data cannot fit even after compression.\nlet mut w = block.writer().unwrap();\nw.write_all(b\"more data, compressed on flush\").unwrap();\nw.flush().unwrap();\n```\n\n`BLZMA2Block` implements `bstack::BStackGuardedSlice`: `read()`/`write()`\ntransparently decompress/compress, `len()` returns `n`, and `as_slice()` is\nunsupported (compressed bytes have no safe plaintext mapping). Unlike\n`BStackGuardedSlice::write`, which truncates its input to `n` bytes before\ncompression, `BLZMA2BlockWriter` accepts writes larger than `n` as long as\nthey compress to fit within `n`.\n\n---\n\n## Composability\n\nBoth allocator wrappers implement `BStackAllocator` themselves, so they can be\npassed to any generic API that accepts `T: BStackAllocator`. This is what\nallows `BCrcBlock` and `BXorBlock` to implement `BStackGuardedSlice` without\nrequiring the stricter `BStackSliceAllocator` bound. The wrappers can also be\nstacked inside each other:\n\n```rust,no_run\nuse bstack::{BStack, LinearBStackAllocator};\nuse bblock::{BCrcBlockAllocator, BXorBlockAllocator};\n\nlet stack = BStack::open(\"data.bstk\").unwrap();\n// XOR checksum over CRC32-checksummed blocks\nlet alloc = BXorBlockAllocator::new(BCrcBlockAllocator::new(LinearBStackAllocator::new(stack)));\n```\n\n---\n\n## bstack `guarded` feature\n\nWhen bstack is built with the `guarded` feature (enabled by default in this\ncrate's `Cargo.toml`), all four concrete types implement\n`bstack::BStackGuardedSlice`: `BCrcBlock`, `BCrcBlockView`, `BXorBlock`, and\n`BXorBlockView`. The view types additionally implement\n`bstack::BStackGuardedSliceSubview`.\n\n* `as_slice()` returns the data region only (the checksum trailer is hidden;\n  for views, only the view's sub-range is exposed).\n* `write()` and `zero()` keep the checksum consistent automatically.\n  `BCrcBlock`/`BCrcBlockView` recompute the full CRC32; `BXorBlock`/`BXorBlockView`\n  update incrementally.\n* `len()`, `is_empty()` (block types) and `len()`, `is_empty()`, `read()`,\n  `write()`, `zero()` (view types) are provided by the trait and require\n  `use bstack::BStackGuardedSlice` to be in scope.\n\n---\n\n## API overview\n\n| Type                  | Description                                                          |\n|-----------------------|----------------------------------------------------------------------|\n| `BCrcBlockAllocator\u003cA\u003e`  | Wraps `A: BStackAllocator`; `alloc`, `realloc`, `dealloc`            |\n| `BCrcBlock\u003c'a, A\u003e`       | Checksummed block handle; `Copy`; source of views and cursors        |\n| `BCrcBlockView\u003c'a, A\u003e`   | Safe read/write window; supports `subview`                           |\n| `BCrcBlockReader\u003c'a, A\u003e` | `io::Read + io::Seek` over the view's data range                     |\n| `BCrcBlockWriter\u003c'a, A\u003e` | `io::Write + io::Seek`; recomputes full CRC32 after every write      |\n| `CHECKSUM_LENGTH`     | `4` — the CRC32 trailer size in bytes                                |\n\n### XOR module types (also re-exported at crate root)\n\n| Type                     | Description                                                          |\n|--------------------------|----------------------------------------------------------------------|\n| `BXorBlockAllocator\u003cA\u003e`  | Wraps `A: BStackAllocator`; `alloc`, `realloc`, `dealloc`            |\n| `BXorBlock\u003c'a, A\u003e`       | Checksummed block handle; `Copy`; source of views and cursors        |\n| `BXorBlockView\u003c'a, A\u003e`   | Safe read/write window; supports `subview`                           |\n| `BXorBlockReader\u003c'a, A\u003e` | `io::Read + io::Seek` over the view's data range                     |\n| `BXorBlockWriter\u003c'a, A\u003e` | `io::Write + io::Seek`; updates XOR checksum incrementally           |\n| `xor::CHECKSUM_LENGTH`   | `4` — the XOR checksum trailer size in bytes                         |\n\nBoth CRC and XOR block types expose the same API shape. Substitute `BXorBlock` /\n`BXorBlockView` / `BXorBlockReader` / `BXorBlockWriter` for the CRC32 variants.\nThe only behavioural difference is that XOR checksum updates are incremental.\n\n### Compression module types (`compress::lzma2`)\n\n| Type                      | Description                                                          |\n|---------------------------|----------------------------------------------------------------------|\n| `BLZMA2BlockAllocator\u003cA\u003e` | Wraps `A: BStackAllocator`; `alloc(n)` reserves `n + LZMA2_OVERHEAD` bytes |\n| `BLZMA2Block\u003c'a, A\u003e`      | Compressed block handle; `Copy`; source of readers and writers      |\n| `BLZMA2BlockReader\u003c'a, A\u003e`| `io::Read + io::Seek` over the decompressed plaintext                |\n| `BLZMA2BlockWriter\u003c'a, A\u003e`| `io::Write + io::Seek`; recompresses on flush, falling back to raw   |\n| `LZMA2_OVERHEAD`          | `13` — the per-block header size in bytes                            |\n\n### `BCrcBlock\u003c'a, A\u003e` / `BXorBlock\u003c'a, A\u003e`\n\n```rust\nimpl\u003c'a, A: BStackAllocator\u003e BCrcBlock\u003c'a, A\u003e {       // same for BXorBlock\n    // Serialisation\n    pub fn to_bytes(\u0026self) -\u003e [u8; 16];\n    pub fn from_bytes(allocator: \u0026'a A, bytes: [u8; 16]) -\u003e Self;\n\n    // Integrity\n    pub fn checksum(\u0026self) -\u003e io::Result\u003cu32\u003e;\n    pub fn verify(\u0026self) -\u003e io::Result\u003cbool\u003e;\n\n    // Safe access\n    pub fn view(\u0026self) -\u003e BCrcBlockView\u003c'a, A\u003e;\n    pub fn reader(\u0026self) -\u003e BCrcBlockReader\u003c'a, A\u003e;\n    pub fn reader_at(\u0026self, offset: u64) -\u003e BCrcBlockReader\u003c'a, A\u003e;\n    pub fn writer(\u0026self) -\u003e BCrcBlockWriter\u003c'a, A\u003e;\n    pub fn writer_at(\u0026self, offset: u64) -\u003e BCrcBlockWriter\u003c'a, A\u003e;\n\n    // Unsafe escape hatch — checksum is no longer tracked\n    pub unsafe fn into_slice(self) -\u003e BStackSlice\u003c'a, A\u003e;\n}\n\n// requires `use bstack::BStackGuardedSlice`\nimpl\u003c'a, A: BStackAllocator\u003e BStackGuardedSlice\u003c'a, A\u003e for BCrcBlock\u003c'a, A\u003e {\n    fn len(\u0026self) -\u003e u64;\n    fn is_empty(\u0026self) -\u003e bool;\n    fn as_slice(\u0026self) -\u003e io::Result\u003cBStackSlice\u003c'a, A\u003e\u003e;\n    fn read(\u0026self) -\u003e io::Result\u003cVec\u003cu8\u003e\u003e;\n    fn write(\u0026self, data: impl AsRef\u003c[u8]\u003e) -\u003e io::Result\u003c()\u003e;\n    fn zero(\u0026self) -\u003e io::Result\u003c()\u003e;\n}\n```\n\n### `BCrcBlockView\u003c'a, A\u003e` / `BXorBlockView\u003c'a, A\u003e`\n\n```rust\nimpl\u003c'a, A: BStackAllocator\u003e BCrcBlockView\u003c'a, A\u003e {   // same for BXorBlockView\n    pub fn new(block: \u0026BCrcBlock\u003c'a, A\u003e) -\u003e Self;\n\n    // Sub-range — coordinates are relative to this view's start\n    pub fn subview(\u0026self, start: u64, end: u64) -\u003e Self;\n\n    // Read (from this view's range)\n    pub fn read_into(\u0026self, buf: \u0026mut [u8]) -\u003e io::Result\u003c()\u003e;\n    pub fn read_range_into(\u0026self, start: u64, buf: \u0026mut [u8]) -\u003e io::Result\u003c()\u003e;\n\n    // Write (to this view's range; always recomputes the full-block checksum)\n    pub fn write_range(\u0026self, start: u64, data: \u0026[u8]) -\u003e io::Result\u003c()\u003e;\n    pub fn zero_range(\u0026self, start: u64, n: u64) -\u003e io::Result\u003c()\u003e;\n\n    // Integrity (always over the full block, not just this view's range)\n    pub fn checksum(\u0026self) -\u003e io::Result\u003cu32\u003e;\n    pub fn verify(\u0026self) -\u003e io::Result\u003cbool\u003e;\n\n    // Cursors\n    pub fn reader(\u0026self) -\u003e BCrcBlockReader\u003c'a, A\u003e;\n    pub fn reader_at(\u0026self, offset: u64) -\u003e BCrcBlockReader\u003c'a, A\u003e;\n    pub fn writer(\u0026self) -\u003e BCrcBlockWriter\u003c'a, A\u003e;\n    pub fn writer_at(\u0026self, offset: u64) -\u003e BCrcBlockWriter\u003c'a, A\u003e;\n}\n\n// requires `use bstack::BStackGuardedSlice`\nimpl\u003c'a, A: BStackAllocator\u003e BStackGuardedSlice\u003c'a, A\u003e for BCrcBlockView\u003c'a, A\u003e {\n    fn len(\u0026self) -\u003e u64;\n    fn is_empty(\u0026self) -\u003e bool;\n    fn as_slice(\u0026self) -\u003e io::Result\u003cBStackSlice\u003c'a, A\u003e\u003e;\n    fn read(\u0026self) -\u003e io::Result\u003cVec\u003cu8\u003e\u003e;\n    fn write(\u0026self, data: impl AsRef\u003c[u8]\u003e) -\u003e io::Result\u003c()\u003e;\n    fn zero(\u0026self) -\u003e io::Result\u003c()\u003e;\n}\n\n// requires `use bstack::BStackGuardedSliceSubview`\nimpl\u003c'a, A: BStackAllocator\u003e BStackGuardedSliceSubview\u003c'a, A\u003e for BCrcBlockView\u003c'a, A\u003e {\n    fn subview(\u0026self, start: u64, end: u64) -\u003e impl BStackGuardedSliceSubview\u003c'a, A\u003e;\n    fn subview_range(\u0026self, range: Range\u003cu64\u003e) -\u003e impl BStackGuardedSliceSubview\u003c'a, A\u003e;\n}\n```\n\n---\n\n## Limitations and caveats\n\n**This crate detects corruption; it does not repair it.**  \n`verify()` returning `false` means the data should not be trusted, but `bblock`\nprovides no mechanism to restore a previous good value.\n\n**Checksumming is not part of the allocator's recovery strategy.**  \n`bstack`'s crash recovery operates on committed-length metadata, independently\nof `bblock`'s checksums. The recovery strategies of different allocators vary.\nIf you need checksum-based recovery baked into the allocator itself, use an\nallocator that natively supports it.\n\n**`unsafe` code, direct `bstack` writes, and buggy allocators are not covered.**  \nThe checksum is maintained only when you write through the safe API\n(`BCrcBlockView`, `BCrcBlockWriter`). Writing through a raw `BStackSlice` from\n`BCrcBlock::into_slice`, using bstack directly on the same region, or relying on\na buggy allocator will all produce stale or incorrect checksums.\n\n**If the checksum itself is corrupted, `verify()` cannot help you.**  \nCRC32 catches the vast majority of real-world corruption scenarios, but it is\nnot a cryptographic guarantee. If both data and checksum are overwritten\nconsistently (e.g., a device returning all-zeros), `verify()` may return `true`\nfor corrupted data. For applications requiring strong consistency guarantees,\nchecksums are a useful building block but are not a substitute for write-ahead\nlogs, copy-on-write, two-phase commit, or other proper recovery strategies.\n\n**Avoid double-wrapping small blocks.**  \nEmbedding a serialised `BCrcBlock` reference (16 bytes) inside another `BCrcBlock`\nis valid, but the 4-byte checksum overhead is proportionally significant for\nsmall payloads. Prefer coarser-grained checksumming for small structures.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamwutq%2Fbblock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwilliamwutq%2Fbblock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamwutq%2Fbblock/lists"}