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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant