Skip to content

Commit 70a8e18

Browse files
authored
Optimize decoding of fixed huffman blocks (#38)
1 parent 4610c91 commit 70a8e18

File tree

1 file changed

+33
-3
lines changed

1 file changed

+33
-3
lines changed

src/decompress.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub struct Decompressor {
103103
queued_rle: Option<(u8, usize)>,
104104
queued_backref: Option<(usize, usize)>,
105105
last_block: bool,
106+
fixed_table: bool,
106107

107108
state: State,
108109
checksum: Adler32,
@@ -145,6 +146,7 @@ impl Decompressor {
145146
state: State::ZlibHeader,
146147
last_block: false,
147148
ignore_adler32: false,
149+
fixed_table: false,
148150
}
149151
}
150152

@@ -182,7 +184,7 @@ impl Decompressor {
182184

183185
fn read_block_header(&mut self, remaining_input: &mut &[u8]) -> Result<(), DecompressionError> {
184186
self.fill_buffer(remaining_input);
185-
if self.nbits < 3 {
187+
if self.nbits < 10 {
186188
return Ok(());
187189
}
188190

@@ -209,8 +211,35 @@ impl Decompressor {
209211
}
210212
0b01 => {
211213
self.consume_bits(3);
212-
// TODO: Do this statically rather than every time.
213-
Self::build_tables(288, &FIXED_CODE_LENGTHS, &mut self.compression)?;
214+
215+
// Check for an entirely empty blocks which can happen if there are "partial
216+
// flushes" in the deflate stream. With fixed huffman codes, the EOF symbol is
217+
// 7-bits of zeros so we peak ahead and see if the next 7-bits are all zero.
218+
if self.peak_bits(7) == 0 {
219+
self.consume_bits(7);
220+
if self.last_block {
221+
self.state = State::Checksum;
222+
return Ok(());
223+
}
224+
225+
// At this point we've consumed the entire block and need to read the next block
226+
// header. If tail call optimization were guaranteed, we could just recurse
227+
// here. But without it, a long sequence of empty fixed-blocks might cause a
228+
// stack overflow. Instead, we consume all empty blocks in a loop and then
229+
// recurse. This is the only recursive call this function, and thus is safe.
230+
while self.nbits >= 10 && self.peak_bits(10) == 0b010 {
231+
self.consume_bits(10);
232+
self.fill_buffer(remaining_input);
233+
}
234+
return self.read_block_header(remaining_input);
235+
}
236+
237+
// Build decoding tables if the previous block wasn't also a fixed block.
238+
if !self.fixed_table {
239+
self.fixed_table = true;
240+
Self::build_tables(288, &FIXED_CODE_LENGTHS, &mut self.compression)?;
241+
}
242+
214243
self.state = State::CompressedData;
215244
Ok(())
216245
}
@@ -231,6 +260,7 @@ impl Decompressor {
231260

232261
self.consume_bits(17);
233262
self.state = State::CodeLengthCodes;
263+
self.fixed_table = false;
234264
Ok(())
235265
}
236266
0b11 => Err(DecompressionError::InvalidBlockType),

0 commit comments

Comments
 (0)