bblock
bblock wraps bstack allocators to provide
persistent, checksummed blocks. Two checksum strategies are available: CRC32
for stronger integrity guarantees and XOR for faster incremental updates.
Every allocation carries a 4-byte checksum trailer; verify() tells you at any
time whether the stored bytes match the checksum.
Features
- Transparent checksumming — allocate as usual; the 4-byte checksum trailer is managed automatically by the safe API.
- Two checksum strategies — CRC32 (
crcmodule / crate root) for stronger error detection; XOR (xormodule) for faster incremental updates on writes. - Composability — both allocator wrappers implement
BStackAllocatorthemselves, so they can be stacked.BXorBlockAllocator<BBlockAllocator<A>>gives XOR-checksummed allocations where each inner slot is also CRC32-protected. guardedfeature —BBlockandBXorBlockimplementbstack::BStackGuardedSlice.as_slice()hides the checksum trailer;write()andzero()keep the checksum consistent automatically.- Sub-range views —
BBlockView::subview(start, end)lets you operate on a named field of a record; writes still update the full-block checksum. - Cursor-based I/O —
BBlockReaderandBBlockWriterimplementio::Read/io::Write/io::Seekwith the same checksum guarantees. - Allocator-agnostic —
BBlockAllocator<A>works with anyA: BStackAllocator; no concrete allocator is imported by this crate.
Quick start
Add to Cargo.toml:
[]
= "0.2"
= { = "0.2", = ["alloc", "set", "guarded"] }
use ;
use BBlockAllocator;
// Open (or create) a bstack file and wrap the allocator.
let stack = open.unwrap;
let alloc = new;
// Allocate a 16-byte block. On disk it occupies 20 bytes (16 + 4 checksum).
let block = alloc.alloc.unwrap;
let view = block.view;
view.write.unwrap;
assert!; // checksum is valid
// Sub-range views update the checksum automatically.
view.subview.write.unwrap;
assert!; // still valid; full CRC32 was recomputed
// Cursor-based writer.
use Write;
let mut w = block.writer;
w.write_all.unwrap;
assert!;
XOR checksum (faster writes)
XOR types are available at the crate root and in the xor module.
use ;
use BXorBlockAllocator;
// Open (or create) a bstack file and wrap the allocator.
let stack = open.unwrap;
let alloc = new;
// Allocate a 16-byte block. On disk it occupies 20 bytes (16 + 4 checksum).
let block = alloc.alloc.unwrap;
let view = block.view;
view.write.unwrap;
assert!; // checksum is valid
// Subview writes update the checksum incrementally (reads only changed bytes).
view.subview.write.unwrap;
assert!; // still valid; checksum updated incrementally
// Cursor-based writer.
use Write;
let mut w = block.writer;
w.write_all.unwrap;
assert!;
Composability
Both allocator wrappers implement BStackAllocator themselves, so they can be
passed to any generic API that accepts T: BStackAllocator. This is what
allows BBlock and BXorBlock to implement BStackGuardedSlice without
requiring the stricter BStackSliceAllocator bound. The wrappers can also be
stacked inside each other:
use ;
use ;
let stack = open.unwrap;
// XOR checksum over CRC32-checksummed blocks
let alloc = new;
bstack guarded feature
When bstack is built with the guarded feature (enabled by default in this
crate's Cargo.toml), all four concrete types implement
bstack::BStackGuardedSlice: BBlock, BBlockView, BXorBlock, and
BXorBlockView. The view types additionally implement
bstack::BStackGuardedSliceSubview.
as_slice()returns the data region only (the checksum trailer is hidden; for views, only the view's sub-range is exposed).write()andzero()keep the checksum consistent automatically.BBlock/BBlockViewrecompute the full CRC32;BXorBlock/BXorBlockViewupdate incrementally.len(),is_empty()(block types) andlen(),is_empty(),read(),write(),zero()(view types) are provided by the trait and requireuse bstack::BStackGuardedSliceto be in scope.
API overview
| Type | Description |
|---|---|
BBlockAllocator<A> |
Wraps A: BStackAllocator; alloc, realloc, dealloc |
BBlock<'a, A> |
Checksummed block handle; Copy; source of views and cursors |
BBlockView<'a, A> |
Safe read/write window; supports subview |
BBlockReader<'a, A> |
io::Read + io::Seek over the view's data range |
BBlockWriter<'a, A> |
io::Write + io::Seek; recomputes full CRC32 after every write |
CHECKSUM_LENGTH |
4 — the CRC32 trailer size in bytes |
XOR module types (also re-exported at crate root)
| Type | Description |
|---|---|
BXorBlockAllocator<A> |
Wraps A: BStackAllocator; alloc, realloc, dealloc |
BXorBlock<'a, A> |
Checksummed block handle; Copy; source of views and cursors |
BXorBlockView<'a, A> |
Safe read/write window; supports subview |
BXorBlockReader<'a, A> |
io::Read + io::Seek over the view's data range |
BXorBlockWriter<'a, A> |
io::Write + io::Seek; updates XOR checksum incrementally |
xor::CHECKSUM_LENGTH |
4 — the XOR checksum trailer size in bytes |
Both CRC and XOR block types expose the same API shape. Substitute BXorBlock /
BXorBlockView / BXorBlockReader / BXorBlockWriter for the CRC32 variants.
The only behavioural difference is that XOR checksum updates are incremental.
BBlock<'a, A> / BXorBlock<'a, A>
// requires `use bstack::BStackGuardedSlice`
BBlockView<'a, A> / BXorBlockView<'a, A>
// requires `use bstack::BStackGuardedSlice`
// requires `use bstack::BStackGuardedSliceSubview`
Limitations and caveats
This crate detects corruption; it does not repair it.
verify() returning false means the data should not be trusted, but bblock
provides no mechanism to restore a previous good value.
Checksumming is not part of the allocator's recovery strategy.
bstack's crash recovery operates on committed-length metadata, independently
of bblock's checksums. The recovery strategies of different allocators vary.
If you need checksum-based recovery baked into the allocator itself, use an
allocator that natively supports it.
unsafe code, direct bstack writes, and buggy allocators are not covered.
The checksum is maintained only when you write through the safe API
(BBlockView, BBlockWriter). Writing through a raw BStackSlice from
BBlock::into_slice, using bstack directly on the same region, or relying on
a buggy allocator will all produce stale or incorrect checksums.
If the checksum itself is corrupted, verify() cannot help you.
CRC32 catches the vast majority of real-world corruption scenarios, but it is
not a cryptographic guarantee. If both data and checksum are overwritten
consistently (e.g., a device returning all-zeros), verify() may return true
for corrupted data. For applications requiring strong consistency guarantees,
checksums are a useful building block but are not a substitute for write-ahead
logs, copy-on-write, two-phase commit, or other proper recovery strategies.
Avoid double-wrapping small blocks.
Embedding a serialised BBlock reference (16 bytes) inside another BBlock
is valid, but the 4-byte checksum overhead is proportionally significant for
small payloads. Prefer coarser-grained checksumming for small structures.
License
MIT — see LICENSE.