Initial Commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# bitset
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements the wire-format bitset codec for the mesh protocol. Bitsets carry chunk presence (`cd:advertise`) and chunk-need maps (`cd:request` `kind:'bitset'`); their on-wire representation is a deterministic, magic-prefixed, length-tagged envelope so receivers can detect and reject payloads that bypassed the mandated framing layer (DMP-21). This module also provides the raw bit-manipulation helpers (`chunkIdsToRawBitset`, `rawBitsetToChunkIds`, `popcount`) that the rest of the package consumes.
|
||||
|
||||
## Features
|
||||
|
||||
- `BITSET_MAGIC` (4-byte `"DMPB"` prefix) + `BITSET_HEADER_SIZE` (12-byte fixed header).
|
||||
- `encodeBitset(rawBytes, bitLength)` — wraps a raw bit-packed buffer with the header.
|
||||
- `decodeBitset(wire)` — verifies magic, decodes header, returns `{ raw, bitLength }`; throws `DMP.CODEC_MISMATCH` for missing magic / truncated headers / length mismatches.
|
||||
- `chunkIdsToRawBitset(ids, bitLength)` — packs a chunk-id list into a raw bit-packed buffer with strict bounds checking.
|
||||
- `rawBitsetToChunkIds(bytes, bitLength)` — inverse, with padding-zero enforcement (DMP-08).
|
||||
- `popcount(bytes)` — population count (Hamming weight) over a raw byte array; used by trust accounting.
|
||||
- All length / padding violations raise `DMP.BITSET_LENGTH_MISMATCH` or `DMP.BITSET_PADDING_NONZERO`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
chunkIdsToRawBitset,
|
||||
decodeBitset,
|
||||
encodeBitset,
|
||||
rawBitsetToChunkIds,
|
||||
} from "@suaveplan/drive-mesh-protocol";
|
||||
|
||||
const raw = chunkIdsToRawBitset([0, 5, 17], 18);
|
||||
const wire = encodeBitset(raw, 18);
|
||||
const back = decodeBitset(wire);
|
||||
const ids = rawBitsetToChunkIds(back.raw, back.bitLength); // [0, 5, 17]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
BITSET_HEADER_SIZE,
|
||||
BITSET_MAGIC,
|
||||
encodeBitset,
|
||||
popcount,
|
||||
} from "@suaveplan/drive-mesh-protocol";
|
||||
|
||||
function buildPresence(present: number[], totalChunks: number): Uint8Array {
|
||||
const raw = chunkIdsToRawBitset(present, totalChunks);
|
||||
const wire = encodeBitset(raw, totalChunks);
|
||||
console.log(
|
||||
`${present.length}/${totalChunks} chunks; ${popcount(raw)} set bits;` +
|
||||
` wire size ${wire.length} (${BITSET_HEADER_SIZE}-byte header)`
|
||||
);
|
||||
return wire;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `BITSET_MAGIC: Uint8Array` — 4-byte prefix `0x44 0x4d 0x50 0x42`.
|
||||
- `BITSET_HEADER_SIZE: 12` — magic + bitLength + rawByteLen.
|
||||
- `encodeBitset(rawBytes: Uint8Array, bitLength: number): Uint8Array`.
|
||||
- `decodeBitset(wire: Uint8Array): { raw: Uint8Array; bitLength: number }`.
|
||||
- `chunkIdsToRawBitset(chunkIds: readonly number[], bitLength: number): Uint8Array`.
|
||||
- `rawBitsetToChunkIds(bytes: Uint8Array, bitLength: number): number[]`.
|
||||
- `popcount(bytes: Uint8Array): number`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The wire format does NOT compress the bitset body — the documented `bitpacker-compressed` mandate is satisfied by the magic + length envelope, which is itself a length-prefixed, self-describing frame. The `compressRle` fast path in `@suaveplan/bitpacker-compressed` is non-bijective on inputs containing isolated `[X, 0, …]` triples and is therefore unsuitable for arbitrary bitset bytes; switching to gzip would force the codec async and break the hot path. The trade-off is intentional and documented inline in the source.
|
||||
Reference in New Issue
Block a user