use miniz_oxide::deflate::compress_to_vec;
use miniz_oxide::inflate::{TINFLStatus, decompress_to_vec_with_limit};
use crate::{Error, Result};
pub const MAX_DECOMPRESSED_LEN: usize = 64 * 1024 * 1024;
const LEVEL: u8 = 6;
pub fn compress(plaintext: &[u8]) -> Result<Vec<u8>> {
Ok(compress_to_vec(plaintext, LEVEL))
}
pub fn decompress(compressed: &[u8]) -> Result<Vec<u8>> {
if compressed.is_empty() {
return Err(Error::SyncEmptyPayload);
}
decompress_to_vec_with_limit(compressed, MAX_DECOMPRESSED_LEN).map_err(|error| {
match error.status {
TINFLStatus::HasMoreOutput => Error::SyncPayloadTooLarge {
limit: MAX_DECOMPRESSED_LEN,
},
_ => Error::SyncDecompressionFailed,
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_preserves_content() {
for size in [0_usize, 1, 15, 4096, 100_000] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let packed = compress(&data).expect("可压缩");
let back = decompress(&packed).expect("可解压");
assert_eq!(back, data, "长度 {size} 的往返不一致");
}
}
#[test]
fn repetitive_payload_actually_shrinks() {
let mut data = Vec::new();
for i in 0..500_u32 {
data.extend_from_slice(b"\x01name\x02otpauth://totp/ACME:alice@example.com");
data.extend_from_slice(&i.to_be_bytes());
}
let packed = compress(&data).expect("可压缩");
assert!(
packed.len() * 4 < data.len(),
"压缩后 {} 字节,原 {} 字节——收益太差,压缩层可能没生效",
packed.len(),
data.len()
);
}
#[test]
fn incompressible_payload_still_roundtrips() {
let mut data = vec![0_u8; 8192];
crate::getrandom::fill(&mut data).expect("OS 随机源可用");
let packed = compress(&data).expect("可压缩");
let back = decompress(&packed).expect("可解压");
assert_eq!(back, data);
}
#[test]
fn empty_input_is_reported_as_empty_not_corrupt() {
let err = decompress(&[]);
assert!(matches!(err, Err(Error::SyncEmptyPayload)), "实际 {err:?}");
}
#[test]
fn garbage_is_rejected() {
for garbage in [
vec![0xFF_u8; 64],
vec![0x78, 0x9C],
b"not deflate at all".to_vec(),
] {
let err = decompress(&garbage);
assert!(
matches!(err, Err(Error::SyncDecompressionFailed)),
"垃圾输入 {garbage:?} 未被拒绝,实际 {err:?}"
);
}
}
#[test]
fn a_decompression_bomb_is_rejected() {
let bomb = vec![0_u8; MAX_DECOMPRESSED_LEN + 1];
let packed = compress(&bomb).expect("可压缩");
assert!(
packed.len() < bomb.len() / 100,
"前提不成立:炸弹没被压得足够小(压后 {} 字节)",
packed.len()
);
let err = decompress(&packed);
assert!(
matches!(err, Err(Error::SyncPayloadTooLarge { .. })),
"超过上限的解压未被拒绝,实际 {err:?}"
);
}
#[test]
fn incompressible_data_over_the_limit_is_rejected() {
let mut data = vec![0_u8; MAX_DECOMPRESSED_LEN + 1];
crate::getrandom::fill(&mut data).expect("OS 随机源可用");
let packed = compress(&data).expect("可压缩");
let err = decompress(&packed);
assert!(
matches!(err, Err(Error::SyncPayloadTooLarge { .. })),
"随机数据超限未被拒绝,实际 {err:?}"
);
}
#[test]
fn exactly_at_the_limit_is_accepted() {
let mut data = vec![0_u8; MAX_DECOMPRESSED_LEN];
crate::getrandom::fill(&mut data).expect("OS 随机源可用");
let packed = compress(&data).expect("可压缩");
let back = decompress(&packed).expect("恰好到上限应当通过");
assert_eq!(back.len(), MAX_DECOMPRESSED_LEN);
assert_eq!(back, data, "恰好到上限的内容应当逐字节一致");
}
#[test]
fn truncated_stream_is_rejected() {
let data = vec![3_u8; 4096];
let packed = compress(&data).expect("可压缩");
for cut in 1..packed.len().min(24) {
let truncated = &packed[..packed.len() - cut];
let result = decompress(truncated);
if let Ok(partial) = result {
panic!(
"截断 {cut} 字节后仍解出 {} 字节(原 {} 字节)",
partial.len(),
data.len()
);
}
}
}
}