- crypto: encrypt/decrypt roundtrip, empty data, size formula, HMAC compute/verify, SHA-256 known values - compression: compress/decompress roundtrip, empty data, large data, should_compress heuristic - format: header write/read roundtrip, TOC entry roundtrip (ASCII + Cyrillic + empty name), bad magic/version rejection, entry size calculation matching FORMAT.md worked example - Update hex-literal to v1.1
100 lines
3.0 KiB
Rust
100 lines
3.0 KiB
Rust
use flate2::read::GzDecoder;
|
|
use flate2::{Compression, GzBuilder};
|
|
use std::io::{Read, Write};
|
|
|
|
/// Gzip-compress data with reproducible output (mtime zeroed).
|
|
///
|
|
/// Uses `GzBuilder::new().mtime(0)` to zero the gzip timestamp,
|
|
/// ensuring reproducible compressed output for testing.
|
|
pub fn compress(data: &[u8]) -> anyhow::Result<Vec<u8>> {
|
|
let mut encoder = GzBuilder::new()
|
|
.mtime(0)
|
|
.write(Vec::new(), Compression::default());
|
|
encoder.write_all(data)?;
|
|
let compressed = encoder.finish()?;
|
|
Ok(compressed)
|
|
}
|
|
|
|
/// Gzip-decompress data.
|
|
pub fn decompress(data: &[u8]) -> anyhow::Result<Vec<u8>> {
|
|
let mut decoder = GzDecoder::new(data);
|
|
let mut decompressed = Vec::new();
|
|
decoder.read_to_end(&mut decompressed)?;
|
|
Ok(decompressed)
|
|
}
|
|
|
|
/// Determine if a file should be compressed based on filename and exclusion list.
|
|
///
|
|
/// Returns false for:
|
|
/// - Files matching any entry in `no_compress_list` (by suffix or exact match)
|
|
/// - Files with known compressed extensions (apk, zip, gz, etc.)
|
|
///
|
|
/// Returns true otherwise.
|
|
pub fn should_compress(filename: &str, no_compress_list: &[String]) -> bool {
|
|
// Check explicit exclusion list
|
|
if no_compress_list
|
|
.iter()
|
|
.any(|nc| filename.ends_with(nc) || filename == nc)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check known compressed extensions
|
|
let ext = filename.rsplit('.').next().unwrap_or("").to_lowercase();
|
|
!matches!(
|
|
ext.as_str(),
|
|
"apk" | "zip" | "gz" | "bz2" | "xz" | "zst"
|
|
| "png" | "jpg" | "jpeg" | "gif" | "webp"
|
|
| "mp4" | "mp3" | "aac" | "ogg" | "flac"
|
|
| "7z" | "rar" | "jar"
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_compress_decompress_roundtrip() {
|
|
let data = b"Hello, World! This is test data for compression.";
|
|
let compressed = compress(data).unwrap();
|
|
let decompressed = decompress(&compressed).unwrap();
|
|
assert_eq!(decompressed, data);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compress_decompress_empty() {
|
|
let data = b"";
|
|
let compressed = compress(data).unwrap();
|
|
let decompressed = decompress(&compressed).unwrap();
|
|
assert_eq!(decompressed, data.as_slice());
|
|
}
|
|
|
|
#[test]
|
|
fn test_compress_decompress_large() {
|
|
// 10000 bytes of pattern data
|
|
let data: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
|
|
let compressed = compress(&data).unwrap();
|
|
let decompressed = decompress(&compressed).unwrap();
|
|
assert_eq!(decompressed, data);
|
|
}
|
|
|
|
#[test]
|
|
fn test_should_compress_text() {
|
|
assert!(should_compress("readme.txt", &[]));
|
|
assert!(should_compress("data.json", &[]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_should_not_compress_known_extensions() {
|
|
assert!(!should_compress("app.apk", &[]));
|
|
assert!(!should_compress("photo.jpg", &[]));
|
|
assert!(!should_compress("archive.zip", &[]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_should_not_compress_excluded() {
|
|
assert!(!should_compress("special.dat", &["special.dat".into()]));
|
|
}
|
|
}
|