use bytes::Bytes;
use flate2::{Compress, Decompress, FlushCompress, FlushDecompress, Status};
pub const DEFAULT_LEVEL: u32 = 6;
pub const DEFAULT_MAX_FRAME_SIZE: u64 = 64 * 1024 * 1024;
const SYNC_FLUSH_TAIL: [u8; 4] = [0x00, 0x00, 0xff, 0xff];
const CHUNK: usize = 8 * 1024;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
#[error("decompression failed")]
Decompress,
#[error("decompressed frame exceeded {0} bytes")]
TooLarge(u64),
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Encoder(Compress);
impl Encoder {
pub fn new() -> Self {
Self::with_level(DEFAULT_LEVEL)
}
pub fn with_level(level: u32) -> Self {
Self(Compress::new(flate2::Compression::new(level.min(9)), false))
}
pub fn frame(&mut self, payload: &[u8]) -> Bytes {
if payload.is_empty() {
return Bytes::new();
}
let mut out = Vec::with_capacity(payload.len() / 2 + 16);
let mut tmp = [0u8; CHUNK];
let mut input = payload;
loop {
let before_in = self.0.total_in();
let before_out = self.0.total_out();
self.0.compress(input, &mut tmp, FlushCompress::Sync).expect("deflate");
let consumed = (self.0.total_in() - before_in) as usize;
let produced = (self.0.total_out() - before_out) as usize;
out.extend_from_slice(&tmp[..produced]);
input = &input[consumed..];
if produced < tmp.len() {
break;
}
}
debug_assert!(
out.ends_with(&SYNC_FLUSH_TAIL),
"a sync flush must end in the deflate marker"
);
out.truncate(out.len() - SYNC_FLUSH_TAIL.len());
Bytes::from(out)
}
}
impl Default for Encoder {
fn default() -> Self {
Self::new()
}
}
pub struct Decoder {
inner: Decompress,
max_frame_size: u64,
}
impl Decoder {
pub fn new() -> Self {
Self::with_max_frame_size(DEFAULT_MAX_FRAME_SIZE)
}
pub fn with_max_frame_size(max_frame_size: u64) -> Self {
Self {
inner: Decompress::new(false),
max_frame_size,
}
}
pub fn frame(&mut self, slice: &[u8]) -> Result<Bytes> {
if slice.is_empty() {
return Ok(Bytes::new());
}
let mut out = Vec::new();
let mut tmp = [0u8; CHUNK];
for segment in [slice, &SYNC_FLUSH_TAIL] {
let mut input = segment;
loop {
let before_in = self.inner.total_in();
let before_out = self.inner.total_out();
let status = self
.inner
.decompress(input, &mut tmp, FlushDecompress::Sync)
.map_err(|_| Error::Decompress)?;
let consumed = (self.inner.total_in() - before_in) as usize;
let produced = (self.inner.total_out() - before_out) as usize;
if out.len() as u64 + produced as u64 > self.max_frame_size {
return Err(Error::TooLarge(self.max_frame_size));
}
out.extend_from_slice(&tmp[..produced]);
input = &input[consumed..];
if matches!(status, Status::StreamEnd) || (input.is_empty() && produced < tmp.len()) {
break;
}
if consumed == 0 && produced == 0 {
break;
}
}
}
Ok(Bytes::from(out))
}
}
impl Default for Decoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod test {
use super::*;
fn roundtrip(frames: &[&[u8]]) -> Vec<Vec<u8>> {
let mut enc = Encoder::new();
let slices: Vec<Bytes> = frames.iter().map(|f| enc.frame(f)).collect();
let mut dec = Decoder::new();
slices.iter().map(|s| dec.frame(s).unwrap().to_vec()).collect()
}
#[test]
fn stream_roundtrip() {
let frames: &[&[u8]] = &[b"the quick brown fox", b"the quick brown dog", b"the lazy fox"];
let got = roundtrip(frames);
for (a, b) in frames.iter().zip(&got) {
assert_eq!(*a, b.as_slice());
}
}
#[test]
fn empty_frames_roundtrip() {
assert!(Encoder::new().frame(b"").is_empty());
assert!(Decoder::new().frame(b"").unwrap().is_empty());
}
#[test]
fn cross_frame_context_shrinks() {
let payload = b"Media over QUIC delivers real-time latency at massive scale.".repeat(6);
let mut enc = Encoder::new();
let first = enc.frame(&payload);
let second = enc.frame(&payload);
assert!(
second.len() < first.len(),
"repeat frame {} should be smaller than first {}",
second.len(),
first.len()
);
}
#[test]
fn frame_larger_than_chunk_roundtrips() {
let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
let payload: Vec<u8> = (0..64 * 1024)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 56) as u8
})
.collect();
let mut enc = Encoder::new();
let slice = enc.frame(&payload);
assert!(slice.len() > CHUNK, "slice {} should exceed CHUNK {CHUNK}", slice.len());
let mut dec = Decoder::new();
assert_eq!(dec.frame(&slice).unwrap(), Bytes::from(payload));
}
#[test]
fn decompress_rejects_garbage() {
let mut dec = Decoder::new();
assert_eq!(dec.frame(b"not a deflate stream at all"), Err(Error::Decompress));
}
#[test]
fn enforces_max_frame_size() {
let payload = vec![0u8; 1024];
let slice = Encoder::new().frame(&payload);
let mut dec = Decoder::with_max_frame_size(512);
assert_eq!(dec.frame(&slice), Err(Error::TooLarge(512)));
}
#[test]
fn custom_level_roundtrips() {
let payload = b"compress me at maximum effort".repeat(8);
let mut enc = Encoder::with_level(9);
let slice = enc.frame(&payload);
let mut dec = Decoder::new();
assert_eq!(dec.frame(&slice).unwrap(), Bytes::from(payload));
}
}