use std::cell::RefCell;
use std::io::{Error, ErrorKind, Result};
use flate2::{Compress, Compression, Crc, Decompress, FlushCompress, FlushDecompress, Status};
const GZIP_LEVEL: u32 = 6;
const HEADER_LEN: usize = 10;
const TRAILER_LEN: usize = 8;
const SCRATCH_CHUNK: usize = 65_536;
const GZIP_HEADER: [u8; HEADER_LEN] = [0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff];
thread_local! {
static DEFLATE: RefCell<Compress> =
RefCell::new(Compress::new(Compression::new(GZIP_LEVEL), false));
static INFLATE: RefCell<Decompress> = RefCell::new(Decompress::new(false));
static SCRATCH: RefCell<Vec<u8>> = RefCell::new(vec![0; SCRATCH_CHUNK]);
}
#[must_use]
pub fn compress(plain: &[u8]) -> Vec<u8> {
DEFLATE.with_borrow_mut(|deflate| {
SCRATCH.with_borrow_mut(|scratch| {
let hint = plain
.len()
.saturating_add(HEADER_LEN)
.saturating_add(TRAILER_LEN)
.saturating_add(16);
let mut out = Vec::with_capacity(hint);
out.extend_from_slice(&GZIP_HEADER);
run_deflate(deflate, scratch, plain, &mut out);
let mut crc = Crc::new();
crc.update(plain);
out.extend_from_slice(&crc.sum().to_le_bytes());
out.extend_from_slice(&plaintext_isize(plain).to_le_bytes());
out
})
})
}
pub fn decompress(stored: &[u8]) -> Result<Vec<u8>> {
let body = gzip_body(stored)?;
let mut plain = Vec::new();
INFLATE.with_borrow_mut(|inflate| {
SCRATCH.with_borrow_mut(|scratch| run_inflate(inflate, scratch, body, &mut plain))
})?;
verify_trailer(stored, &plain)?;
Ok(plain)
}
fn gzip_body(stored: &[u8]) -> Result<&[u8]> {
let Some(body_end) = stored.len().checked_sub(TRAILER_LEN) else {
return Err(Error::new(
ErrorKind::UnexpectedEof,
"stored object is too short to be a gzip member",
));
};
match stored {
[0x1f, 0x8b, 0x08, 0x00, ..] => {}
[0x1f, 0x8b, 0x08, ..] => {
return Err(Error::new(
ErrorKind::InvalidData,
"stored object sets unsupported gzip header flags",
));
}
[0x1f, 0x8b, ..] => {
return Err(Error::new(
ErrorKind::InvalidData,
"stored object uses an unsupported gzip compression method",
));
}
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"stored object is not gzip (bad magic); it may be a legacy uncompressed object",
));
}
}
stored.get(HEADER_LEN..body_end).ok_or_else(|| {
Error::new(
ErrorKind::UnexpectedEof,
"stored object is too short to be a gzip member",
)
})
}
fn verify_trailer(stored: &[u8], plain: &[u8]) -> Result<()> {
let trailer_start = stored
.len()
.checked_sub(TRAILER_LEN)
.expect("decompress validated the stored length before inflating");
let trailer: [u8; TRAILER_LEN] = stored
.get(trailer_start..)
.and_then(|tail| <[u8; TRAILER_LEN]>::try_from(tail).ok())
.expect("the trailer is exactly eight bytes");
let [c0, c1, c2, c3, i0, i1, i2, i3] = trailer;
let expected_crc = u32::from_le_bytes([c0, c1, c2, c3]);
let expected_isize = u32::from_le_bytes([i0, i1, i2, i3]);
let mut crc = Crc::new();
crc.update(plain);
if crc.sum() != expected_crc {
return Err(Error::new(
ErrorKind::InvalidData,
"stored object failed its gzip CRC-32 check",
));
}
if plaintext_isize(plain) != expected_isize {
return Err(Error::new(
ErrorKind::InvalidData,
"stored object length does not match its gzip ISIZE",
));
}
Ok(())
}
#[expect(
clippy::cast_possible_truncation,
reason = "gzip ISIZE is defined as the input length modulo 2^32"
)]
fn plaintext_isize(plain: &[u8]) -> u32 {
plain.len() as u32
}
fn run_deflate(deflate: &mut Compress, scratch: &mut [u8], input: &[u8], out: &mut Vec<u8>) {
deflate.reset();
loop {
let consumed = usize::try_from(deflate.total_in())
.expect("a compressed body shorter than usize::MAX was processed");
let remaining = input
.get(consumed..)
.expect("the deflate input cursor never passes the input end");
let produced_before = deflate.total_out();
let status = deflate
.compress(remaining, scratch, FlushCompress::Finish)
.expect("deflate into an in-memory buffer cannot fail");
let produced = usize::try_from(
deflate
.total_out()
.checked_sub(produced_before)
.expect("the output counter is monotonic"),
)
.expect("an output shorter than usize::MAX was produced");
out.extend_from_slice(
scratch
.get(..produced)
.expect("a pass never produces more than the scratch window holds"),
);
if matches!(status, Status::StreamEnd) {
break;
}
}
}
fn run_inflate(
inflate: &mut Decompress,
scratch: &mut [u8],
body: &[u8],
out: &mut Vec<u8>,
) -> Result<()> {
inflate.reset(false);
out.reserve(body.len().max(64));
loop {
let consumed = usize::try_from(inflate.total_in())
.expect("a compressed body shorter than usize::MAX was processed");
let remaining = body
.get(consumed..)
.expect("the inflate input cursor never passes the body end");
let produced_before = inflate.total_out();
let status = inflate
.decompress(remaining, scratch, FlushDecompress::None)
.map_err(|error| Error::new(ErrorKind::InvalidData, error))?;
let produced = usize::try_from(
inflate
.total_out()
.checked_sub(produced_before)
.expect("the output counter is monotonic"),
)
.expect("an output shorter than usize::MAX was produced");
out.extend_from_slice(
scratch
.get(..produced)
.expect("a pass never produces more than the scratch window holds"),
);
if matches!(status, Status::StreamEnd) {
return Ok(());
}
if produced == 0 {
return Err(Error::new(
ErrorKind::UnexpectedEof,
"gzip body ended before the deflate stream was complete",
));
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
#![allow(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
reason = "panic is fine in tests"
)]
use std::io::{Read as _, Write as _};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use super::*;
fn sample_json() -> Vec<u8> {
let record = r#"{"id":["fast_time","capture","two_instants"],"metrics":[{"kind":"WallTime","value":12.5}]},"#;
let mut body = String::from(r#"{"schema":1,"results":["#);
for _ in 0..64 {
body.push_str(record);
}
body.push_str("]}");
body.into_bytes()
}
fn incompressible() -> Vec<u8> {
incompressible_of(200_000)
}
fn incompressible_of(len: usize) -> Vec<u8> {
let mut state = 0x1234_5678_u32;
std::iter::repeat_with(|| {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
u8::try_from(state >> 24).expect("a byte-wide shift yields a single byte")
})
.take(len)
.collect()
}
#[test]
fn roundtrips_representative_json() {
let plain = sample_json();
let restored = decompress(&compress(&plain)).unwrap();
assert_eq!(restored, plain);
}
#[test]
fn roundtrips_empty_input() {
let restored = decompress(&compress(b"")).unwrap();
assert_eq!(restored, b"");
}
#[test]
fn roundtrips_non_utf8_bytes() {
let plain: Vec<u8> = (0_u8..=255).cycle().take(1000).collect();
let restored = decompress(&compress(&plain)).unwrap();
assert_eq!(restored, plain);
}
#[test]
fn roundtrips_incompressible_input_growing_both_buffers() {
let plain = incompressible();
let compressed = compress(&plain);
let restored = decompress(&compressed).unwrap();
assert_eq!(restored, plain);
}
#[test]
fn reuses_thread_state_across_many_calls() {
for seed in 0..50_u32 {
let plain: Vec<u8> = (0..1000).map(|i| (i ^ seed).to_le_bytes()[0]).collect();
assert_eq!(decompress(&compress(&plain)).unwrap(), plain);
}
}
#[test]
fn is_deterministic() {
let plain = sample_json();
assert_eq!(compress(&plain), compress(&plain));
}
#[test]
fn emits_gzip_magic() {
let compressed = compress(&sample_json());
assert!(
compressed.starts_with(&[0x1f, 0x8b]),
"a stored body must carry the gzip magic so legacy plaintext is distinguishable"
);
}
#[test]
fn output_is_standard_gzip_readable_by_a_stock_decoder() {
let plain = sample_json();
let compressed = compress(&plain);
let mut decoder = GzDecoder::new(&compressed[..]);
let mut restored = Vec::new();
decoder.read_to_end(&mut restored).unwrap();
assert_eq!(restored, plain);
}
#[test]
fn decompresses_output_from_a_stock_encoder() {
let plain = sample_json();
let mut encoder = GzEncoder::new(Vec::new(), Compression::new(GZIP_LEVEL));
encoder.write_all(&plain).unwrap();
let compressed = encoder.finish().unwrap();
let restored = decompress(&compressed).unwrap();
assert_eq!(restored, plain);
}
#[test]
fn compresses_repetitive_input() {
let plain = sample_json();
let compressed = compress(&plain);
assert!(
compressed.len() < plain.len(),
"repetitive JSON must shrink: {} -> {}",
plain.len(),
compressed.len()
);
}
#[test]
fn decompress_rejects_plaintext_json() {
let error = decompress(br#"{"schema":1,"results":[],"padding":"xxxxxxxxxx"}"#).unwrap_err();
drop(error);
}
#[test]
fn decompress_rejects_unsupported_method_and_flags() {
let mut bad_method = compress(&sample_json());
*bad_method.get_mut(2).expect("header byte present") = 0x07;
let error = decompress(&bad_method).unwrap_err();
assert!(
error.to_string().contains("compression method"),
"an unsupported method must be named as such: {error}"
);
let mut bad_flags = compress(&sample_json());
*bad_flags.get_mut(3).expect("header byte present") = 0x08;
let error = decompress(&bad_flags).unwrap_err();
assert!(
error.to_string().contains("flags"),
"unsupported header flags must be named as such: {error}"
);
}
#[test]
fn decompress_rejects_a_short_body_with_valid_magic() {
let mut stored = GZIP_HEADER[..4].to_vec();
stored.extend_from_slice(&[0_u8; TRAILER_LEN]);
assert_eq!(stored.len(), 12, "magic plus a trailer, but no full header");
let error = decompress(&stored).unwrap_err();
assert!(
error.to_string().contains("too short"),
"a header-less body must be reported as too short: {error}"
);
}
#[test]
fn decompress_rejects_truncated_stream() {
let mut compressed = compress(&sample_json());
compressed.truncate(compressed.len().div_ceil(2));
let error = decompress(&compressed).unwrap_err();
drop(error);
}
#[test]
fn decompress_rejects_empty_input() {
let error = decompress(b"").unwrap_err();
drop(error);
}
#[test]
fn decompress_rejects_corrupt_crc() {
let mut compressed = compress(&sample_json());
let crc_index = compressed.len() - TRAILER_LEN;
*compressed.get_mut(crc_index).expect("trailer byte present") ^= 0xff;
decompress(&compressed).unwrap_err();
}
#[test]
fn decompress_rejects_corrupt_length() {
let mut compressed = compress(&sample_json());
let isize_index = compressed.len() - 4;
*compressed
.get_mut(isize_index)
.expect("trailer byte present") ^= 0xff;
decompress(&compressed).unwrap_err();
}
#[test]
#[cfg_attr(
miri,
ignore = "multi-megabyte round trips are far too slow under Miri"
)]
fn roundtrips_a_large_compressible_payload_growing_inflate_many_times() {
let plain = vec![b'A'; 2_000_000];
let compressed = compress(&plain);
assert!(
compressed.len().saturating_mul(100) < plain.len(),
"a run of one byte must compress drastically: {} -> {}",
plain.len(),
compressed.len()
);
assert_eq!(decompress(&compressed).unwrap(), plain);
}
#[test]
#[cfg_attr(
miri,
ignore = "multi-megabyte round trips are far too slow under Miri"
)]
fn compress_expands_incompressible_data_forcing_a_deflate_regrow() {
let incompressible = compress(&incompressible_of(1_000_000));
let compressed = compress(&incompressible);
assert!(
compressed.len() > incompressible.len(),
"re-compressing gzip output must expand it: {} -> {}",
incompressible.len(),
compressed.len()
);
assert_eq!(decompress(&compressed).unwrap(), incompressible);
}
}