Repository navigation
Conversation
…s C does C's AddBit shifts the bit by the caller's bp as given, so for bp >= 8 the BFINAL bit falls outside the byte, and only then masks bp to 0..7. deflate_helper masked bp before writing anything, so the port wrote BFINAL at bit bp & 7 and, for a multiple of 8, started a new byte.
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Write BFINAL with the caller's bit pointer, as C's AddBit does
Problem
ZopfliDeflateandZopfliDeflatePart(C, Rust) take the caller's bit pointerbp. C's comment says bp is always in 0..7, and so does the port's safety note, but neither side checks it. The first bit a block writes is BFINAL (C, Rust), and the two sides write it differently when bp is 8 or more:AddBitappends a byte only when bp is 0, ORsbit << *bpinto the last byte, and only then masks bp to 0..7. For bp from 8 to 31 the shifted bit falls outside the byte, so BFINAL is lost, and a multiple of 8 appends no byte.bp & 7, and a multiple of 8 starts a new byte.Every bit after BFINAL is the same on both sides, since
(bp + 1) & 7is((bp & 7) + 1) & 7. For example,ZopfliDeflatewithbtype = 1,final = 2^31-1,bp = 115, an empty input and one output byte 0xb7:b7 00(BFINAL dropped)bf 00(BFINAL at bit 3)With
final = 0both sides write a 0 bit, so they agree.Question
Which behaviour do we want: C's or the Rust port's? Specifically, when bp is 8 or more, should the block's BFINAL bit be shifted by the caller's bp, so that it is dropped and a multiple of 8 starts no new byte (like the C source), or should bp be masked to 0..7 before anything is written (like the Rust port)?
This PR implements C's behaviour.
Fix
deflate_helperkeeps the caller's bp:let old_bp = *bpin place of*bp & 7. A bp that is a multiple of 8 then continues the caller's last byte, as in C.add_bitshifts as C does: the shift is done on au32, as C'sbit << *bpis done on anint, so a bit shifted past bit 7 is dropped. bp is advanced withwrapping_add, as C'sintarithmetic does for bp 255.Replayed before and after (x86-64), on the campaigns' records:
ZopfliDeflate,btype = 1, bp 115 (the example)b7 00bf 00b7 00ZopfliDeflate,btype = 1, bp 246, a 2-byte inputZopfliDeflatePart,btype = 1, bp 244, a 2-byte inputThe
.outsizerecords the campaigns found have an out-of-rangebtypeas well as a bp of 8 or more. With this PR and #212, theZopfliDeflateones (bp 196, 99 and 64) agree with C too.Downstream check. The normal path is untouched (bp in 0..7):
ZopfliGzipCompresson the repository'sREADMEgives the same 831-byte stream from the port before and after this patch, andgzip.decompressrestores the file.Notes for review:
bp mod 32.wrapping_shldoes the same, so this patch reproduces x86-64. Other targets may differ. The patch itself is safe Rust.ZopfliDeflatePartrecord (btypeout of range,instart2^64-1 >inend0, bp 123) still differs with this PR, Compress an out-of-range btype as a dynamic block, as C does #212 and Read the input the way C does when ZopfliDeflatePart gets instart > inend #216: the port returns without writing. Its dynamic-block path also refusesinstart > inend, which Read the input the way C does when ZopfliDeflatePart gets instart > inend #216 does not change.