use crate::{Error, Result};
pub(crate) const COMPRESS_MIN_BYTES: usize = 256;
#[derive(Debug)]
pub(crate) enum Compressed<'a> {
Passthrough { bytes: &'a [u8] },
Encoded { bytes: Vec<u8>, original_len: u32 },
}
#[cfg(feature = "compress")]
#[must_use]
pub(crate) fn compress_into(input: &[u8]) -> Compressed<'_> {
if input.len() < COMPRESS_MIN_BYTES {
return Compressed::Passthrough { bytes: input };
}
let encoded = lz4_flex::compress(input);
if encoded.len() >= input.len() {
return Compressed::Passthrough { bytes: input };
}
Compressed::Encoded {
bytes: encoded,
original_len: input.len() as u32,
}
}
#[cfg(not(feature = "compress"))]
#[must_use]
pub(crate) fn compress_into(input: &[u8]) -> Compressed<'_> {
Compressed::Passthrough { bytes: input }
}
#[cfg(feature = "compress")]
pub(crate) fn decompress_into(
input: &[u8],
compressed: bool,
original_len: u32,
out: &mut Vec<u8>,
) -> Result<()> {
if !compressed {
out.extend_from_slice(input);
return Ok(());
}
let target = original_len as usize;
let decoded =
lz4_flex::decompress(input, target).map_err(|_decompress_err| Error::Corrupted {
offset: 0,
reason: "lz4 decompress rejected the input",
})?;
if decoded.len() != target {
return Err(Error::Corrupted {
offset: 0,
reason: "decompressed length does not match original_len",
});
}
out.extend_from_slice(&decoded);
Ok(())
}
#[cfg(not(feature = "compress"))]
pub(crate) fn decompress_into(
input: &[u8],
compressed: bool,
_original_len: u32,
out: &mut Vec<u8>,
) -> Result<()> {
if compressed {
return Err(Error::InvalidConfig(
"record marked compressed but the `compress` feature is not enabled",
));
}
out.extend_from_slice(input);
Ok(())
}
#[cfg(test)]
mod tests {
use super::{compress_into, decompress_into, Compressed, COMPRESS_MIN_BYTES};
#[test]
fn small_input_passes_through() {
let small = b"hello";
match compress_into(small) {
Compressed::Passthrough { bytes } => assert_eq!(bytes, small),
Compressed::Encoded { .. } => panic!("small input should not compress"),
}
}
#[cfg(feature = "compress")]
#[test]
fn large_repetitive_input_compresses() {
let payload = vec![b'x'; 4 * COMPRESS_MIN_BYTES];
match compress_into(&payload) {
Compressed::Passthrough { .. } => {
panic!("large repetitive input should compress")
}
Compressed::Encoded {
bytes,
original_len,
} => {
assert!(bytes.len() < payload.len());
assert_eq!(original_len as usize, payload.len());
let mut out = Vec::new();
let decoded = decompress_into(&bytes, true, original_len, &mut out);
assert!(decoded.is_ok());
assert_eq!(out, payload);
}
}
}
#[cfg(feature = "compress")]
#[test]
fn random_input_falls_back_to_passthrough_when_compression_grows_it() {
let payload: Vec<u8> = (0..COMPRESS_MIN_BYTES as u8).collect();
let (encoded_bytes, compressed_flag, original_len) = match compress_into(&payload) {
Compressed::Passthrough { bytes } => (bytes.to_vec(), false, bytes.len() as u32),
Compressed::Encoded {
bytes,
original_len,
} => (bytes, true, original_len),
};
let mut out = Vec::new();
let decoded = decompress_into(&encoded_bytes, compressed_flag, original_len, &mut out);
assert!(decoded.is_ok());
assert_eq!(out, payload);
}
#[test]
fn passthrough_round_trips_with_compressed_false() {
let payload = b"hello world";
let mut out = Vec::new();
let decoded = decompress_into(payload, false, payload.len() as u32, &mut out);
assert!(decoded.is_ok());
assert_eq!(out, payload);
}
#[cfg(not(feature = "compress"))]
#[test]
fn compressed_flag_without_feature_returns_invalid_config() {
let mut out = Vec::new();
let decoded = decompress_into(&[1, 2, 3], true, 3, &mut out);
assert!(decoded.is_err());
}
#[test]
fn threshold_constant_is_locked_for_format_compatibility() {
assert_eq!(COMPRESS_MIN_BYTES, 256);
}
}