// Deflate compression and decompression (RFC 1951)
// Implements both the inflate decoder and deflate encoder
// Re-export dependencies
///|
using @bytebuf {type ByteBuf}
///|
using @bitstream {type BitWriter}
///|
using @huffman {type HuffmanDecoder}
///|
using @huffman {type HuffmanEncoder}
// ============================================================================
// RFC 1951 DEFLATE Constants and Lookup Tables
// ============================================================================
///|
/// Maximum literal/length symbol value (285) per RFC 1951.
/// Symbols 0-255: literal bytes, 256: end-of-block, 257-285: length codes.
let litlen_sym_max : Int = 285
///|
/// End-of-block symbol (256) terminates each deflate block.
/// Required at the end of every block to signal decoder completion.
let litlen_end_of_block_sym : Int = 256
// let litlen_first_len_sym : Int = 257
///|
/// Maximum distance symbol value (29) per RFC 1951.
/// Distance symbols 0-29 encode back-reference distances 1-32768.
let dist_sym_max : Int = 29
///|
/// Total count of distance symbols (30) including symbol 0.
let max_dist_sym_count : Int = 30
///|
/// Total count of code length symbols (19) for dynamic Huffman trees.
/// Used to encode the code lengths of literal/length and distance trees.
let max_codelen_sym_count : Int = 19
///|
/// Code length symbol transmission order per RFC 1951 Section 3.2.7.
///
/// Dynamic Huffman blocks first transmit code lengths for code length symbols
/// in this specific order to optimize compression of typical length patterns.
/// Most significant symbols (16-18) appear first as they're often unused.
let codelen_order : Array[Int] = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
]
// ============================================================================
// Length and Distance Value Decoding Functions
// ============================================================================
///|
/// Extract base length value from packed length encoding.
/// Upper 28 bits contain the base length for length symbols 257-285.
fn length_value_base(v : Int) -> Int {
v >> 4
}
///|
/// Extract extra bits count from packed length encoding.
/// Lower 4 bits specify how many additional bits to read for exact length.
fn length_value_extra_bits(v : Int) -> Int {
v & 0xF
}
///|
/// Extract base distance value from packed distance encoding.
/// Upper 28 bits contain the base distance for distance symbols 0-29.
fn dist_value_base(v : Int) -> Int {
v >> 4
}
///|
/// Extract extra bits count from packed distance encoding.
/// Lower 4 bits specify how many additional bits to read for exact distance.
fn dist_value_extra_bits(v : Int) -> Int {
v & 0xF
}
// Length and distance value tables
///|
fn length_value_of_length_sym(sym : Int) -> Int {
// Symbol must be 257-285
let length_value_table : Array[Int] = [
3 << 4,
4 << 4,
5 << 4,
6 << 4,
7 << 4,
8 << 4,
9 << 4,
10 << 4,
(11 << 4) | 1,
(13 << 4) | 1,
(15 << 4) | 1,
(17 << 4) | 1,
(19 << 4) | 2,
(23 << 4) | 2,
(27 << 4) | 2,
(31 << 4) | 2,
(35 << 4) | 3,
(43 << 4) | 3,
(51 << 4) | 3,
(59 << 4) | 3,
(67 << 4) | 4,
(83 << 4) | 4,
(99 << 4) | 4,
(115 << 4) | 4,
(131 << 4) | 5,
(163 << 4) | 5,
(195 << 4) | 5,
(227 << 4) | 5,
258 << 4,
]
length_value_table[sym - 257]
}
///|
let dist_value_of_sym : Array[Int] = [
1 << 4,
2 << 4,
3 << 4,
4 << 4,
(5 << 4) | 1,
(7 << 4) | 1,
(9 << 4) | 2,
(13 << 4) | 2,
(17 << 4) | 3,
(25 << 4) | 3,
(33 << 4) | 4,
(49 << 4) | 4,
(65 << 4) | 5,
(97 << 4) | 5,
(129 << 4) | 6,
(193 << 4) | 6,
(257 << 4) | 7,
(385 << 4) | 7,
(513 << 4) | 8,
(769 << 4) | 8,
(1025 << 4) | 9,
(1537 << 4) | 9,
(2049 << 4) | 10,
(3073 << 4) | 10,
(4097 << 4) | 11,
(6145 << 4) | 11,
(8193 << 4) | 12,
(12289 << 4) | 12,
(16385 << 4) | 13,
(24577 << 4) | 13,
]
// ============================================================================
// Inflate Decoder (Decompression)
// ============================================================================
///|
/// Inflate decoder state
priv struct InflateDecoder {
src : BytesView // Source compressed data view (no copy)
src_max : Int // Maximum valid index in src
mut src_pos : Int // Current read position
mut src_bits : Int // Buffered bits (up to 31 bits)
mut src_bits_len : Int // Number of valid bits in src_bits
dst : ByteBuf // Output buffer
dyn_litlen : HuffmanDecoder // Dynamic literal/length decoder
dyn_dist : HuffmanDecoder // Dynamic distance decoder
}
///|
/// Create a new inflate decoder
fn InflateDecoder::new(
src_view : BytesView,
decompressed_size : Int?,
) -> InflateDecoder {
let len = src_view.length()
let src_max = len - 1
let dst = match decompressed_size {
Some(size) => @bytebuf.new(size_hint=size, fixed=true)
None => @bytebuf.new(size_hint=len * 3)
}
{
src: src_view,
src_max,
src_pos: 0,
src_bits: 0,
src_bits_len: 0,
dst,
dyn_litlen: @huffman.HuffmanDecoder::new(),
dyn_dist: @huffman.HuffmanDecoder::new(),
}
}
///|
/// Read N bits from the bit stream (N < 32)
fn InflateDecoder::read_bits(self : InflateDecoder, count : Int) -> Int raise {
let mut bits = self.src_bits
let mut bits_len = self.src_bits_len
// Refill bit buffer if needed
while bits_len < count {
if self.src_pos > self.src_max {
fail("Corrupted deflate stream: unexpected end of data")
}
let byte = self.src[self.src_pos].to_int()
bits = bits | (byte << bits_len)
self.src_pos = self.src_pos + 1
bits_len = bits_len + 8
}
// Extract requested bits
let result = bits & ((1 << count) - 1)
self.src_bits = bits >> count
self.src_bits_len = bits_len - count
result
}
///|
/// Read an integer value: base + read_bits(bit_count)
fn InflateDecoder::read_int(
self : InflateDecoder,
base : Int,
bit_count : Int,
) -> Int raise {
if bit_count == 0 {
base
} else {
base + self.read_bits(bit_count)
}
}
///|
/// Read a symbol using a Huffman decoder (helper)
fn read_symbol_loop(
decoder_state : InflateDecoder,
decoder : HuffmanDecoder,
len : Int,
base : Int,
offs : Int,
) -> Int raise {
let new_offs = 2 * offs + decoder_state.read_bits(1)
let count = decoder.count(len)
if new_offs < count {
decoder.symbol(base + new_offs)
} else {
read_symbol_loop(
decoder_state,
decoder,
len + 1,
base + count,
new_offs - count,
)
}
}
///|
/// Read a symbol using a Huffman decoder
fn InflateDecoder::read_symbol(
self : InflateDecoder,
decoder : HuffmanDecoder,
) -> Int raise {
read_symbol_loop(self, decoder, 1, 0, 0)
}
///|
/// Read and process symbols from a compressed block
fn read_block_symbols(
decoder : InflateDecoder,
litlen_decoder : HuffmanDecoder,
dist_decoder : HuffmanDecoder,
) -> Unit raise {
while true {
let sym = decoder.read_symbol(litlen_decoder)
if sym < litlen_end_of_block_sym {
// Literal byte
decoder.dst.write_byte(sym.to_byte())
} else if sym == litlen_end_of_block_sym {
// End of block
return
} else if sym > litlen_decoder.max_sym() ||
sym > litlen_sym_max ||
litlen_decoder.max_sym() == -1 {
fail("Corrupted deflate stream: invalid literal/length symbol")
} else {
// Length symbol - read the length
let len_value = length_value_of_length_sym(sym)
let base = length_value_base(len_value)
let extra_bits = length_value_extra_bits(len_value)
let length = decoder.read_int(base, extra_bits)
// Read the distance
let dist_sym = decoder.read_symbol(dist_decoder)
if dist_sym > dist_decoder.max_sym() || dist_sym > dist_sym_max {
fail("Corrupted deflate stream: invalid distance symbol")
}
let dist_value = dist_value_of_sym[dist_sym]
let dist_base = dist_value_base(dist_value)
let dist_extra = dist_value_extra_bits(dist_value)
let dist = decoder.read_int(dist_base, dist_extra)
// Copy from earlier in the output
if dist > decoder.dst.length() {
fail("Corrupted deflate stream: distance too large")
}
decoder.dst.recopy(decoder.dst.length() - dist, length)
}
}
}
///|
/// Read an uncompressed (stored) block
fn read_uncompressed_block(decoder : InflateDecoder) -> Unit raise {
// Skip to byte boundary
decoder.src_bits = 0
decoder.src_bits_len = 0
// Need at least 4 bytes for length fields
if decoder.src_max - decoder.src_pos + 1 < 4 {
fail("Corrupted deflate stream: truncated uncompressed block")
}
// Read length and inverted length
let length = decoder.src[decoder.src_pos].to_int() |
(decoder.src[decoder.src_pos + 1].to_int() << 8)
let inv_length = decoder.src[decoder.src_pos + 2].to_int() |
(decoder.src[decoder.src_pos + 3].to_int() << 8)
decoder.src_pos = decoder.src_pos + 4
// Verify they are complements
if length != (inv_length ^ 0xFFFF) {
fail("Corrupted deflate stream: invalid uncompressed block length")
}
// Check we have enough data
if decoder.src_max - decoder.src_pos + 1 < length {
fail("Corrupted deflate stream: truncated uncompressed block data")
}
// Copy bytes directly (use bytes view to avoid per-byte loop in our wrapper)
decoder.dst.write_bytesview(
decoder.src[decoder.src_pos:decoder.src_pos + length],
)
decoder.src_pos = decoder.src_pos + length
}
///|
/// Read a block compressed with fixed Huffman codes
fn read_fixed_block(decoder : InflateDecoder) -> Unit raise {
read_block_symbols(
decoder, @huffman.fixed_litlen_decoder, @huffman.fixed_dist_decoder,
)
}
///|
/// Read a block compressed with dynamic Huffman codes
fn read_dynamic_block(decoder : InflateDecoder) -> Unit raise {
// Read number of literal/length codes (257-286)
let hlit = decoder.read_int(257, 5)
// Read number of distance codes (1-32)
let hdist = decoder.read_int(1, 5)
if hlit > 286 || hdist > max_dist_sym_count {
fail("Corrupted deflate stream: invalid dynamic block header")
}
// Read number of code length codes (4-19)
let hclen = decoder.read_int(4, 4)
// Read code length code lengths
let codelen_lengths = Array::make(max_codelen_sym_count, 0)
for i = 0; i < hclen; i = i + 1 {
codelen_lengths[codelen_order[i]] = decoder.read_bits(3)
}
// Build Huffman decoder for code lengths (temporarily use dyn_litlen)
decoder.dyn_litlen.init_from_lengths(
codelen_lengths, 0, max_codelen_sym_count,
)
if decoder.dyn_litlen.max_sym() == -1 {
fail("Corrupted deflate stream: empty code length code")
}
// Decode the literal/length and distance code lengths
let lengths = Array::make(286 + max_dist_sym_count, 0)
let mut num = 0
let total = hlit + hdist
while num < total {
let sym = decoder.read_symbol(decoder.dyn_litlen)
if sym > decoder.dyn_litlen.max_sym() {
fail("Corrupted deflate stream: invalid code length symbol")
}
let (repeat, value) = match sym {
16 => {
// Repeat previous code length 3-6 times
if num == 0 {
fail("Corrupted deflate stream: repeat with no previous code")
}
(decoder.read_int(3, 2), lengths[num - 1])
}
17 =>
// Repeat zero 3-10 times
(decoder.read_int(3, 3), 0)
18 =>
// Repeat zero 11-138 times
(decoder.read_int(11, 7), 0)
_ => (1, sym)
}
if repeat > total - num {
fail("Corrupted deflate stream: code length repeat too long")
}
for i = 0; i < repeat; i = i + 1 {
lengths[num] = value
num = num + 1
}
}
// Check that end-of-block symbol has non-zero length
if lengths[256] == 0 {
fail("Corrupted deflate stream: missing end-of-block code")
}
// Initialize the literal/length and distance decoders
decoder.dyn_litlen.init_from_lengths(lengths, 0, hlit)
decoder.dyn_dist.init_from_lengths(lengths, hlit, hdist)
// Decompress the block
read_block_symbols(decoder, decoder.dyn_litlen, decoder.dyn_dist)
}
///|
/// Main inflate loop - decompress all blocks
fn inflate_loop(decoder : InflateDecoder) -> Bytes raise {
while true {
// Read block header
let is_final = decoder.read_bits(1) == 1
let btype = decoder.read_bits(2)
// Process block based on type
match btype {
0 => read_uncompressed_block(decoder) // No compression
1 => read_fixed_block(decoder) // Fixed Huffman
2 => read_dynamic_block(decoder) // Dynamic Huffman
_ => fail("Corrupted deflate stream: invalid block type")
}
if is_final {
return decoder.dst.contents()
}
}
// Unreachable
abort("Unreachable")
}
///|
/// Decompress deflate format data (RFC 1951)
/// Decompress a deflate (RFC 1951) stream segment.
/// Parameters:
/// src_view - BytesView identifying compressed data (no copy performed)
/// decompressed_size - optional expected output size (optimizes allocation / validation)
/// Errors: raises on malformed block headers, invalid Huffman codes, or truncated input.
pub fn inflate(src_view : BytesView, decompressed_size? : Int) -> Bytes raise {
// Directly construct decoder from view (avoid intermediate copy)
let decoder = InflateDecoder::new(src_view, decompressed_size)
inflate_loop(decoder)
}
///|
/// Decompress deflate data and compute CRC-32
/// Inflate and also compute CRC-32 of the decompressed output (single pass over result).
/// Returns (bytes, crc32).
// NOTE: Previously we exposed helper wrappers `inflate_and_crc32` and
// `inflate_and_adler32` that returned (bytes, checksum). These added API
// surface without providing substantial value (a single extra line for the
// caller). They have been removed; callers should now:
// let bytes = inflate(src, start, len, size?)
// let crc = @crc32.bytes_crc32(bytes[:])
// (or Adler-32 respectively).
// ============================================================================
// Deflate Encoder (Compression)
// ============================================================================
///|
/// Create stored (uncompressed) deflate blocks per RFC 1951 Section 3.2.4.
///
/// Stored blocks contain raw uncompressed data with a simple header structure:
/// - Block header (1 byte): BFINAL bit + BTYPE=00 (stored)
/// - LEN (2 bytes, little-endian): actual data length
/// - NLEN (2 bytes, little-endian): bitwise complement of LEN for integrity
/// - Raw data bytes (LEN bytes)
///
/// The NLEN field provides error detection - decoders verify NLEN = ~LEN.
/// This redundancy is mandated by RFC 1951 for data integrity checking.
///
/// ## Parameters
/// - `bytes`: source data buffer
/// - `start`: starting offset in source buffer
/// - `len`: number of bytes to compress (max 65535 for single block)
///
/// ## Limitations
/// - Single block only (no chunking for len > 65535)
/// - Always marks block as final (BFINAL=1)
/// - For larger data, use higher-level `deflate()` function
///
/// ## Returns
/// Complete deflate stream with stored block containing the specified data slice.
pub fn deflate_stored(data : BytesView) -> Bytes raise {
let len = data.length()
let max_block_size = 65535
guard len <= max_block_size else {
fail("Deflate compression for data > 65535 bytes not yet implemented")
}
// Single block (always final)
let result_size = 1 + 4 + len
let buffer = @bytebuf.new(size_hint=result_size)
buffer.write_byte((0b00000001).to_byte()) // BFINAL=1, BTYPE=00
// LEN (little-endian)
buffer.write_byte((len & 0xFF).to_byte())
buffer.write_byte(((len >> 8) & 0xFF).to_byte())
// NLEN = one's complement
let nlen = len ^ 0xFFFF
buffer.write_byte((nlen & 0xFF).to_byte())
buffer.write_byte(((nlen >> 8) & 0xFF).to_byte())
// Raw bytes
buffer.write_bytesview(data)
buffer.contents()
}
///|
/// Build optimal Huffman code lengths using Package-Merge algorithm.
///
/// Constructs minimum-redundancy prefix codes (Huffman codes) from symbol frequencies
/// while respecting the maximum code length constraint required by deflate format.
///
/// ## Algorithm Overview
/// 1. **Frequency Analysis**: Count non-zero symbol frequencies
/// 2. **Trivial Cases**: Handle 0 or 1 symbols specially
/// 3. **Package-Merge**: Build optimal length-limited codes
/// 4. **Length Assignment**: Assign code lengths to minimize total bits
///
/// ## Deflate Constraints
/// - Literal/length codes: max 15 bits
/// - Distance codes: max 15 bits
/// - Code length codes: max 7 bits
///
/// ## Parameters
/// - `freqs`: frequency count for each symbol (0 to max_sym)
/// - `max_sym`: highest valid symbol index
/// - `max_code_len`: maximum allowed code length (typically 15 for deflate)
///
/// ## Returns
/// Array of code lengths indexed by symbol, 0 = unused symbol.
fn build_optimal_code_lengths(
freqs : Array[Int],
max_sym : Int,
max_code_len : Int,
) -> Array[Int] {
let lengths = Array::make(max_sym + 1, 0)
// Count non-zero frequencies
let mut count = 0
for i = 0; i <= max_sym; i = i + 1 {
if freqs[i] > 0 {
count = count + 1
}
}
// Trivial cases
if count == 0 {
return lengths
}
if count == 1 {
for i = 0; i <= max_sym; i = i + 1 {
if freqs[i] > 0 {
lengths[i] = 1
break
}
}
return lengths
}
if count == 2 {
let mut found = 0
for i = 0; i <= max_sym && found < 2; i = i + 1 {
if freqs[i] > 0 {
lengths[i] = 1
found = found + 1
}
}
return lengths
}
// Build Huffman tree
let heap = Array::make(count * 2, (0, 0))
let mut heap_size = 0
for i = 0; i <= max_sym; i = i + 1 {
if freqs[i] > 0 {
heap[heap_size] = (freqs[i], i)
heap_size = heap_size + 1
}
}
let parent = Array::make(max_sym + 1 + count, -1)
let mut next_internal_id = max_sym + 1
while heap_size > 1 {
// Extract two minimums
let mut min1_idx = 0
for i = 1; i < heap_size; i = i + 1 {
if heap[i].0 < heap[min1_idx].0 {
min1_idx = i
}
}
let node1 = heap[min1_idx]
heap[min1_idx] = heap[heap_size - 1]
heap_size = heap_size - 1
let mut min2_idx = 0
for i = 1; i < heap_size; i = i + 1 {
if heap[i].0 < heap[min2_idx].0 {
min2_idx = i
}
}
let node2 = heap[min2_idx]
parent[node1.1] = next_internal_id
parent[node2.1] = next_internal_id
heap[min2_idx] = (node1.0 + node2.0, next_internal_id)
next_internal_id = next_internal_id + 1
}
// Compute depths
for i = 0; i <= max_sym; i = i + 1 {
if freqs[i] > 0 {
let mut depth = 0
let mut node_id = i
while parent[node_id] != -1 {
depth = depth + 1
node_id = parent[node_id]
}
lengths[i] = if depth > max_code_len { max_code_len } else { depth }
}
}
lengths
}
///|
/// Build canonical Huffman codes from code lengths
fn build_canonical_huffman(
lengths : Array[Int],
max_sym : Int,
) -> HuffmanEncoder {
let encoder = @huffman.HuffmanEncoder::new()
let count = Array::make(16, 0)
for i = 0; i <= max_sym; i = i + 1 {
let len = lengths[i]
if len > 0 {
count[len] = count[len] + 1
}
}
count[0] = 0
let next_code = Array::make(16, 0)
let mut code = 0
for len = 1; len <= 15; len = len + 1 {
code = (code + count[len - 1]) << 1
next_code[len] = code
}
for sym = 0; sym <= max_sym; sym = sym + 1 {
let len = lengths[sym]
if len > 0 {
let c = next_code[len]
let bits = @huffman.reverse_bits(c, len)
encoder.set(sym, @huffman.sym_info_make(bits, len))
next_code[len] = c + 1
}
}
encoder
}
///|
/// Encode code lengths with run-length compression
fn encode_code_lengths(
litlen_lengths : Array[Int],
dist_lengths : Array[Int],
hlit : Int,
hdist : Int,
) -> (Array[Int], Array[Int], Int) {
let litlen_count = hlit + 257
let dist_count = hdist + 1
let total = litlen_count + dist_count
let all_lengths = Array::make(total, 0)
for i = 0; i < litlen_count; i = i + 1 {
all_lengths[i] = litlen_lengths[i]
}
for i = 0; i < dist_count; i = i + 1 {
all_lengths[litlen_count + i] = dist_lengths[i]
}
let codelen_syms = Array::make(total + 100, 0)
let codelen_freqs = Array::make(19, 0)
let mut sym_count = 0
let mut i = 0
while i < total {
let len = all_lengths[i]
if len == 0 {
let mut zero_count = 1
while i + zero_count < total &&
all_lengths[i + zero_count] == 0 &&
zero_count < 138 {
zero_count = zero_count + 1
}
if zero_count < 3 {
codelen_syms[sym_count] = 0
codelen_freqs[0] = codelen_freqs[0] + 1
sym_count = sym_count + 1
i = i + 1
} else if zero_count <= 10 {
let extra = zero_count - 3
codelen_syms[sym_count] = 17 | (extra << 8)
codelen_freqs[17] = codelen_freqs[17] + 1
sym_count = sym_count + 1
i = i + zero_count
} else {
let extra = zero_count - 11
codelen_syms[sym_count] = 18 | (extra << 8)
codelen_freqs[18] = codelen_freqs[18] + 1
sym_count = sym_count + 1
i = i + zero_count
}
} else {
codelen_syms[sym_count] = len
codelen_freqs[len] = codelen_freqs[len] + 1
sym_count = sym_count + 1
i = i + 1
let mut repeat_count = 0
let max_look = (i + 5).min(total)
while i + repeat_count < max_look && all_lengths[i + repeat_count] == len {
repeat_count = repeat_count + 1
}
if repeat_count >= 3 {
let extra = repeat_count - 3
codelen_syms[sym_count] = 16 | (extra << 8)
codelen_freqs[16] = codelen_freqs[16] + 1
sym_count = sym_count + 1
i = i + repeat_count
}
}
}
(codelen_syms, codelen_freqs, sym_count)
}
///|
/// Write dynamic Huffman block header
fn write_dynamic_header(
writer : BitWriter,
_litlen_encoder : HuffmanEncoder,
_dist_encoder : HuffmanEncoder,
codelen_encoder : HuffmanEncoder,
codelen_syms : Array[Int],
sym_count : Int,
hlit : Int,
hdist : Int,
) -> Unit {
let mut hclen_idx = 18
while hclen_idx > 0 {
let sym = codelen_order[hclen_idx]
if @huffman.sym_info_code_length(codelen_encoder.get(sym)) > 0 {
break
}
hclen_idx = hclen_idx - 1
}
if hclen_idx < 3 {
hclen_idx = 3
}
let hclen = hclen_idx + 1 - 4
writer.write_bits(hlit, 5)
writer.write_bits(hdist, 5)
writer.write_bits(hclen, 4)
for i = 0; i <= hclen_idx; i = i + 1 {
let sym = codelen_order[i]
let len = @huffman.sym_info_code_length(codelen_encoder.get(sym))
writer.write_bits(len, 3)
}
for i = 0; i < sym_count; i = i + 1 {
let sym_with_extra = codelen_syms[i]
let sym = sym_with_extra & 0xFF
let extra_bits = sym_with_extra >> 8
let info = codelen_encoder.get(sym)
let code = @huffman.sym_info_code(info)
let code_len = @huffman.sym_info_code_length(info)
writer.write_bits(code, code_len)
if sym == 16 {
writer.write_bits(extra_bits, 2)
} else if sym == 17 {
writer.write_bits(extra_bits, 3)
} else if sym == 18 {
writer.write_bits(extra_bits, 7)
}
}
}
///|
/// Convert a match length to symbol
fn length_to_symbol(length : Int) -> Int {
if length <= 10 {
257 + (length - 3)
} else if length <= 18 {
265 + (length - 11) / 2
} else if length <= 34 {
269 + (length - 19) / 4
} else if length <= 66 {
273 + (length - 35) / 8
} else if length <= 130 {
277 + (length - 67) / 16
} else if length <= 257 {
281 + (length - 131) / 32
} else {
285
}
}
///|
/// Convert a distance to symbol
fn distance_to_symbol(dist : Int) -> Int {
if dist <= 4 {
dist - 1
} else if dist <= 8 {
4 + ((dist - 5) >> 1)
} else if dist <= 16 {
6 + ((dist - 9) >> 2)
} else if dist <= 32 {
8 + ((dist - 17) >> 3)
} else if dist <= 64 {
10 + ((dist - 33) >> 4)
} else if dist <= 128 {
12 + ((dist - 65) >> 5)
} else if dist <= 256 {
14 + ((dist - 129) >> 6)
} else if dist <= 512 {
16 + ((dist - 257) >> 7)
} else if dist <= 1024 {
18 + ((dist - 513) >> 8)
} else if dist <= 2048 {
20 + ((dist - 1025) >> 9)
} else if dist <= 4096 {
22 + ((dist - 2049) >> 10)
} else if dist <= 8192 {
24 + ((dist - 4097) >> 11)
} else if dist <= 16384 {
26 + ((dist - 8193) >> 12)
} else {
28 + ((dist - 16385) >> 13)
}
}
///|
/// Write a literal or end-of-block symbol
fn BitWriter::write_literal_symbol(
writer : BitWriter,
encoder : HuffmanEncoder,
symbol : Int,
) -> Unit {
let info = encoder.get(symbol)
let code = @huffman.sym_info_code(info)
let len = @huffman.sym_info_code_length(info)
writer.write_bits(code, len)
}
///|
/// Write a length/distance pair
fn write_length_distance(
writer : BitWriter,
litlen_encoder : HuffmanEncoder,
dist_encoder : HuffmanEncoder,
length : Int,
distance : Int,
) -> Unit {
// Write length symbol
let len_sym = length_to_symbol(length)
let len_info = litlen_encoder.get(len_sym)
let len_code = @huffman.sym_info_code(len_info)
let len_code_len = @huffman.sym_info_code_length(len_info)
writer.write_bits(len_code, len_code_len)
// Write extra bits for length
let len_value = length_value_of_length_sym(len_sym)
let len_base = length_value_base(len_value)
let len_extra_bits = length_value_extra_bits(len_value)
if len_extra_bits > 0 {
let extra = length - len_base
writer.write_bits(extra, len_extra_bits)
}
// Write distance symbol
let dist_sym = distance_to_symbol(distance)
let dist_info = dist_encoder.get(dist_sym)
let dist_code = @huffman.sym_info_code(dist_info)
let dist_code_len = @huffman.sym_info_code_length(dist_info)
writer.write_bits(dist_code, dist_code_len)
// Write extra bits for distance
let dist_value = dist_value_of_sym[dist_sym]
let dist_base = dist_value_base(dist_value)
let dist_extra_bits = dist_value_extra_bits(dist_value)
if dist_extra_bits > 0 {
let extra = distance - dist_base
writer.write_bits(extra, dist_extra_bits)
}
}
///|
/// Frequency counter for dynamic Huffman
priv struct FrequencyCounter {
litlen_freqs : Array[Int]
dist_freqs : Array[Int]
}
///|
fn FrequencyCounter::new() -> FrequencyCounter {
{ litlen_freqs: Array::make(286, 0), dist_freqs: Array::make(30, 0), }
}
///|
fn FrequencyCounter::add_literal(self : FrequencyCounter, lit : Int) -> Unit {
self.litlen_freqs[lit] = self.litlen_freqs[lit] + 1
}
///|
fn FrequencyCounter::add_length(self : FrequencyCounter, length : Int) -> Unit {
let sym = length_to_symbol(length)
self.litlen_freqs[sym] = self.litlen_freqs[sym] + 1
}
///|
fn FrequencyCounter::add_distance(self : FrequencyCounter, dist : Int) -> Unit {
let sym = distance_to_symbol(dist)
self.dist_freqs[sym] = self.dist_freqs[sym] + 1
}
///|
fn FrequencyCounter::add_end_of_block(self : FrequencyCounter) -> Unit {
self.litlen_freqs[256] = 1
}
///|
/// Deflate with fixed Huffman (literals only, no LZ77)
// fn deflate_fixed_literals_only(
// bytes : Bytes,
// start : Int,
// len : Int,
// is_final : Bool,
// ) -> Bytes {
// let output = @bytebuf.new(len * 2, false)
// let writer = @bitstream.BitWriter::new(output)
// let header = if is_final { 0b011 } else { 0b010 }
// writer.write_bits(header, 3)
// for i = 0; i < len; i = i + 1 {
// let byte = bytes[start + i].to_int()
// write_literal_symbol(writer, @huffman.fixed_litlen_encoder, byte)
// }
// write_literal_symbol(
// writer, @huffman.fixed_litlen_encoder, litlen_end_of_block_sym,
// )
// writer.flush()
// output.contents()
// }
///|
/// Deflate compression using LZ77 + Fixed Huffman codes per RFC 1951 Section 3.2.6.
///
/// This implements the "fixed Huffman" compression mode (BTYPE=01) which combines:
/// 1. **LZ77 Algorithm**: Finds repeated substrings and encodes as length/distance pairs
/// 2. **Fixed Huffman**: Uses predefined code tables (no dynamic tree overhead)
///
/// ## LZ77 Processing
/// - Maintains sliding window hash table for fast duplicate detection
/// - Configurable match quality vs speed tradeoffs via `good_match`/`max_chain`
/// - Outputs literal bytes + (length, distance) back-references
///
/// ## Fixed Huffman Encoding
/// - Literal/length symbols: 0-143 (8 bits), 144-255 (9 bits), 256-279 (7 bits), 280-287 (8 bits)
/// - Distance symbols: 0-31 (5 bits each)
/// - No dynamic tree transmission overhead
///
/// ## Performance Tuning
/// - `good_match`: stop searching when match >= this length (speed vs compression)
/// - `max_chain`: maximum hash chain traversals (speed vs compression)
/// - Higher values = better compression, slower encoding
///
/// ## Parameters
/// - `bytes`: source data buffer
/// - `start`: starting offset in source
/// - `len`: number of bytes to compress
/// - `is_final`: whether this is the final block in stream
/// - `good_match`: LZ77 "good enough" match length threshold
/// - `max_chain`: LZ77 maximum hash chain search depth
///
/// ## Returns
/// Deflate block with fixed Huffman encoding of LZ77-compressed data.
pub fn deflate_fixed(
data : BytesView,
is_final : Bool,
good_match : Int,
max_chain : Int,
) -> Bytes {
let len = data.length()
if len == 0 {
let output = @bytebuf.new(size_hint=10)
let header = if is_final { 0b011 } else { 0b010 }
let writer = @bitstream.BitWriter::new(output)
writer
..write_bits(header, 3)
..write_literal_symbol(
@huffman.fixed_litlen_encoder, litlen_end_of_block_sym,
)
.flush()
return output.contents()
}
let hash_head = Array::make(@lz77.hash_size, @lz77.no_pos)
let hash_prev = Array::make(@lz77.window_size, 0)
let output = @bytebuf.new(size_hint=len * 2)
let writer = @bitstream.BitWriter::new(output)
let header = if is_final { 0b011 } else { 0b010 }
writer.write_bits(header, 3)
let max_pos = len - @lz77.min_match_len
fn compress_loop(pos : Int, prev_bref : Int) -> Unit {
if pos > max_pos {
let prev_len = @lz77.backref_len(prev_bref)
let final_pos = if prev_len == 0 {
pos
} else {
let dist = @lz77.backref_dist(prev_bref)
write_length_distance(
writer, @huffman.fixed_litlen_encoder, @huffman.fixed_dist_encoder, prev_len,
dist,
)
pos - 1 + prev_len
}
let end = len
for i = final_pos; i < end; i = i + 1 {
let byte = data[i].to_int()
writer.write_literal_symbol(@huffman.fixed_litlen_encoder, byte)
}
writer.write_literal_symbol(
@huffman.fixed_litlen_encoder, litlen_end_of_block_sym,
)
} else {
if pos + 4 > len {
if prev_bref > 0 {
let prev_len = @lz77.backref_len(prev_bref)
let dist = @lz77.backref_dist(prev_bref)
write_length_distance(
writer, @huffman.fixed_litlen_encoder, @huffman.fixed_dist_encoder, prev_len,
dist,
)
compress_loop(pos - 1 + prev_len, 0)
} else {
let byte = data[pos].to_int()
writer.write_literal_symbol(@huffman.fixed_litlen_encoder, byte)
compress_loop(pos + 1, 0)
}
return
}
let hash = @lz77.hash4(data[pos:])
let max_match = (len - pos).min(@lz77.max_match_len)
let prev_len = @lz77.backref_len(prev_bref)
let cur_bref = @lz77.find_backref(
data, hash_head, hash_prev, pos, hash, prev_len, max_match, good_match, max_chain,
)
let cur_len = @lz77.backref_len(cur_bref)
@lz77.insert_hash(hash_head, hash_prev, hash, pos)
if prev_len > 0 && prev_len >= cur_len {
let dist = @lz77.backref_dist(prev_bref)
write_length_distance(
writer, @huffman.fixed_litlen_encoder, @huffman.fixed_dist_encoder, prev_len,
dist,
)
let next = pos - 1 + prev_len
let last = (next - 1).min(max_pos)
for j = pos + 1; j <= last; j = j + 1 {
if j + 3 < len {
let h = @lz77.hash4(data[j:])
@lz77.insert_hash(hash_head, hash_prev, h, j)
}
}
compress_loop(next, 0)
} else if cur_len == 0 {
if prev_len > 0 {
let byte = data[pos - 1].to_int()
writer.write_literal_symbol(@huffman.fixed_litlen_encoder, byte)
} else {
let byte = data[pos].to_int()
writer.write_literal_symbol(@huffman.fixed_litlen_encoder, byte)
}
compress_loop(pos + 1, 0)
} else {
if prev_len > 0 {
let byte = data[pos - 1].to_int()
writer.write_literal_symbol(@huffman.fixed_litlen_encoder, byte)
}
compress_loop(pos + 1, cur_bref)
}
}
}
compress_loop(0, 0)
writer.flush()
output.contents()
}
///|
/// DEFLATE compression using LZ77 + Dynamic Huffman trees per RFC 1951 Section 3.2.7.
///
/// This implements the most sophisticated DEFLATE compression mode (BTYPE=10) which
/// provides optimal compression by building custom Huffman trees tailored to the
/// specific input data characteristics.
///
/// ## Algorithm Overview
/// 1. **Two-Pass Processing**:
/// - Pass 1: LZ77 compression + symbol frequency counting
/// - Pass 2: Huffman tree construction + final encoding
///
/// 2. **Dynamic Tree Construction**:
/// - Build optimal literal/length tree (up to 286 symbols)
/// - Build optimal distance tree (up to 30 symbols)
/// - Build code length tree to encode the above trees
///
/// 3. **Header Transmission**:
/// - HLIT: literal/length tree size
/// - HDIST: distance tree size
/// - HCLEN: code length tree size
/// - Code length tree itself (3-7 bits per symbol)
/// - Literal/length tree encoded with code length tree
/// - Distance tree encoded with code length tree
///
/// ## Compression Benefits
/// - **Adaptive Trees**: Huffman codes optimized for actual symbol frequencies
/// - **Better Compression**: Typically 5-15% smaller than fixed Huffman
/// - **Versatile**: Handles both text and binary data effectively
///
/// ## Overhead Considerations
/// - **Header Cost**: ~100-500 bytes for tree transmission
/// - **Computation Cost**: ~2x encoding time vs fixed Huffman
/// - **Minimum Size**: Most effective for inputs >256 bytes
///
/// ## Parameters
/// - `bytes`: source data buffer
/// - `start`: starting offset in source
/// - `len`: number of bytes to compress
/// - `is_final`: whether this is the final block in stream
/// - `good_match`: LZ77 "good enough" match length threshold
/// - `max_chain`: LZ77 maximum hash chain search depth
///
/// ## Returns
/// Complete DEFLATE block with dynamic Huffman encoding including tree headers.
pub fn deflate_dynamic(
data : BytesView,
is_final : Bool,
good_match : Int,
max_chain : Int,
) -> Bytes {
/// Dynamic Huffman DEFLATE compression.
///
/// This variant builds optimal Huffman trees for the provided data slice.
/// Takes a BytesView instead of (bytes,start,len) so callers can pass
/// subranges directly: `deflate_dynamic(bytes[i:j], true, 8, 1024)`.
///
/// Parameters:
/// - `data`: Input window to compress (treated as an independent block)
/// - `is_final`: Whether this block is the last (sets BFINAL bit)
/// - `good_match`: LZ77 early-exit threshold for match search effort
/// - `max_chain`: Maximum hash chain traversal depth (search effort)
///
/// Returns: A complete DEFLATE block (header + compressed payload).
let len = data.length()
if len == 0 {
let output = @bytebuf.new(size_hint=10)
let writer = @bitstream.BitWriter::new(output)
// For empty input we emit a (possibly final) *fixed* Huffman block (BTYPE=01).
// Rationale: avoids building dynamic trees & keeps a canonical minimal representation.
// Previous implementation (before BytesView refactor) used fixed here; restoring to
// preserve test expectations and spec correctness (a dynamic header without trees is invalid).
let header = if is_final { 0b011 } else { 0b010 } // BFINAL + BTYPE=01 (fixed)
writer.write_bits(header, 3)
// End-of-block only symbol using fixed encoder
writer.write_literal_symbol(
@huffman.fixed_litlen_encoder, litlen_end_of_block_sym,
)
writer.flush()
return output.contents()
}
// Step 1: Run LZ77 and collect frequencies directly on BytesView (no copy)
let hash_head = Array::make(@lz77.hash_size, @lz77.no_pos)
let hash_prev = Array::make(@lz77.window_size, 0)
let freqs = FrequencyCounter::new()
let max_symbols = len + 1000
let symbols = Array::make(max_symbols, 0)
let mut sym_idx = 0
let max_pos = len - @lz77.min_match_len
fn compress_and_count(pos : Int, prev_bref : Int) -> Unit {
if pos > max_pos {
let prev_len = @lz77.backref_len(prev_bref)
let final_pos = if prev_len == 0 {
pos
} else {
let dist = @lz77.backref_dist(prev_bref)
freqs.add_length(prev_len)
freqs.add_distance(dist)
symbols[sym_idx] = prev_bref
sym_idx = sym_idx + 1
pos - 1 + prev_len
}
let end = len
for i = final_pos; i < end; i = i + 1 {
let byte = data[i].to_int()
freqs.add_literal(byte)
symbols[sym_idx] = byte
sym_idx = sym_idx + 1
}
freqs.add_end_of_block()
} else {
if pos + 4 > len {
if prev_bref > 0 {
let prev_len = @lz77.backref_len(prev_bref)
let dist = @lz77.backref_dist(prev_bref)
freqs.add_length(prev_len)
freqs.add_distance(dist)
symbols[sym_idx] = prev_bref
sym_idx = sym_idx + 1
compress_and_count(pos - 1 + prev_len, 0)
} else {
let byte = data[pos].to_int()
freqs.add_literal(byte)
symbols[sym_idx] = byte
sym_idx = sym_idx + 1
compress_and_count(pos + 1, 0)
}
return
}
let hash = @lz77.hash4(data[pos:])
let max_match = (len - pos).min(@lz77.max_match_len)
let prev_len = @lz77.backref_len(prev_bref)
let cur_bref = @lz77.find_backref(
data, hash_head, hash_prev, pos, hash, prev_len, max_match, good_match, max_chain,
)
let cur_len = @lz77.backref_len(cur_bref)
@lz77.insert_hash(hash_head, hash_prev, hash, pos)
if prev_len > 0 && prev_len >= cur_len {
let dist = @lz77.backref_dist(prev_bref)
freqs.add_length(prev_len)
freqs.add_distance(dist)
symbols[sym_idx] = prev_bref
sym_idx = sym_idx + 1
let next = pos - 1 + prev_len
let last = (next - 1).min(max_pos)
for j = pos + 1; j <= last; j = j + 1 {
if j + 3 < len {
let h = @lz77.hash4(data[j:])
@lz77.insert_hash(hash_head, hash_prev, h, j)
}
}
compress_and_count(next, 0)
} else if cur_len == 0 {
if prev_len > 0 {
let byte = data[pos - 1].to_int()
freqs.add_literal(byte)
symbols[sym_idx] = byte
sym_idx = sym_idx + 1
} else {
let byte = data[pos].to_int()
freqs.add_literal(byte)
symbols[sym_idx] = byte
sym_idx = sym_idx + 1
}
compress_and_count(pos + 1, 0)
} else {
if prev_len > 0 {
let byte = data[pos - 1].to_int()
freqs.add_literal(byte)
symbols[sym_idx] = byte
sym_idx = sym_idx + 1
}
compress_and_count(pos + 1, cur_bref)
}
}
}
compress_and_count(0, 0)
// Step 2: Build Huffman trees
let litlen_lengths = build_optimal_code_lengths(freqs.litlen_freqs, 285, 15)
let litlen_encoder = build_canonical_huffman(litlen_lengths, 285)
let mut litlen_max = 285
while litlen_max > 256 && litlen_lengths[litlen_max] == 0 {
litlen_max = litlen_max - 1
}
let hlit = litlen_max + 1 - 257
let dist_lengths = build_optimal_code_lengths(freqs.dist_freqs, 29, 15)
let mut dist_encoder = build_canonical_huffman(dist_lengths, 29)
let mut dist_max = 29
while dist_max > 0 && dist_lengths[dist_max] == 0 {
dist_max = dist_max - 1
}
let hdist = if dist_max == 0 && dist_lengths[0] == 0 {
dist_lengths[0] = 1
dist_encoder = @huffman.HuffmanEncoder::new()
dist_encoder.set(0, @huffman.sym_info_make(0, 1))
0
} else {
dist_max + 1 - 1
}
// Step 3: Encode code lengths
let (codelen_syms, codelen_freqs, codelen_count) = encode_code_lengths(
litlen_lengths, dist_lengths, hlit, hdist,
)
// Step 4: Build codelen tree
let codelen_lengths = build_optimal_code_lengths(codelen_freqs, 18, 7)
let codelen_encoder = build_canonical_huffman(codelen_lengths, 18)
// Step 5: Write dynamic block
let output = @bytebuf.new(size_hint=len * 2)
let writer = @bitstream.BitWriter::new(output)
let header = if is_final { 0b101 } else { 0b100 }
writer.write_bits(header, 3)
write_dynamic_header(
writer, litlen_encoder, dist_encoder, codelen_encoder, codelen_syms, codelen_count,
hlit, hdist,
)
// Step 6: Write symbols
for i = 0; i < sym_idx; i = i + 1 {
let sym = symbols[i]
let dist = @lz77.backref_dist(sym)
if dist == 0 {
let lit = @lz77.backref_len(sym)
writer.write_literal_symbol(litlen_encoder, lit)
} else {
let length = @lz77.backref_len(sym)
write_length_distance(writer, litlen_encoder, dist_encoder, length, dist)
}
}
writer.write_literal_symbol(litlen_encoder, litlen_end_of_block_sym)
writer.flush()
output.contents()
}
// ============================================================================
// Compression Level Enum
// ============================================================================
///|
/// Compression level controlling the speed vs compression ratio tradeoff.
///
/// Each level adjusts multiple parameters that affect both encoding time and output size:
/// - **LZ77 Parameters**: match quality thresholds and search depth
/// - **Huffman Strategy**: fixed vs dynamic trees
/// - **Block Size Heuristics**: when to prefer different compression methods
///
/// ## Performance Characteristics
/// - `None`: Fastest encoding, largest output (stored blocks only)
/// - `Fast`: Quick encoding, moderate compression (fixed Huffman, shallow search)
/// - `Default`: Balanced speed/size (dynamic Huffman, reasonable search)
/// - `Best`: Slowest encoding, smallest output (dynamic Huffman, deep search)
///
/// ## Implementation Details
/// - `None`: Uses `deflate_stored()` exclusively
/// - `Fast`: `deflate_fixed()` with good_match=4, max_chain=128
/// - `Default`: `deflate_dynamic()` with good_match=8, max_chain=1024
/// - `Best`: `deflate_dynamic()` with good_match=32, max_chain=4096
pub(all) enum DeflateLevel {
None // No compression, use stored blocks only
Fast // Fast compression with fixed Huffman
Default // Default compression with dynamic Huffman
Best // Best compression with maximum effort
} derive(Eq, Debug)
///|
pub extend DeflateLevel with Eq::{not_equal, equal}
///|
pub extend DeflateLevel with Debug::{to_repr}
// ============================================================================
// High-level DEFLATE API Functions
// ============================================================================
///|
/// High-level DEFLATE compression with automatic strategy selection.
///
/// This is the main entry point for DEFLATE compression, automatically choosing
/// the optimal compression strategy based on the specified level and input characteristics.
///
/// ## Strategy Selection Logic
/// 1. **Level-based**: DeflateLevel determines algorithm parameters
/// 2. **Size-based**: Small inputs (<256 bytes) prefer fixed Huffman to avoid overhead
/// 3. **Final Block**: Always produces a single final block (BFINAL=1)
///
/// ## Algorithm Selection
/// - `None`: Stored blocks (no compression)
/// - `Fast`: Fixed Huffman with minimal LZ77 effort
/// - `Default`/`None`: Dynamic Huffman for data ≥256 bytes, fixed for smaller
/// - `Best`: Dynamic Huffman with maximum LZ77 search effort
///
/// ## Parameters
/// - `data`: input slice (`BytesView`) to compress
/// - `level`: optional compression level (defaults to `Default`)
///
/// ## Returns
/// Complete DEFLATE stream (RFC 1951) suitable for gzip, zlib, or ZIP usage.
///
/// ## Limitations
/// - Single block output only (no streaming segmentation)
/// - Maximum effective input size: ~65KB for stored, unlimited for compressed
/// - No preset dictionary support
pub fn deflate(data : BytesView, level? : DeflateLevel) -> Bytes raise {
let len = data.length()
let (good_match, max_chain, use_dynamic) = match level {
Some(DeflateLevel::None) =>
// No compression: return stored (uncompressed) block
return deflate_stored(data)
Some(DeflateLevel::Fast) => (4, 128, false) // Fast: fixed Huffman only
Some(DeflateLevel::Default) | None => (8, 1024, true) // Default: dynamic Huffman
Some(DeflateLevel::Best) => (32, 4096, true) // Best: dynamic Huffman with max effort
}
if use_dynamic && len >= 256 {
deflate_dynamic(data, true, good_match, max_chain)
} else {
deflate_fixed(data, true, good_match, max_chain)
}
}
///|
/// Compute CRC-32 checksum and DEFLATE compress in one operation.
///
/// This convenience function combines CRC-32 calculation with DEFLATE compression,
/// which is a common pattern for formats like gzip and ZIP that require both
/// integrity checking and compression.
///
/// ## Operation Order
/// 1. **CRC-32 Calculation**: Computed on original uncompressed data
/// 2. **DEFLATE Compression**: Applied to same input data
/// 3. **Return Tuple**: (checksum, compressed_bytes)
///
/// ## Use Cases
/// - **gzip Format**: Requires CRC-32 of original data in footer
/// - **ZIP Format**: Central directory stores CRC-32 of uncompressed content
/// - **zlib Format**: Includes Adler-32 (similar concept, different algorithm)
///
/// ## Parameters
/// - `data`: input slice (`BytesView`) to process
/// - `level`: optional compression level (defaults to `Default`)
///
/// ## Returns
/// Tuple of (CRC-32 checksum as UInt, DEFLATE compressed bytes).
///
/// ## Performance Note
/// CRC-32 calculation adds minimal overhead (~1-2% of compression time)
/// since it's a simple linear pass over the input data.
// NOTE: Convenience wrappers `crc32_and_deflate` and `adler32_and_deflate`
// have been removed. Callers should now compute the checksum explicitly on
// the uncompressed input before (or after) invoking `deflate` as needed.
// Example:
// let crc = @crc32.bytes_crc32(data)
// let compressed = deflate(data, level?)
// ============================================================================
// zlib Wrapper Format (RFC 1950)
// ============================================================================
///|
/// Compress data with zlib wrapper format (RFC 1950)
/// Returns (Adler-32 checksum, compressed bytes)
///
/// zlib format:
/// - 2 bytes: CMF + FLG header
/// - N bytes: deflate compressed data
/// - 4 bytes: Adler-32 checksum (big-endian)
/// Produce a zlib (RFC 1950) wrapped deflate stream.
/// Returns (adler32, bytes) where checksum is of original data.
pub fn zlib_compress(data : BytesView, level? : DeflateLevel) -> (UInt, Bytes) {
let len = data.length()
// Determine compression parameters
let (good_match, max_chain, flevel) = match level {
Some(DeflateLevel::None) => (4, 128, 0)
Some(DeflateLevel::Fast) => (4, 128, 1)
Some(DeflateLevel::Default) | None => (8, 1024, 2)
Some(DeflateLevel::Best) => (32, 4096, 3)
}
let output = @bytebuf.new(size_hint=len + 100)
// Write CMF (Compression Method and Flags)
// Bits 0-3: CM (compression method) = 8 for deflate
// Bits 4-7: CINFO (window size) = 7 for 32KB window
let cmf = (7 << 4) | 8 // 0x78 = 120
output.write_byte(cmf.to_byte())
// Write FLG (Flags)
// Bits 0-4: FCHECK (check bits to make (CMF*256 + FLG) % 31 == 0)
// Bit 5: FDICT = 0 (no preset dictionary)
// Bits 6-7: FLEVEL (compression level)
let flg_base = flevel << 6
let header = (cmf << 8) | flg_base
let fcheck = (31 - header.mod(31)).mod(31)
let flg = flg_base | fcheck
output.write_byte(flg.to_byte())
// Write deflate compressed data
let compressed = deflate_fixed(data, true, good_match, max_chain)
for i = 0; i < compressed.length(); i = i + 1 {
output.write_byte(compressed[i])
}
// Compute Adler-32 checksum of uncompressed data
let adler = @adler32.bytes_adler32(data)
// Write Adler-32 checksum (big-endian, 4 bytes)
let a32 = adler.reinterpret_as_int()
output.write_byte(((a32 >> 24) & 0xFF).to_byte())
output.write_byte(((a32 >> 16) & 0xFF).to_byte())
output.write_byte(((a32 >> 8) & 0xFF).to_byte())
output.write_byte((a32 & 0xFF).to_byte())
(adler, output.contents())
}
///|
/// Decompress zlib format data (RFC 1950)
/// Returns (decompressed bytes, Adler-32 checksum)
/// Validates header and checksum
/// Parse and decompress a zlib wrapper, validating header & Adler-32.
/// Returns (decompressed bytes, adler32) and raises on header/checksum errors.
pub fn zlib_decompress(data : BytesView) -> (Bytes, UInt) raise {
let len = data.length()
if len < 6 {
fail("zlib data too short (minimum 6 bytes)")
}
// Header bytes
let cmf = data[0].to_int()
let flg = data[1].to_int()
let cm = cmf & 0x0F
let cinfo = (cmf >> 4) & 0x0F
if cm != 8 {
fail("Invalid compression method (expected 8 for deflate)")
}
if cinfo > 7 {
fail("Invalid window size")
}
let fdict = (flg >> 5) & 0x01
let header = (cmf << 8) | flg
if header % 31 != 0 {
fail("Invalid zlib header checksum")
}
if fdict != 0 {
fail("Preset dictionary not supported")
}
// Copy bytes (BytesView -> Bytes) for inflate API
let bytes_copy = data.to_owned()
let deflate_len = len - 6
let decompressed = inflate(bytes_copy[2:2 + deflate_len])
// Trailer
let trailer_pos = len - 4
let stored_adler = (data[trailer_pos].to_int() << 24) |
(data[trailer_pos + 1].to_int() << 16) |
(data[trailer_pos + 2].to_int() << 8) |
data[trailer_pos + 3].to_int()
let computed_adler = @adler32.bytes_adler32(decompressed[:])
if computed_adler.reinterpret_as_int() != stored_adler {
fail("Adler-32 checksum mismatch")
}
(decompressed, computed_adler)
}