use super::base64_encode;
use super::deflate_compress;
use crate::utils::crc32::calculate_crc32;
use crate::{Transform, TransformError, TransformerCategory};
use std::time::{SystemTime, UNIX_EPOCH};
const ID1: u8 = 0x1f;
const ID2: u8 = 0x8b;
const CM_DEFLATE: u8 = 8;
const OS_UNKNOWN: u8 = 255;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GzipCompress;
impl Transform for GzipCompress {
fn name(&self) -> &'static str {
"Gzip Compress"
}
fn id(&self) -> &'static str {
"gzipcompress"
}
fn category(&self) -> TransformerCategory {
TransformerCategory::Compression
}
fn description(&self) -> &'static str {
"Compresses input using Gzip (RFC 1952) and encodes the output as Base64."
}
fn default_test_input(&self) -> &'static str {
"Hello, Gzip World!"
}
fn transform(&self, input: &str) -> Result<String, TransformError> {
let input_bytes = input.as_bytes();
let deflated_data = deflate_compress::deflate_bytes(input_bytes)
.map_err(|e| TransformError::CompressionError(format!("DEFLATE failed: {}", e)))?;
let crc32_checksum = calculate_crc32(input_bytes);
let isize: u32 = input_bytes.len().try_into().map_err(|_| {
TransformError::CompressionError("Input too large for ISIZE (max 2^32 - 1)".into())
})?;
#[cfg(target_arch = "wasm32")]
let mtime: u32 = 0;
#[cfg(not(target_arch = "wasm32"))]
let mtime = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| {
TransformError::CompressionError(format!("Failed to get system time: {}", e))
})?
.as_secs()
.try_into()
.unwrap_or(0u32);
let mut output = Vec::with_capacity(10 + deflated_data.len() + 8);
output.push(ID1);
output.push(ID2);
output.push(CM_DEFLATE);
output.push(0); output.extend_from_slice(&mtime.to_le_bytes());
output.push(0); output.push(OS_UNKNOWN);
output.extend_from_slice(&deflated_data);
output.extend_from_slice(&crc32_checksum.to_le_bytes());
output.extend_from_slice(&isize.to_le_bytes());
Ok(base64_encode::base64_encode(&output))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transformers::gzip_decompress::GzipDecompress;
use crate::Transform;
#[test]
fn test_gzip_empty() {
let transformer = GzipCompress;
let result_base64 = transformer.transform("").unwrap();
let decompressor = GzipDecompress;
let decompressed_result = decompressor.transform(&result_base64);
assert!(
decompressed_result.is_ok(),
"Decompression failed: {:?}",
decompressed_result.err()
);
assert_eq!(decompressed_result.unwrap(), "");
}
#[test]
fn test_gzip_simple() {
let compressor = GzipCompress;
let decompressor = GzipDecompress;
let input = compressor.default_test_input();
let compressed_b64 = compressor.transform(input).unwrap();
let decompressed = decompressor.transform(&compressed_b64).unwrap();
assert_eq!(decompressed, input);
}
#[test]
fn test_gzip_repeated() {
let transformer = GzipCompress;
let input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let result_base64 = transformer.transform(input).unwrap();
let decompressor = GzipDecompress;
let decompressed_result = decompressor.transform(&result_base64);
assert!(
decompressed_result.is_ok(),
"Decompression failed: {:?}",
decompressed_result.err()
);
assert_eq!(decompressed_result.unwrap(), input);
}
}