use std::io::Read;
use ruzstd::decoding::StreamingDecoder;
use ruzstd::encoding::CompressionLevel;
use crate::varint;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Compression {
#[default]
None,
Lz4,
Snappy,
Zstd,
Deflate {
level: u8,
},
}
const NONE: u8 = 0;
const LZ4: u8 = 1;
const SNAPPY: u8 = 2;
const ZSTD: u8 = 3;
const DEFLATE: u8 = 4;
const LZ4_MAX_RATIO: usize = 255;
const SNAPPY_MAX_RATIO: usize = 22;
const ZSTD_RESERVE_RATIO: usize = 8;
pub(super) fn compress(compression: Compression, payload: &[u8]) -> Option<(Vec<u8>, u8)> {
let length = u32::try_from(payload.len()).ok()?;
if payload.is_empty() {
return None;
}
let mut sealed = Vec::new();
varint::put_u32(&mut sealed, length);
let byte = match compression {
Compression::None => return None,
Compression::Lz4 => {
sealed.extend_from_slice(&lz4_flex::block::compress(payload));
LZ4
}
Compression::Snappy => {
let body = snap::raw::Encoder::new().compress_vec(payload).ok()?;
sealed.extend_from_slice(&body);
SNAPPY
}
Compression::Zstd => {
ruzstd::encoding::compress(payload, &mut sealed, CompressionLevel::Fastest);
ZSTD
}
Compression::Deflate { level } => {
sealed.extend_from_slice(&miniz_oxide::deflate::compress_to_vec(payload, level));
DEFLATE
}
};
let saved = payload.len().saturating_sub(payload.len() / 8);
(sealed.len() < saved).then_some((sealed, byte))
}
pub(super) fn decompress(byte: u8, stored: &[u8]) -> Result<Vec<u8>, &'static str> {
if byte == NONE {
return Ok(stored.to_vec());
}
if !matches!(byte, LZ4 | SNAPPY | ZSTD | DEFLATE) {
return Err("unknown compression");
}
let (length, used) = varint::get_u32(stored).ok_or("compressed length")?;
let body = stored.get(used..).ok_or("compressed length")?;
let length = usize::try_from(length).map_err(|_| "compressed length")?;
let payload = match byte {
LZ4 => {
bounded(length, body, LZ4_MAX_RATIO)?;
lz4_flex::block::decompress(body, length).map_err(|_| "does not decompress")?
}
SNAPPY => {
bounded(length, body, SNAPPY_MAX_RATIO)?;
if snap::raw::decompress_len(body) != Ok(length) {
return Err("decompresses to another length");
}
snap::raw::Decoder::new()
.decompress_vec(body)
.map_err(|_| "does not decompress")?
}
ZSTD => {
let reserve = length.min(body.len().saturating_mul(ZSTD_RESERVE_RATIO));
let mut payload = Vec::with_capacity(reserve);
let decoder = StreamingDecoder::new(body).map_err(|_| "does not decompress")?;
let limit = u64::try_from(length).map_or(u64::MAX, |limit| limit.saturating_add(1));
decoder
.take(limit)
.read_to_end(&mut payload)
.map_err(|_| "does not decompress")?;
payload
}
_ => miniz_oxide::inflate::decompress_to_vec_with_limit(body, length)
.map_err(|_| "does not decompress")?,
};
if payload.len() == length {
Ok(payload)
} else {
Err("decompresses to another length")
}
}
fn bounded(length: usize, body: &[u8], ratio: usize) -> Result<(), &'static str> {
if length > body.len().saturating_mul(ratio).saturating_add(16) {
Err("claims more than its bytes can expand to")
} else {
Ok(())
}
}