use crate::error::{OxCacheError, OxCacheResult};
pub fn check_data_size(data: &[u8], max_size: usize, data_type: &str) -> OxCacheResult<()> {
if data.len() > max_size {
return Err(OxCacheError::Serialization(format!(
"{} data too large: {} bytes (max: {} bytes)",
data_type,
data.len(),
max_size
)));
}
Ok(())
}
#[cfg(feature = "flate2")]
const MIN_COMPRESS_SIZE: usize = 100;
pub const MAX_DECOMPRESS_SIZE: usize = 64 * 1024 * 1024;
#[cfg(feature = "flate2")]
fn is_gzip(data: &[u8]) -> bool {
data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b
}
#[cfg(feature = "flate2")]
pub fn compress_data(data: &[u8]) -> OxCacheResult<Vec<u8>> {
use flate2::Compression;
use flate2::write::GzEncoder;
use std::io::Write;
if data.len() < MIN_COMPRESS_SIZE {
return Ok(data.to_vec());
}
let compression = if data.len() < 1024 {
Compression::fast()
} else if data.len() < 100 * 1024 {
Compression::new(6)
} else {
Compression::best()
};
let mut encoder = GzEncoder::new(Vec::new(), compression);
encoder
.write_all(data)
.map_err(|e| OxCacheError::Serialization(e.to_string()))?;
let compressed = encoder
.finish()
.map_err(|e| OxCacheError::Serialization(e.to_string()))?;
if compressed.len() >= data.len() {
return Ok(data.to_vec());
}
Ok(compressed)
}
#[cfg(feature = "flate2")]
pub fn decompress_data_with_limit(data: &[u8], max_size: usize) -> OxCacheResult<Vec<u8>> {
if !is_gzip(data) {
return Ok(data.to_vec());
}
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(data).take(max_size as u64 + 1);
let mut decoded = Vec::new();
decoder
.read_to_end(&mut decoded)
.map_err(|e| OxCacheError::Serialization(e.to_string()))?;
if decoded.len() > max_size {
return Err(OxCacheError::Serialization(format!(
"decompressed data too large: {} bytes (max: {} bytes)",
decoded.len(),
max_size
)));
}
Ok(decoded)
}
#[cfg(feature = "flate2")]
pub fn decompress_data(data: &[u8]) -> OxCacheResult<Vec<u8>> {
decompress_data_with_limit(data, MAX_DECOMPRESS_SIZE)
}
#[cfg(not(feature = "flate2"))]
pub fn compress_data(data: &[u8]) -> OxCacheResult<Vec<u8>> {
Ok(data.to_vec())
}
#[cfg(not(feature = "flate2"))]
pub fn decompress_data(data: &[u8]) -> OxCacheResult<Vec<u8>> {
Ok(data.to_vec())
}
#[cfg(not(feature = "flate2"))]
pub fn decompress_data_with_limit(data: &[u8], _max_size: usize) -> OxCacheResult<Vec<u8>> {
Ok(data.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_data_size_within_limit() {
let data = vec![0u8; 1000];
assert!(check_data_size(&data, 2000, "test").is_ok());
}
#[test]
fn test_check_data_size_exceeds_limit() {
let data = vec![0u8; 3000];
assert!(check_data_size(&data, 2000, "test").is_err());
}
#[test]
#[cfg(not(feature = "flate2"))]
fn test_compress_data_no_feature() {
let data = b"hello world";
let compressed = compress_data(data).unwrap();
assert_eq!(compressed, data);
}
#[test]
#[cfg(not(feature = "flate2"))]
fn test_decompress_data_no_feature() {
let data = b"hello world";
let decompressed = decompress_data(data).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_compress_data_with_feature() {
let data = vec![0u8; 200]; let compressed = compress_data(&data).unwrap();
assert_ne!(compressed, data);
let decompressed = decompress_data(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_decompress_data_with_feature() {
let data = vec![0u8; 200]; let compressed = compress_data(&data).unwrap();
let decompressed = decompress_data(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_small_data_not_compressed() {
let data = b"small data";
let compressed = compress_data(data).unwrap();
assert_eq!(compressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_compress_ratio_check_incompressible_data() {
let data: Vec<u8> = (0..200u32).map(|i| (i * 31 % 256) as u8).collect();
let compressed = compress_data(&data).unwrap();
assert!(compressed.len() <= data.len());
let decompressed = decompress_data(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_compress_ratio_check_compressed_input() {
let data = vec![0u8; 500];
let first = compress_data(&data).unwrap();
let second = compress_data(&first).unwrap();
assert!(second.len() <= first.len());
let decompressed = decompress_data(&second).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_decompress_data_with_limit_exceeds() {
let data = vec![0u8; 1024 * 1024]; let compressed = compress_data(&data).unwrap();
assert!(compressed.len() < data.len());
let result = decompress_data_with_limit(&compressed, 1024);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "flate2")]
fn test_decompress_data_with_limit_within() {
let data = vec![0u8; 5000];
let compressed = compress_data(&data).unwrap();
let decompressed = decompress_data_with_limit(&compressed, 64 * 1024).unwrap();
assert_eq!(decompressed, data);
}
#[test]
#[cfg(feature = "flate2")]
fn test_decompress_data_with_limit_uncompressed_passthrough() {
let data = b"not compressed data at all";
let result = decompress_data_with_limit(data, 1024).unwrap();
assert_eq!(result, data);
}
}