Levels 1 through 9, multi-block streams, the full symbol map, multiple Huffman tables per block, and the legacy randomised block flag. Output is accepted by every BZip2 implementation, and their output is accepted here.
Bounded memory
The encoder and decoder each hold one block at a time. Streaming and whole-file entry points use the same fixed footprint whether the input is a kilobyte or a terabyte.
Explicit everything
Every entry point takes the allocator and the I/O it needs. No global allocator, no global file handles, no hidden state.
Honest errors
Truncated, corrupted and malformed streams are refused with an error that names what was wrong, rather than decoding into plausible-looking wrong bytes.
BZip2 trades speed for ratio. Text, source code, logs, database dumps and uncompressed containers all compress considerably better with it than with a faster format, at the cost of taking longer. If throughput matters more than output size, a different format is the better tool.
Use the level to trade between the two. The level is the block size in units of 100 kB, so level 1 codes 100 kB blocks and level 9 codes 900 kB ones.
Level 1
Level 9
Block size
100 kB
900 kB
Compression
faster
slower
Ratio
slightly worse
slightly better
Intermediate levels are often the right answer, because the block-sorting stage dominates the cost and the difference between the extremes is usually small.
Streams are validated structurally before use, and both the block and stream CRCs are verified before a result is returned. For anything fetched from outside the process, also cap the output:
A short stream can expand into a much larger one, and the cap turns that into error.OutputTooLarge instead of an allocation whose size came from the sender.
This is a from-scratch, native Zig implementation of the BZip2 format. The format specification and the reference implementation used for this project is libarchive/bzip2.