use std::cell::RefCell;
use std::io;
use zlib_rs::{Inflate, InflateError, InflateFlush, Status};
const ZLIB_HEADER: bool = true;
const WINDOW_BITS: u8 = 15;
thread_local! {
static DECOMPRESSOR: RefCell<(Inflate, Vec<u8>)> = RefCell::new((
Inflate::new(ZLIB_HEADER, WINDOW_BITS),
Vec::with_capacity(4096),
));
static INFLATE_POOL: RefCell<Vec<(Inflate, Vec<u8>)>> = const { RefCell::new(Vec::new()) };
}
fn inflate_into_spare(
inflate: &mut Inflate,
input: &[u8],
out: &mut Vec<u8>,
flush: InflateFlush,
) -> Result<Status, InflateError> {
let before = inflate.total_out();
let status = inflate.decompress_uninit(input, out.spare_capacity_mut(), flush)?;
let produced = (inflate.total_out() - before) as usize;
unsafe { out.set_len(out.len() + produced) };
Ok(status)
}
pub struct InflateReader<'a> {
input: &'a [u8],
in_pos: usize,
decomp: Option<Inflate>,
buf: Vec<u8>,
cursor: usize,
total_out: u64,
max: u64,
eof: bool,
stream_end: bool,
}
impl<'a> InflateReader<'a> {
const CHUNK: usize = 64 * 1024;
const RETAINED_CAPACITY: usize = Self::CHUNK;
const POOL_MAX: usize = 4;
pub fn new(input: &'a [u8], max: u64) -> Self {
let (decomp, buf) = INFLATE_POOL.with(|p| p.borrow_mut().pop()).map_or_else(
|| {
(
Inflate::new(ZLIB_HEADER, WINDOW_BITS),
Vec::with_capacity(Self::CHUNK),
)
},
|(mut decomp, mut buf)| {
decomp.reset(ZLIB_HEADER);
buf.clear();
(decomp, buf)
},
);
Self {
input,
in_pos: 0,
decomp: Some(decomp),
buf,
cursor: 0,
total_out: 0,
max,
eof: false,
stream_end: false,
}
}
#[inline]
pub fn available(&self) -> &[u8] {
&self.buf[self.cursor..]
}
#[inline]
pub fn consume(&mut self, n: usize) {
self.cursor = (self.cursor + n).min(self.buf.len());
}
pub fn ensure(&mut self, need: usize) -> io::Result<bool> {
while self.buf.len() - self.cursor < need {
if self.eof {
return Ok(false);
}
self.pump()?;
}
Ok(true)
}
pub fn is_done(&self) -> bool {
self.eof && self.cursor >= self.buf.len()
}
pub fn total_out(&self) -> u64 {
self.total_out
}
#[inline]
pub fn compressed_progress(&self) -> (usize, usize) {
(self.in_pos, self.input.len())
}
pub fn stream_ended(&self) -> bool {
self.stream_end
}
fn pump(&mut self) -> io::Result<()> {
if self.cursor != 0 {
let remaining = self.buf.len() - self.cursor;
self.buf.copy_within(self.cursor.., 0);
self.buf.truncate(remaining);
self.cursor = 0;
}
let decomp = self
.decomp
.as_mut()
.ok_or_else(|| io::Error::other("InflateReader used after pool return"))?;
if self.buf.len() == self.buf.capacity() {
self.buf.reserve(Self::CHUNK);
}
let prev_in = decomp.total_in();
let prev_out = decomp.total_out();
let status = inflate_into_spare(
decomp,
&self.input[self.in_pos..],
&mut self.buf,
InflateFlush::NoFlush,
)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.as_str()))?;
let new_in = decomp.total_in();
let produced = (decomp.total_out() - prev_out) as usize;
self.in_pos += (new_in - prev_in) as usize;
self.total_out += produced as u64;
if self.total_out > self.max {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("decompressed payload exceeds {} bytes", self.max),
));
}
match status {
Status::StreamEnd => {
self.eof = true;
self.stream_end = true;
}
_ if produced == 0 => {
if self.in_pos >= self.input.len() {
self.eof = true;
} else if new_in == prev_in {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"zlib stream stalled (no progress)",
));
}
}
_ => {}
}
Ok(())
}
}
impl Drop for InflateReader<'_> {
fn drop(&mut self) {
if let Some(decomp) = self.decomp.take() {
let mut buf = std::mem::take(&mut self.buf);
buf.clear();
if buf.capacity() > Self::RETAINED_CAPACITY {
buf.shrink_to(Self::RETAINED_CAPACITY);
}
INFLATE_POOL.with(|p| {
let mut pool = p.borrow_mut();
if pool.len() < Self::POOL_MAX {
pool.push((decomp, buf));
}
});
}
}
}
fn grow_by_observed_ratio(
scratch: &mut Vec<u8>,
decompressor: &Inflate,
compressed_len: usize,
cap: usize,
) {
let consumed = decompressor.total_in() as usize;
let produced = decompressor.total_out() as usize;
let remaining_in = compressed_len.saturating_sub(consumed) as u64;
let projected = if consumed > 0 && produced > 0 {
((produced as u64).saturating_mul(remaining_in) / consumed as u64).saturating_mul(9) / 8
} else {
0
};
let min_grow = scratch.capacity().max(4096);
let want = (projected.min(usize::MAX as u64) as usize)
.max(min_grow)
.min(cap - scratch.len());
scratch.reserve(want);
}
pub fn decompress_zlib_pooled(compressed: &[u8], max_size: u64) -> io::Result<Vec<u8>> {
DECOMPRESSOR.with(|cell| {
let (decompressor, scratch) = &mut *cell.borrow_mut();
decompressor.reset(ZLIB_HEADER);
scratch.clear();
let cap = (max_size as usize).saturating_add(1);
let floor = 4096.min(cap);
let estimated = compressed.len().saturating_mul(2).clamp(floor, cap);
if scratch.capacity() < estimated {
scratch.reserve(estimated - scratch.capacity());
}
let mut input_offset = 0;
loop {
if scratch.len() >= cap {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("decompressed payload exceeds {max_size} bytes"),
));
}
let prev_in = decompressor.total_in();
let prev_out = decompressor.total_out();
let status = inflate_into_spare(
decompressor,
&compressed[input_offset..],
scratch,
InflateFlush::Finish,
)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.as_str()))?;
input_offset = decompressor.total_in() as usize;
if scratch.len() as u64 > max_size {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("decompressed payload exceeds {max_size} bytes"),
));
}
match status {
Status::StreamEnd => break,
Status::Ok => {
grow_by_observed_ratio(scratch, decompressor, compressed.len(), cap);
}
Status::BufError => {
if decompressor.total_in() == prev_in && decompressor.total_out() == prev_out {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"zlib stream truncated (no progress)",
));
}
grow_by_observed_ratio(scratch, decompressor, compressed.len(), cap);
}
}
}
let result = std::mem::take(scratch);
scratch.reserve(4096);
Ok(result)
})
}
#[cfg(test)]
mod tests {
use super::*;
use flate2::Compression;
use flate2::write::ZlibEncoder;
use std::io::Write;
fn zlib(data: &[u8]) -> Vec<u8> {
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data).unwrap();
e.finish().unwrap()
}
fn varied(n: usize) -> Vec<u8> {
let mut s: u64 = 0x9e37_79b9_7f4a_7c15;
(0..n)
.map(|_| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 24) as u8
})
.collect()
}
fn stored_zlib(data: &[u8]) -> Vec<u8> {
assert!(data.len() <= u16::MAX as usize, "one stored block only");
let mut out = vec![0x78, 0x01];
let len = data.len() as u16;
out.push(0x01);
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&(!len).to_le_bytes());
out.extend_from_slice(data);
let (mut a, mut b) = (1u32, 0u32);
for &byte in data {
a = (a + byte as u32) % 65521;
b = (b + a) % 65521;
}
out.extend_from_slice(&(((b << 16) | a).to_be_bytes()));
out
}
#[test]
fn pooled_roundtrip_small_input() {
let original = varied(1024);
let compressed = stored_zlib(&original);
assert_eq!(
decompress_zlib_pooled(&compressed, 64 * 1024).unwrap(),
original
);
assert_eq!(drain_reader(&compressed, original.len()), original);
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_roundtrip_across_chunks() {
let original = varied(200 * 1024);
let compressed = zlib(&original);
let mut r = InflateReader::new(&compressed, 64 * 1024 * 1024);
let mut out = Vec::with_capacity(original.len());
while r.ensure(1).unwrap() {
let n = r.available().len().min(7);
out.extend_from_slice(&r.available()[..n]);
r.consume(n);
}
assert!(r.is_done());
assert_eq!(out, original);
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_ensure_larger_than_chunk() {
let original: Vec<u8> = (0..150 * 1024).map(|i| (i % 256) as u8).collect();
let compressed = zlib(&original);
let mut r = InflateReader::new(&compressed, 64 * 1024 * 1024);
assert!(r.ensure(150 * 1024).unwrap());
assert_eq!(&r.available()[..150 * 1024], &original[..]);
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_keeps_one_window_for_smaller_records() {
INFLATE_POOL.with(|p| p.borrow_mut().clear());
const RECORD: usize = 30 * 1024;
let original = varied(RECORD * 8);
let compressed = zlib(&original);
let mut r = InflateReader::new(&compressed, 64 * 1024 * 1024);
for expected in original.chunks(RECORD) {
assert!(r.ensure(expected.len()).unwrap());
assert_eq!(&r.available()[..expected.len()], expected);
r.consume(expected.len());
assert!(
r.buf.capacity() <= InflateReader::CHUNK,
"sub-window records grew the inflate buffer to {} bytes",
r.buf.capacity()
);
}
assert!(!r.ensure(1).unwrap());
assert!(r.is_done());
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_enforces_max() {
let original = vec![0u8; 1024 * 1024];
let compressed = zlib(&original);
let mut r = InflateReader::new(&compressed, 4096);
assert!(r.ensure(1024 * 1024).is_err());
}
#[test]
#[cfg_attr(miri, ignore)]
fn pooled_high_ratio_stream_roundtrips() {
let original: Vec<u8> = (0..4_000_000u32).map(|i| ((i / 1024) % 7) as u8).collect();
let compressed = zlib(&original);
assert!(
compressed.len() < original.len() / 20,
"fixture not high-ratio"
);
let out = decompress_zlib_pooled(&compressed, 64 * 1024 * 1024).unwrap();
assert_eq!(out, original);
assert!(
out.capacity() < original.len() * 2,
"capacity {} vs data {}",
out.capacity(),
original.len()
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn pooled_oneshot_matches_streaming() {
let original = varied(100_000);
let compressed = zlib(&original);
let one_shot = decompress_zlib_pooled(&compressed, 64 * 1024 * 1024).unwrap();
assert_eq!(one_shot, original);
}
fn drain_reader(compressed: &[u8], n: usize) -> Vec<u8> {
let mut r = InflateReader::new(compressed, 64 * 1024 * 1024);
let mut out = Vec::with_capacity(n);
while r.ensure(1).unwrap() {
let take = r.available().len();
out.extend_from_slice(r.available());
r.consume(take);
}
assert!(r.is_done());
out
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_reuses_pool_state_correctly() {
for n in [10_000usize, 250_000, 1, 80_000] {
let original = varied(n);
assert_eq!(drain_reader(&zlib(&original), n), original, "size {n}");
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn inflate_reader_reuse_after_error() {
{
let compressed = zlib(&varied(500_000));
let mut r = InflateReader::new(&compressed, 4096);
assert!(r.ensure(500_000).is_err());
}
let original = varied(120_000);
assert_eq!(drain_reader(&zlib(&original), 120_000), original);
}
#[test]
#[cfg_attr(miri, ignore)]
fn drop_shrinks_oversized_buffer_before_pooling() {
INFLATE_POOL.with(|p| p.borrow_mut().clear());
let big = varied(2 * 1024 * 1024);
let compressed = zlib(&big);
{
let mut r = InflateReader::new(&compressed, 64 * 1024 * 1024);
assert!(r.ensure(big.len()).unwrap());
assert!(r.buf.capacity() >= big.len(), "buf should grow while alive");
}
let pooled = INFLATE_POOL.with(|p| p.borrow().last().map(|(_, b)| b.capacity()));
assert!(
matches!(pooled, Some(cap) if cap <= InflateReader::RETAINED_CAPACITY),
"pooled buffer not shrunk: {pooled:?}"
);
}
}