pure-zstd/CHANGELOG.md
Tizian 「ティツィアーン」 57aa532069
Decode frames that declare no content size
Streaming encoders leave Frame_Content_Size out unless told the size up front,
and their readers often can't know it either: every zstd frame in an Anki
package (its collection, its media list, each media file) is written that way.
Such a frame needed the exact size passed as expectedSize, and anything else
was refused.

Now it gets the most its blocks can hold, Block_Maximum_Size (128 KiB) each,
found by walking the block headers alone, and the output is trimmed to what
the blocks held. The output is still allocated once. expectedSize keeps its
exact check when given, and maxOutputSize bounds the allocation as before.

Checked against libzstd (python-zstandard) on Anki 26.09.3's exports: a small
package's four frames and a 30 MB collection decode byte for byte the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 05:09:51 +02:00

1.7 KiB

Changelog

Unreleased

  • Decodes a frame that declares no content size without expectedSize, as streaming encoders write them (Anki's packages, for one): the output is allocated at the most the frame's blocks can hold, 128 KiB each, found from their headers, and trimmed to what they held. expectedSize still checks the size exactly when given.

1.0.0

First release.

A zstd decompressor in plain Dart. Decompression only, native platforms only, one frame per call.

  • Decodes RFC 8878 frames: raw, RLE and compressed blocks; raw, RLE, compressed and treeless literals in one-stream and four-stream form; Huffman weights in both the direct nibble and FSE-coded forms; predefined, RLE, FSE and repeat sequence tables for all three symbols; repeated offsets including the no-literals shift; multi-block frames reusing an earlier block's tables.
  • Verifies the content checksum when a frame carries one, with XXH64 in lib/src/xxhash.dart.
  • Refuses dictionaries, several frames in one buffer, and any input left over after a frame.
  • Bounds what a frame header can make it allocate. ZstdDecoder takes a maxOutputSize, 64 MiB by default, and a caller passing expectedSize gets an exact check against the frame's own declared size.
  • Every failure is a ZstdException.

Correctness is measured against libzstd through pyzstd rather than against expectations written by hand: 2,615 blocks of real elevation data across 32 containers, plus a committed corpus of 23 cases covering the format branches that real data never reaches. 131 tests.

Roughly four times slower than native zstd. On a Pixel 10 Pro XL, profile AOT, a 128 KiB block decodes in 0.414 ms, which is 302 MB/s.