Skip to content

Fix sync-flush stored-block misalignment in Deflate.flush - #286

Open
kixelated wants to merge 1 commit into
101arrowz:masterfrom
kixelated:claude/epic-driscoll-c40fd0
Open

Fix sync-flush stored-block misalignment in Deflate.flush#286
kixelated wants to merge 1 commit into
101arrowz:masterfrom
kixelated:claude/epic-driscoll-c40fd0

Conversation

@kixelated

Copy link
Copy Markdown

NOTE: AI generated. I did verify that fflate produces incorrect output periodically.

When a deflated block ended at a bit position with (s.r & 7) == 6, flush(true) wrote the empty stored block's LEN/NLEN fields one byte too early. wfblk byte-aligns using shft(pos + 2), reserving only 2 header bits, but the flush path's pos (s.r) still points at the BFINAL bit, so the stored-block header is 3 bits (BFINAL + 2-bit BTYPE). At that alignment the third header bit lands on a byte boundary and the 00 00 ff ff sync marker becomes bit-misaligned. fflate's own inflate masked the corruption, but spec-compliant decoders (zlib, pako) reject the following block with "invalid stored block lengths".

Pass (s.r & 7) + 1 to account for the implicit BFINAL bit, matching the convention at wfblk's other call site in wblk. This also fixes a latent case where the carried partial-byte value packed into s.r's high bits would skew the shft() arithmetic.

Adds a regression test covering fflate self round-trip and reference decode (Node zlib) for the minimal trigger, a real JSON snapshot+deltas stream, and a sweep of repeated-byte lengths.

When a deflated block ended at a bit position with `(s.r & 7) == 6`,
`flush(true)` wrote the empty stored block's LEN/NLEN fields one byte too
early. `wfblk` byte-aligns using `shft(pos + 2)`, reserving only 2 header
bits, but the flush path's `pos` (s.r) still points at the BFINAL bit, so
the stored-block header is 3 bits (BFINAL + 2-bit BTYPE). At that alignment
the third header bit lands on a byte boundary and the 00 00 ff ff sync
marker becomes bit-misaligned. fflate's own inflate masked the corruption,
but spec-compliant decoders (zlib, pako) reject the following block with
"invalid stored block lengths".

Pass `(s.r & 7) + 1` to account for the implicit BFINAL bit, matching the
convention at wfblk's other call site in wblk. This also fixes a latent
case where the carried partial-byte value packed into s.r's high bits would
skew the shft() arithmetic.

Adds a regression test covering fflate self round-trip and reference decode
(Node zlib) for the minimal trigger, a real JSON snapshot+deltas stream, and
a sweep of repeated-byte lengths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Author

And just to elaborate, this is Z_SYNC_FLUSH.

It makes a HUGE difference for my framed JSON payloads. 40% bitrate savings -> 90% bitrate savings, all because flushing means we can reuse the sliding window between these relatively small snapshots (emitted once every 30ms).

I have to use pako in the meantime. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant