diff --git a/src/blz.rs b/src/blz.rs index ad18051..30689e0 100644 --- a/src/blz.rs +++ b/src/blz.rs @@ -6,10 +6,10 @@ //! back-reference into already-decoded data, grouped under flag bytes and //! finished with a small trailer. //! -//! Only compression is implemented here, since that is all the KIP1 builder -//! needs. [`compress`] always allocates a worst-case output buffer up front, so -//! it cannot fail and is infallible for any input (including empty), and it -//! never mutates the caller's slice. +//! [`compress`] always allocates a worst-case output buffer up front, so it +//! cannot fail and is infallible for any input (including empty), and it never +//! mutates the caller's slice. [`decompress`] is its inverse and is fallible, +//! because it is the half that reads bytes the crate did not produce. //! //! # Why "backwards" //! @@ -36,7 +36,9 @@ //! [ raw bytes ][ 0x00 padding ][ u32 = 0 ] //! ``` //! -//! The decoded result is simply the leading `len - 4` bytes. +//! The decoded result is the leading bytes, but *how many* is not recorded anywhere in the +//! stream: the padding is indistinguishable from data. A decoder is told the length from +//! outside — for a KIP1 segment it is the segment header's `decomp_size`. //! //! ## Packed (compressed input) //! @@ -241,14 +243,33 @@ fn compress_into(input: &mut [u8], output: &mut [u8]) -> usize { input.reverse(); output[..packed_len].reverse(); + // The trailer is written into the bytes the packed layout saves, so a layout saving less than + // the trailer costs cannot be expressed: `extra_len` would have to be negative. Saving exactly + // the trailer is no better — that writes `extra_len` as zero, which is the marker for a stored + // stream, and the result would decode as one. Both cases fall back to storing, which is valid + // and, for the small highly compressible inputs that reach here, smaller anyway. + let trailer_fits = + len - best_packed - best_remaining > packed_header_size(best_remaining + best_packed); + // Compare the aligned packed layout against simply storing the raw bytes. - if best_packed == 0 || len + 4 < ((best_packed + best_remaining + 3) & 0xFFFF_FFFC) + 8 { + if best_packed == 0 + || !trailer_fits + || len + 4 < ((best_packed + best_remaining + 3) & 0xFFFF_FFFC) + 8 + { store_uncompressed(input, output) } else { store_packed(input, output, packed_len, best_packed, best_remaining) } } +/// Size of the trailer for a packed region ending at `packed_end`, alignment padding included. +/// +/// The padding brings the trailer onto a 4-byte boundary and is counted in the recorded header +/// size, so both the layout decision and the writer have to agree on it. +fn packed_header_size(packed_end: usize) -> usize { + size_of::