diff --git a/Cargo.lock b/Cargo.lock index f70d1c4..6ad9fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-bigint" version = "0.5.3" @@ -671,6 +680,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2162,6 +2181,7 @@ dependencies = [ "base64", "chrono", "clap", + "flate2", "hex", "openpgp-card", "openpgp-card-pcsc", diff --git a/Cargo.toml b/Cargo.toml index f2a9d9f..1e21b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ aes-gcm-stream = "0.2.0" base64 = "0.21.0" chrono = "0.4.23" clap = { version = "4.1.4", features = ["derive"] } +flate2 = "1.0.27" hex = "0.4.3" openpgp-card = "0.3.7" openpgp-card-pcsc = "0.3.0" diff --git a/src/compress.rs b/src/compress.rs new file mode 100644 index 0000000..1be667d --- /dev/null +++ b/src/compress.rs @@ -0,0 +1,125 @@ +use std::io::Write; + +use flate2::Compression; +use flate2::write::{GzDecoder, GzEncoder}; +use rust_util::XResult; +use x509_parser::nom::AsBytes; + +pub struct GzStreamEncoder { + gz_encoder: GzEncoder>, +} + +impl GzStreamEncoder { + pub fn new_default() -> Self { + GzStreamEncoder::new(Compression::default()) + } + + pub fn new(compression: Compression) -> Self { + let buffer = Vec::with_capacity(1024 * 8); + let gz_encoder = GzEncoder::new(buffer, compression); + Self { gz_encoder } + } + + pub fn update(&mut self, buff: &[u8]) -> XResult> { + self.gz_encoder.write_all(buff)?; + let inner = self.gz_encoder.get_mut(); + let result = inner.clone(); + inner.clear(); + Ok(result) + } + + pub fn finalize(mut self) -> Result, String> { + match self.gz_encoder.finish() { + Ok(last_buffer) => Ok(last_buffer), + Err(e) => Err(format!("Decode stream failed: {}", e)), + } + } +} + +pub struct GzStreamDecoder { + gz_decoder: GzDecoder>, +} + +impl GzStreamDecoder { + pub fn new() -> Self { + let buffer = Vec::with_capacity(1024 * 8); + let gz_decoder = GzDecoder::new(buffer); + Self { gz_decoder } + } + + pub fn update(&mut self, buff: &[u8]) -> XResult> { + self.gz_decoder.write_all(buff)?; + let inner = self.gz_decoder.get_mut(); + let result = inner.clone(); + inner.clear(); + Ok(result) + } + + pub fn finalize(mut self) -> Result, String> { + match self.gz_decoder.finish() { + Ok(last_buffer) => Ok(last_buffer), + Err(e) => Err(format!("Decode stream failed: {}", e)), + } + } +} + +#[test] +fn test_gzip_compress() { + for (compressed, decompressed) in vec![ + ("1f8b0800000000000000f348cdc9c95708cf2fca49010056b1174a0b000000", "Hello World"), + ( + "1f8b0800000000000000f348cdc9c95708cf2fca49f12081090044f4575937000000", + "Hello WorldHello WorldHello WorldHello WorldHello World" + ), + ] { + let compressed = hex::decode(compressed).unwrap(); + let mut decoder = GzStreamDecoder::new(); + let mut decompressed_bytes = decoder.update(&compressed).unwrap(); + let last_buffer = decoder.finalize().unwrap(); + decompressed_bytes.extend_from_slice(&last_buffer); + assert_eq!(decompressed, String::from_utf8(decompressed_bytes).unwrap()); + } +} + +#[test] +fn test_gzip_compress_multi_blocks() { + let compressed = hex::decode("1f8b0800000000000000f348cdc9c95708cf2fca49f12081090044f4575937000000").unwrap(); + let decompressed = "Hello WorldHello WorldHello WorldHello WorldHello World"; + let mut decoder = GzStreamDecoder::new(); + let mut decompressed_bytes = vec![]; + for i in 0..compressed.len() { + let b = decoder.update(&compressed[i..i + 1]).unwrap(); + decompressed_bytes.extend_from_slice(&b); + } + let last_buffer = decoder.finalize().unwrap(); + decompressed_bytes.extend_from_slice(&last_buffer); + assert_eq!(decompressed, String::from_utf8(decompressed_bytes).unwrap()); +} + + +#[test] +fn test_gzip_decompress() { + for (compression, message) in vec![ + (Compression::default(), "Hello World"), + (Compression::default(), "Hello WorldHello WorldHello World"), + (Compression::default(), "Hello WorldHello WorldHello WorldHello WorldHello World"), + (Compression::default(), "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World"), + (Compression::none(), "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World"), + (Compression::fast(), "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World"), + (Compression::best(), "Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World"), + ] { + let mut encoder = GzStreamEncoder::new(compression); + let mut compressed_bytes = encoder.update(message.as_bytes()).unwrap(); + let last_compress_buffer = encoder.finalize().unwrap(); + compressed_bytes.extend_from_slice(&last_compress_buffer); + + let mut decoder = GzStreamDecoder::new(); + let mut decompressed_bytes = decoder.update(&compressed_bytes).unwrap(); + let last_decompress_buffer = decoder.finalize().unwrap(); + decompressed_bytes.extend_from_slice(&last_decompress_buffer); + + let decompressed_string = String::from_utf8(decompressed_bytes).unwrap(); + + assert_eq!(message, decompressed_string.as_str()); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4bb0e0e..b8b156c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use crate::cmd_encrypt::CmdEncrypt; use crate::cmd_info::CmdInfo; mod util; +mod compress; mod config; mod spec; mod crypto;